# Legal Case Tracker (/docs/receipes/legal-case-tracker)



This recipe tells an agent exactly how to build a legal case tracker in andibase.

Goal [#goal]

Create four things in one workspace:

1. A `legal-case` data definition.
2. A `legal-case-comment` data definition.
3. One agent that helps users manage case records and updates.
4. One mini app that shows case details, comments, and lets members update status.

Expected outcome [#expected-outcome]

After following this recipe, the workspace should have:

* a `legal-case` definition for matters and their current state
* a `legal-case-comment` definition for case notes and updates
* a `legal-case-agent` agent
* a `legal-case-tracker` mini app

Use the API documented in:

* [Data Model](/docs/data-model)
* [Apps](/docs/apps)
* [Get started (for AI Agents)](/docs/agent-get-started)
* [Data definitions API](/docs/api-reference/data-definitions)
* [Data API](/docs/api-reference/data)
* [Agents API](/docs/api-reference/agents)
* [Apps API reference](/docs/api-reference/apps)

Important scope note [#important-scope-note]

This recipe creates an operational tracker for legal matters.

It does not make legal decisions, give legal advice, or replace attorney review.

Agent instructions [#agent-instructions]

When an agent executes this recipe, it should follow these rules:

1. Create the `legal-case` definition first.
2. Create the `legal-case-comment` definition second.
3. In the current API, data definition handles are generated from `name`. Use `name: "Legal Case"` and `name: "Legal Case Comment"` so the created handles normalize to `legal-case` and `legal-case-comment`.
4. If your execution environment can resolve the created legal-case definition id, wire the comment-to-case link as a `relationship` field. If it cannot, use a text foreign key such as `caseRecordId` instead, because the public HTTP API still expects `dataDefinitionId` for relationship fields.
5. Create the legal-case agent with instructions focused on case intake, status updates, and timeline hygiene.
6. Create the mini app last.
7. Do not invent additional legal workflows, billing logic, or compliance fields unless the user explicitly asks for them.
8. Use sensible defaults and ask the user only when a missing choice materially changes the workflow, such as jurisdiction, visibility, or status policy.

Step 1: Create the data model [#step-1-create-the-data-model]

Create two data definitions: `legal-case` and `legal-case-comment`.

1\. Legal Case definition [#1-legal-case-definition]

Use `name: "Legal Case"`. The current API derives the handle from the name, so this will create the `legal-case` handle.

Recommended fields:

```json
{
  "name": "Legal Case",
  "description": "Tracks one legal matter, its current status, and operational details.",
  "fields": {
    "title": {
      "name": "Title",
      "type": "text"
    },
    "caseNumber": {
      "name": "Case number",
      "type": "text"
    },
    "clientName": {
      "name": "Client name",
      "type": "text"
    },
    "matterType": {
      "name": "Matter type",
      "type": "select",
      "options": [
        { "value": "litigation", "label": "Litigation", "color": "red" },
        { "value": "contract", "label": "Contract", "color": "blue" },
        { "value": "employment", "label": "Employment", "color": "amber" },
        { "value": "regulatory", "label": "Regulatory", "color": "violet" },
        { "value": "ip", "label": "IP", "color": "cyan" },
        { "value": "corporate", "label": "Corporate", "color": "emerald" },
        { "value": "other", "label": "Other", "color": "zinc" }
      ]
    },
    "jurisdiction": {
      "name": "Jurisdiction",
      "type": "text"
    },
    "courtOrForum": {
      "name": "Court or forum",
      "type": "text"
    },
    "assignedOwner": {
      "name": "Assigned owner",
      "type": "text"
    },
    "priority": {
      "name": "Priority",
      "type": "select",
      "options": [
        { "value": "low", "label": "Low", "color": "zinc" },
        { "value": "medium", "label": "Medium", "color": "blue" },
        { "value": "high", "label": "High", "color": "amber" },
        { "value": "critical", "label": "Critical", "color": "red" }
      ]
    },
    "status": {
      "name": "Status",
      "type": "select",
      "options": [
        { "value": "intake", "label": "Intake", "color": "sky" },
        { "value": "active", "label": "Active", "color": "green" },
        { "value": "waiting-on-client", "label": "Waiting on client", "color": "amber" },
        { "value": "waiting-on-court", "label": "Waiting on court", "color": "violet" },
        { "value": "review", "label": "Review", "color": "blue" },
        { "value": "closed", "label": "Closed", "color": "stone" },
        { "value": "archived", "label": "Archived", "color": "zinc" }
      ]
    },
    "openedOn": {
      "name": "Opened on",
      "type": "date"
    },
    "nextDeadline": {
      "name": "Next deadline",
      "type": "date"
    },
    "summary": {
      "name": "Summary",
      "type": "text",
      "variant": "long-text"
    },
    "details": {
      "name": "Details",
      "type": "text",
      "variant": "long-text"
    },
    "lastUpdateSummary": {
      "name": "Last update summary",
      "type": "text",
      "variant": "long-text"
    },
    "commentsCount": {
      "name": "Comments count",
      "type": "number"
    },
    "lastCommentAt": {
      "name": "Last comment at",
      "type": "timestamp"
    },
    "tags": {
      "name": "Tags",
      "type": "multi-select",
      "options": [
        { "value": "dispute", "label": "Dispute", "color": "red" },
        { "value": "urgent", "label": "Urgent", "color": "amber" },
        { "value": "client-facing", "label": "Client-facing", "color": "blue" },
        { "value": "internal", "label": "Internal", "color": "zinc" }
      ]
    }
  }
}
```

Recommended defaults for agent-created case rows:

* `priority`: `medium`
* `status`: `intake`
* `commentsCount`: `0`
* `openedOn`: today

2\. Legal Case Comment definition [#2-legal-case-comment-definition]

Use `name: "Legal Case Comment"`. The current API derives the handle from the name, so this will create the `legal-case-comment` handle.

Recommended fields:

```json
{
  "name": "Legal Case Comment",
  "description": "Tracks timeline comments, notes, and case updates.",
  "fields": {
    "caseRecordId": {
      "name": "Case record id",
      "description": "Store the linked legal-case row id when the client only has public HTTP API access.",
      "type": "text"
    },
    "authorName": {
      "name": "Author name",
      "type": "text"
    },
    "commentType": {
      "name": "Comment type",
      "type": "select",
      "options": [
        { "value": "note", "label": "Note", "color": "zinc" },
        { "value": "status-update", "label": "Status update", "color": "blue" },
        { "value": "deadline", "label": "Deadline", "color": "amber" },
        { "value": "client-update", "label": "Client update", "color": "cyan" },
        { "value": "court-update", "label": "Court update", "color": "violet" }
      ]
    },
    "visibility": {
      "name": "Visibility",
      "type": "select",
      "options": [
        { "value": "internal", "label": "Internal", "color": "zinc" },
        { "value": "shareable", "label": "Shareable", "color": "green" }
      ]
    },
    "commentDate": {
      "name": "Comment date",
      "type": "timestamp"
    },
    "body": {
      "name": "Body",
      "type": "text",
      "variant": "long-text"
    }
  }
}
```

If your client already has the created legal-case definition id, you can replace `caseRecordId` with:

```json
{
  "caseId": {
    "name": "Case",
    "type": "relationship",
    "dataDefinitionId": "<legal-case-definition-id>"
  }
}
```

3\. Example create requests [#3-example-create-requests]

Create the definitions with `POST /api/v1/data-definitions`.

Current API note:

* the create payload uses `name`, `description`, and `fields`
* the handle is derived from `name`
* data rows are created with `data`, not `attributes`

Example for `legal-case`:

```bash
curl -X POST "https://andibase.com/api/v1/data-definitions" \
  -H "Authorization: Bearer $ANDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Legal Case",
    "description": "Tracks one legal matter, its current status, and operational details.",
    "fields": {
      "title": { "name": "Title", "type": "text" },
      "caseNumber": { "name": "Case number", "type": "text" },
      "clientName": { "name": "Client name", "type": "text" },
      "status": {
        "name": "Status",
        "type": "select",
        "options": [
          { "value": "intake", "label": "Intake", "color": "sky" },
          { "value": "active", "label": "Active", "color": "green" },
          { "value": "closed", "label": "Closed", "color": "stone" }
        ]
      },
      "summary": { "name": "Summary", "type": "text", "variant": "long-text" },
      "details": { "name": "Details", "type": "text", "variant": "long-text" },
      "commentsCount": { "name": "Comments count", "type": "number" }
    }
  }'
```

Create sample case rows with `POST /api/v1/data-definitions/legal-case/data/upsert-many`.

```bash
curl -X POST "https://andibase.com/api/v1/data-definitions/legal-case/data/upsert-many" \
  -H "Authorization: Bearer $ANDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "data": {
          "title": "Acme vendor dispute",
          "caseNumber": "ACME-2026-014",
          "clientName": "Acme Corp",
          "matterType": "litigation",
          "jurisdiction": "Chile",
          "courtOrForum": "Santiago Civil Court",
          "assignedOwner": "María Soto",
          "priority": "high",
          "status": "active",
          "openedOn": "2026-03-20",
          "nextDeadline": "2026-03-28",
          "summary": "Vendor payment dispute involving alleged delivery delays.",
          "details": "Initial pleadings filed. Awaiting response deadlines and supporting invoices.",
          "commentsCount": 0
        }
      }
    ]
  }'
```

Step 2: Create the legal case agent [#step-2-create-the-legal-case-agent]

Create one workspace agent with handle `legal-case-agent`.

Recommended behavior:

* create new legal case records from structured or natural-language intake
* update case status, owner, deadlines, and narrative details
* add timeline comments when users describe a new development
* keep `commentsCount`, `lastCommentAt`, and `lastUpdateSummary` on the linked case in sync when it creates a comment
* summarize the latest operational state clearly without giving legal advice

Recommended agent payload:

```json
{
  "name": "Legal Case Agent",
  "handle": "legal-case-agent",
  "description": "Tracks legal matters, comments, deadlines, and status updates.",
  "model": "openai/gpt-5.4",
  "capabilities": {
    "webAccess": false,
    "browserAccess": false,
    "objectsAccess": true
  },
  "instructions": "You manage a workspace legal case tracker. Use the legal-case and legal-case-comment data definitions as the source of truth. Be action-oriented: make reasonable assumptions, complete the work when the intended outcome is clear, and avoid asking for confirmation on every small step. Create new case records when the user describes a new matter. Update existing case rows when the user changes status, ownership, deadlines, summary, or details. When the user provides a case update or note, create a legal-case-comment row linked to the correct case, then update the linked case commentsCount, lastCommentAt, and lastUpdateSummary. If multiple cases could match, ask one short clarifying question. Do not provide legal advice or legal conclusions. Stay focused on tracking, organization, and operational summaries."
}
```

Create the agent with `POST /api/v1/agents`.

Step 3: Create the mini app [#step-3-create-the-mini-app]

Create one app with handle `legal-case-tracker`.

Mini app requirements:

* read cases and comments using `window.andibase.fetch`
* show a list of cases
* show one selected case detail view
* display summary, details, metadata, and comments for the selected case
* allow a member to update status for the selected case
* allow a member to add a new comment for the selected case
* keep styling simple and flat
* prefer shared `@andibase/ui` components with default styles

Current API note:

* `POST /api/v1/apps` currently accepts `name`, `handle`, `description`, and `code`
* app access fields such as `memberRole` and `publicPermissions` are server-managed defaults on create
* new apps are created as member-only by default

Example app code:

```jsx
import { useEffect, useMemo, useState } from "react";
import { Badge } from "@andibase/ui/badge";
import { Button } from "@andibase/ui/button";
import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from "@andibase/ui/card";
import { Label } from "@andibase/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@andibase/ui/select";
import { Textarea } from "@andibase/ui/textarea";

const statusOptions = [
  "intake",
  "active",
  "waiting-on-client",
  "waiting-on-court",
  "review",
  "closed",
  "archived",
];

async function fetchJson(path, init) {
  const response = await window.andibase.fetch(path, init);
  if (!response.ok) {
    throw new Error(`Request failed with ${response.status}`);
  }
  return response.json();
}

export default function App() {
  const [cases, setCases] = useState([]);
  const [comments, setComments] = useState([]);
  const [selectedCaseId, setSelectedCaseId] = useState(null);
  const [nextStatus, setNextStatus] = useState("");
  const [commentBody, setCommentBody] = useState("");
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");

  async function load() {
    setLoading(true);
    setError("");

    try {
      const [casesPayload, commentsPayload] = await Promise.all([
        fetchJson("/data-definitions/legal-case/query?page=1&pageSize=100"),
        fetchJson("/data-definitions/legal-case-comment/query?page=1&pageSize=200"),
      ]);

      const nextCases = casesPayload.items ?? [];
      const nextComments = commentsPayload.items ?? [];

      setCases(nextCases);
      setComments(nextComments);
      setSelectedCaseId((current) => current ?? nextCases[0]?.id ?? null);
    } catch (loadError) {
      setError(loadError instanceof Error ? loadError.message : "Failed to load cases");
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    load();
  }, []);

  const selectedCase = useMemo(
    () => cases.find((item) => item.id === selectedCaseId) ?? null,
    [cases, selectedCaseId],
  );

  const selectedComments = useMemo(() => {
    return comments
      .filter((item) => item.data?.caseRecordId === selectedCaseId)
      .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
  }, [comments, selectedCaseId]);

  useEffect(() => {
    setNextStatus(selectedCase?.data?.status ?? "");
  }, [selectedCase?.id, selectedCase?.data?.status]);

  async function updateStatus() {
    if (!selectedCase || !nextStatus) return;

    setSaving(true);
    setError("");

    try {
      await fetchJson("/data-definitions/legal-case/data/patch-many", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          items: [
            {
              id: selectedCase.id,
              data: {
                status: nextStatus,
                lastUpdateSummary: `Status changed to ${nextStatus}.`,
              },
            },
          ],
        }),
      });

      await load();
    } catch (saveError) {
      setError(saveError instanceof Error ? saveError.message : "Failed to update status");
    } finally {
      setSaving(false);
    }
  }

  async function addComment() {
    if (!selectedCase || !commentBody.trim()) return;

    setSaving(true);
    setError("");

    try {
      await fetchJson("/data-definitions/legal-case-comment/data/upsert-many", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          items: [
            {
              data: {
                caseRecordId: selectedCase.id,
                authorName: "Workspace member",
                commentType: "note",
                visibility: "internal",
                commentDate: new Date().toISOString(),
                body: commentBody.trim(),
              },
            },
          ],
        }),
      });

      await fetchJson("/data-definitions/legal-case/data/patch-many", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          items: [
            {
              id: selectedCase.id,
              data: {
                commentsCount: Number(selectedCase.data?.commentsCount ?? 0) + 1,
                lastCommentAt: new Date().toISOString(),
                lastUpdateSummary: commentBody.trim(),
              },
            },
          ],
        }),
      });

      setCommentBody("");
      await load();
    } catch (saveError) {
      setError(saveError instanceof Error ? saveError.message : "Failed to add comment");
    } finally {
      setSaving(false);
    }
  }

  if (loading) {
    return <main style={{ padding: 24 }}>Loading...</main>;
  }

  return (
    <main style={{ padding: 24 }}>
      <div style={{ display: "grid", gap: 16 }}>
        <h1>Legal case tracker</h1>
        {error ? <p>{error}</p> : null}
        <div
          style={{
            display: "grid",
            gap: 16,
            gridTemplateColumns: "minmax(240px, 320px) minmax(0, 1fr)",
          }}
        >
          <Card>
            <CardHeader>
              <CardTitle>Cases</CardTitle>
            </CardHeader>
            <CardContent>
              <div style={{ display: "grid", gap: 8 }}>
                {cases.map((item) => (
                  <Button
                    key={item.id}
                    variant={item.id === selectedCaseId ? "default" : "outline"}
                    onClick={() => setSelectedCaseId(item.id)}
                    style={{ justifyContent: "flex-start" }}
                  >
                    {item.data?.title ?? item.id}
                  </Button>
                ))}
              </div>
            </CardContent>
          </Card>

          <div style={{ display: "grid", gap: 16 }}>
            <Card>
              <CardHeader>
                <CardTitle>{selectedCase?.data?.title ?? "Select a case"}</CardTitle>
              </CardHeader>
              <CardContent>
                {selectedCase ? (
                  <div style={{ display: "grid", gap: 12 }}>
                    <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                      <Badge>{selectedCase.data?.status ?? "unknown"}</Badge>
                      <Badge variant="outline">{selectedCase.data?.priority ?? "no priority"}</Badge>
                    </div>
                    <div>
                      <strong>Client:</strong> {selectedCase.data?.clientName ?? "-"}
                    </div>
                    <div>
                      <strong>Case number:</strong> {selectedCase.data?.caseNumber ?? "-"}
                    </div>
                    <div>
                      <strong>Assigned owner:</strong> {selectedCase.data?.assignedOwner ?? "-"}
                    </div>
                    <div>
                      <strong>Next deadline:</strong> {selectedCase.data?.nextDeadline ?? "-"}
                    </div>
                    <div>
                      <strong>Summary:</strong>
                      <p>{selectedCase.data?.summary ?? "-"}</p>
                    </div>
                    <div>
                      <strong>Details:</strong>
                      <p>{selectedCase.data?.details ?? "-"}</p>
                    </div>
                    <div style={{ display: "grid", gap: 8, maxWidth: 280 }}>
                      <Label>Status</Label>
                      <Select value={nextStatus} onValueChange={setNextStatus}>
                        <SelectTrigger>
                          <SelectValue placeholder="Select status" />
                        </SelectTrigger>
                        <SelectContent>
                          {statusOptions.map((status) => (
                            <SelectItem key={status} value={status}>
                              {status}
                            </SelectItem>
                          ))}
                        </SelectContent>
                      </Select>
                      <Button onClick={updateStatus} disabled={saving || !nextStatus}>
                        Update status
                      </Button>
                    </div>
                  </div>
                ) : (
                  <p>No case selected.</p>
                )}
              </CardContent>
            </Card>

            <Card>
              <CardHeader>
                <CardTitle>Comments</CardTitle>
              </CardHeader>
              <CardContent>
                <div style={{ display: "grid", gap: 12 }}>
                  <Textarea
                    value={commentBody}
                    onChange={(event) => setCommentBody(event.target.value)}
                    placeholder="Add an internal case update or note."
                    rows={4}
                  />
                  <Button onClick={addComment} disabled={saving || !selectedCase}>
                    Add comment
                  </Button>
                  <div style={{ display: "grid", gap: 8 }}>
                    {selectedComments.length > 0 ? (
                      selectedComments.map((item) => (
                        <Card key={item.id}>
                          <CardContent style={{ paddingTop: 16 }}>
                            <div style={{ display: "grid", gap: 8 }}>
                              <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                                <Badge variant="outline">{item.data?.commentType ?? "note"}</Badge>
                                <Badge variant="outline">{item.data?.visibility ?? "internal"}</Badge>
                              </div>
                              <div>{item.data?.body ?? "-"}</div>
                            </div>
                          </CardContent>
                        </Card>
                      ))
                    ) : (
                      <p>No comments yet.</p>
                    )}
                  </div>
                </div>
              </CardContent>
            </Card>
          </div>
        </div>
      </div>
    </main>
  );
}
```

Create the app with `POST /api/v1/apps`.

Recommended acceptance checks [#recommended-acceptance-checks]

An agent should consider the recipe complete only if all of the following are true:

1. `legal-case` exists and includes the required fields.
2. `legal-case-comment` exists and includes either the `caseId` relationship or the `caseRecordId` fallback used by the current public HTTP API flow.
3. `legal-case-agent` exists.
4. `legal-case-tracker` exists.
5. At least one sample legal-case row can be created successfully.
6. At least one sample legal-case-comment row can be created successfully.
7. The app loads, shows case details, accepts a new comment, and updates case status without runtime auth errors.

Minimal delivery summary [#minimal-delivery-summary]

When the agent finishes, it should report:

* the created data definition handles
* the created agent handle
* the created app handle
* whether the app is member-only
* any assumptions made for status workflow, matter type defaults, or comment visibility


## Documentation Navigation
Use these paths to traverse the relevant docs and generated API reference files for the app.
- Legal Case Tracker [current] -> `/docs/receipes/legal-case-tracker` (source: `content/docs/receipes/legal-case-tracker.mdx`)
- andibase Overview -> `/docs` (source: `content/docs/index.mdx`)
- Get started (for AI Agents) -> `/docs/agent-get-started` (source: `content/docs/agent-get-started.mdx`)
- Agent Tools -> `/docs/agent-tools` (source: `content/docs/agent-tools.mdx`)
- Agents -> `/docs/agents` (source: `content/docs/agents.mdx`)
- Agent Auth -> `/docs/api-reference/agent-auth`
- Get agent login request -> `/docs/api-reference/agent-auth/get-api-v1-agent-auth-requests-usercode`
- Exchange agent login -> `/docs/api-reference/agent-auth/post-api-v1-agent-auth-exchange`
- Start agent login -> `/docs/api-reference/agent-auth/post-api-v1-agent-auth-requests`
- Approve agent login -> `/docs/api-reference/agent-auth/post-api-v1-agent-auth-requests-usercode-approve`
- Deny agent login -> `/docs/api-reference/agent-auth/post-api-v1-agent-auth-requests-usercode-deny`
- Agents -> `/docs/api-reference/agents`
- Delete workspace agent -> `/docs/api-reference/agents/delete-api-v1-agents-id`
- List workspace agents -> `/docs/api-reference/agents/get-api-v1-agents`
- Get workspace agent -> `/docs/api-reference/agents/get-api-v1-agents-id`
- List agent chats -> `/docs/api-reference/agents/get-api-v1-agents-id-chats`
- Get agent chat -> `/docs/api-reference/agents/get-api-v1-agents-id-chats-chatid`
- Update workspace agent -> `/docs/api-reference/agents/patch-api-v1-agents-id`
- Create workspace agent -> `/docs/api-reference/agents/post-api-v1-agents`
- Send agent message -> `/docs/api-reference/agents/post-api-v1-agents-id-chats-chatid-messages`
- Apps -> `/docs/api-reference/apps`
- Delete app -> `/docs/api-reference/apps/delete-api-v1-apps-id`
- List workspace apps -> `/docs/api-reference/apps/get-api-v1-apps`
- Get app by id -> `/docs/api-reference/apps/get-api-v1-apps-id`
- Update app -> `/docs/api-reference/apps/patch-api-v1-apps-id`
- Create app -> `/docs/api-reference/apps/post-api-v1-apps`
- Automations -> `/docs/api-reference/automations`
- Delete workspace automation -> `/docs/api-reference/automations/delete-api-v1-automations-id`
- List workspace automations -> `/docs/api-reference/automations/get-api-v1-automations`
- Get workspace automation -> `/docs/api-reference/automations/get-api-v1-automations-id`
- List automation runs -> `/docs/api-reference/automations/get-api-v1-automations-id-runs`
- Get automation run -> `/docs/api-reference/automations/get-api-v1-automations-id-runs-runid`
- Update workspace automation -> `/docs/api-reference/automations/patch-api-v1-automations-id`
- Create workspace automation -> `/docs/api-reference/automations/post-api-v1-automations`
- Run automation -> `/docs/api-reference/automations/post-api-v1-automations-id-run`
- Trigger automation webhook -> `/docs/api-reference/automations/post-api-v1-automations-webhooks-publicid-secret`
- Channels -> `/docs/api-reference/channels`
- Delete channel -> `/docs/api-reference/channels/delete-api-v1-channels-channelid`
- List channels -> `/docs/api-reference/channels/get-api-v1-channels`
- Get channel -> `/docs/api-reference/channels/get-api-v1-channels-channelid`
- Update channel -> `/docs/api-reference/channels/patch-api-v1-channels-channelid`
- Create channel -> `/docs/api-reference/channels/post-api-v1-channels`
- Data -> `/docs/api-reference/data`
- Data Definitions -> `/docs/api-reference/data-definitions`
- Delete data definition -> `/docs/api-reference/data-definitions/delete-api-v1-data-definitions-id`
- List data definitions -> `/docs/api-reference/data-definitions/get-api-v1-data-definitions`
- Get data definition -> `/docs/api-reference/data-definitions/get-api-v1-data-definitions-id`
- Update data definition -> `/docs/api-reference/data-definitions/patch-api-v1-data-definitions-id`
- Create data definition -> `/docs/api-reference/data-definitions/post-api-v1-data-definitions`
- Data SQL Query -> `/docs/api-reference/data-sql-query`
- Run SQL query against workspace data -> `/docs/api-reference/data-sql-query/post-api-v1-data-sql-query`
- Get data by id -> `/docs/api-reference/data/get-api-v1-data-definitions-definitionid-data-id`
- Select all data row ids -> `/docs/api-reference/data/get-api-v1-data-definitions-definitionid-data-select-all`
- List data -> `/docs/api-reference/data/get-api-v1-data-definitions-definitionid-query`
- Patch many data rows -> `/docs/api-reference/data/patch-api-v1-data-definitions-definitionid-data-patch-many`
- Delete many data rows -> `/docs/api-reference/data/post-api-v1-data-definitions-definitionid-data-delete-many`
- Upsert many data rows -> `/docs/api-reference/data/post-api-v1-data-definitions-definitionid-data-upsert-many`
- DuckDB Query -> `/docs/api-reference/duckdb-query`
- Run a DuckDB query against registered sources -> `/docs/api-reference/duckdb-query/post-api-v1-duckdb-query`
- Explorer -> `/docs/api-reference/explorer`
- Delete explorer folder -> `/docs/api-reference/explorer/delete-api-v1-workspace-nodes-nodeid`
- List explorer nodes -> `/docs/api-reference/explorer/get-api-v1-workspace-nodes`
- List explorer folders -> `/docs/api-reference/explorer/get-api-v1-workspace-nodes-folders`
- Rename explorer folder -> `/docs/api-reference/explorer/patch-api-v1-workspace-nodes-nodeid-rename`
- Create folder -> `/docs/api-reference/explorer/post-api-v1-workspace-nodes-folders`
- Move explorer node -> `/docs/api-reference/explorer/post-api-v1-workspace-nodes-nodeid-move`
- Files -> `/docs/api-reference/files`
- List workspace files -> `/docs/api-reference/files/get-api-v1-files`
- Read file content -> `/docs/api-reference/files/get-api-v1-files-fileid-content`
- Create file -> `/docs/api-reference/files/post-api-v1-files`
- Complete file upload -> `/docs/api-reference/files/post-api-v1-files-fileid-complete`
- Presign multipart parts -> `/docs/api-reference/files/post-api-v1-files-fileid-parts`
- Update file content -> `/docs/api-reference/files/put-api-v1-files-fileid-content`
- Messages -> `/docs/api-reference/messages`
- List channel messages -> `/docs/api-reference/messages/get-api-v1-channels-channelid-messages`
- List thread messages -> `/docs/api-reference/messages/get-api-v1-channels-channelid-threads-threadid-messages`
- Create channel message -> `/docs/api-reference/messages/post-api-v1-channels-channelid-messages`
- Create thread message -> `/docs/api-reference/messages/post-api-v1-channels-channelid-threads-threadid-messages`
- Notifications -> `/docs/api-reference/notifications`
- List notification devices -> `/docs/api-reference/notifications/get-api-v1-notifications-devices`
- Check Expo notification receipts -> `/docs/api-reference/notifications/post-api-v1-notifications-receipts`
- Send workspace notifications -> `/docs/api-reference/notifications/post-api-v1-notifications-send`
- Runs -> `/docs/api-reference/runs`
- List workspace runs -> `/docs/api-reference/runs/get-api-v1-runs`
- Get workspace run -> `/docs/api-reference/runs/get-api-v1-runs-runid`
- Threads -> `/docs/api-reference/threads`
- List channel threads -> `/docs/api-reference/threads/get-api-v1-channels-channelid-threads`
- Get thread -> `/docs/api-reference/threads/get-api-v1-channels-channelid-threads-threadid`
- Create thread -> `/docs/api-reference/threads/post-api-v1-channels-channelid-threads`
- Workflows -> `/docs/api-reference/workflows`
- Delete workflow definition -> `/docs/api-reference/workflows/delete-api-v1-workflows-id`
- List workflow definitions -> `/docs/api-reference/workflows/get-api-v1-workflows`
- Get workflow definition -> `/docs/api-reference/workflows/get-api-v1-workflows-id`
- Update workflow definition -> `/docs/api-reference/workflows/patch-api-v1-workflows-id`
- Create workflow definition -> `/docs/api-reference/workflows/post-api-v1-workflows`
- Workspaces -> `/docs/api-reference/workspaces`
- Delete workspace API key -> `/docs/api-reference/workspaces/delete-api-v1-workspaces-workspacehandle-api-keys-keyid`
- Delete workspace credential -> `/docs/api-reference/workspaces/delete-api-v1-workspaces-workspacehandle-credentials-credentialid`
- Delete workspace invitation -> `/docs/api-reference/workspaces/delete-api-v1-workspaces-workspacehandle-invitations-invitationid`
- Delete workspace user -> `/docs/api-reference/workspaces/delete-api-v1-workspaces-workspacehandle-users-userid`
- List my workspaces -> `/docs/api-reference/workspaces/get-api-v1-workspaces`
- List workspace API keys -> `/docs/api-reference/workspaces/get-api-v1-workspaces-workspacehandle-api-keys`
- List workspace credentials -> `/docs/api-reference/workspaces/get-api-v1-workspaces-workspacehandle-credentials`
- List workspace credential tools -> `/docs/api-reference/workspaces/get-api-v1-workspaces-workspacehandle-credentials-credentialid-tools`
- List workspace invitations -> `/docs/api-reference/workspaces/get-api-v1-workspaces-workspacehandle-invitations`
- List workspace users -> `/docs/api-reference/workspaces/get-api-v1-workspaces-workspacehandle-users`
- Create workspace -> `/docs/api-reference/workspaces/post-api-v1-workspaces`
- Create workspace API key -> `/docs/api-reference/workspaces/post-api-v1-workspaces-workspacehandle-api-keys`
- Create workspace credential -> `/docs/api-reference/workspaces/post-api-v1-workspaces-workspacehandle-credentials`
- Invite user to workspace -> `/docs/api-reference/workspaces/post-api-v1-workspaces-workspacehandle-invitations`
- Create workspace user -> `/docs/api-reference/workspaces/post-api-v1-workspaces-workspacehandle-users`
- Apps -> `/docs/apps` (source: `content/docs/apps.mdx`)
- Authentication -> `/docs/authentication` (source: `content/docs/authentication.mdx`)
- Building Blocks -> `/docs/building-blocks` (source: `content/docs/building-blocks.mdx`)
- Data Model -> `/docs/data-model` (source: `content/docs/data-model.mdx`)
- Embedded Host Actions -> `/docs/embedded-host-actions` (source: `content/docs/embedded-host-actions.mdx`)
- Introduction -> `/docs/introduction` (source: `content/docs/introduction.mdx`)
- Recipes -> `/docs/receipes` (source: `content/docs/receipes/index.mdx`)
- Construction Site Visit Agent -> `/docs/receipes/construction-site-visit-agent` (source: `content/docs/receipes/construction-site-visit-agent.mdx`)
- Daily Lead Qualification Agent -> `/docs/receipes/daily-lead-qualification-agent` (source: `content/docs/receipes/daily-lead-qualification-agent.mdx`)
- Day Planner Agent -> `/docs/receipes/day-planner-agent` (source: `content/docs/receipes/day-planner-agent.mdx`)
- Expense Tracker -> `/docs/receipes/expense-tracker` (source: `content/docs/receipes/expense-tracker.mdx`)
- Spare Parts Request Agent -> `/docs/receipes/spare-parts-request-agent` (source: `content/docs/receipes/spare-parts-request-agent.mdx`)
- Weekly Report Email Agent -> `/docs/receipes/weekly-report-email-agent` (source: `content/docs/receipes/weekly-report-email-agent.mdx`)
- UI Components -> `/docs/ui-components` (source: `content/docs/ui-components.mdx`)