REST API quickstart

Make your first call; link out to the live OpenAPI spec.

This page gets you from zero to a successful API call. For the complete operation catalogue, see the live OpenAPI spec at /api-docs — that's the authoritative reference for every endpoint, schema and example.

Base URL

Cloud: https://dalea.app. Enterprise dedicated tenants: substitute your tenant URL.

All public endpoints live under /api/v1/. Stable; backwards-compatible within the major version.

Authentication

Every call needs a Bearer token. The fastest path:

  1. Get a workspace API key

    Settings → Security → API keys → New key. Pick a workspace and a role. See Authentication for the complete picture, including OAuth and MCP tokens.

  2. Send it on the Authorization header
    Authorization: Bearer dalea_xxxxxxxxx

Your first call

List the documents you can see:

curl -sS "https://dalea.app/api/v1/workspaces/$WORKSPACE_ID/documents?limit=10" \
  -H "Authorization: Bearer $DALEA_API_KEY"
import os, requests

WORKSPACE = os.environ["DALEA_WORKSPACE_ID"]
KEY       = os.environ["DALEA_API_KEY"]

resp = requests.get(
    f"https://dalea.app/api/v1/workspaces/{WORKSPACE}/documents",
    headers={"Authorization": f"Bearer {KEY}"},
    params={"limit": 10},
)
resp.raise_for_status()
for d in resp.json()["items"]:
    print(d["id"], d["title"])
const resp = await fetch(
  `https://dalea.app/api/v1/workspaces/${process.env.DALEA_WORKSPACE_ID}/documents?limit=10`,
  { headers: { Authorization: `Bearer ${process.env.DALEA_API_KEY}` } },
);
if (!resp.ok) throw new Error(`Dalea API ${resp.status}`);
const { items } = await resp.json();
items.forEach((d: any) => console.log(d.id, d.title));

Response shape

Successful list responses follow a standard envelope:

{
  "items": [ /* array of resources */ ],
  "total": 42,
  "next_cursor": "cmd2..."  // present when more pages exist
}

Single-resource reads return the resource object directly (not wrapped).

Pagination

List endpoints support cursor pagination. Pass the previous response's next_cursor as the cursor query param to get the next page.

def paginate(url, headers, params):
    while True:
        r = requests.get(url, headers=headers, params=params)
        r.raise_for_status()
        body = r.json()
        yield from body["items"]
        if not body.get("next_cursor"):
            return
        params["cursor"] = body["next_cursor"]

For very large pulls, set limit to its maximum (typically 200) to reduce round-trips. Don't loop without a cursor — there's no fallback offset pagination.

Filtering and sorting

Most list endpoints accept filter and sort query params:

?project_id=proj_123&updated_after=2026-04-01T00:00:00Z&sort=updated_at:desc

Allowed filter and sort fields are documented per-endpoint in the OpenAPI spec. Apply them at the API layer rather than fetching everything and filtering client-side — it's faster and respects rate limits.

Errors

Standard HTTP semantics:

StatusWhat it meansTypical fix
400Bad request — malformed inputRead the error.message; fix the payload.
401Missing or invalid tokenCheck the Bearer header; rotate the key if expired.
403Authenticated but not allowedYour role doesn't grant this action.
404Not found, or you don't have permission to know it existsCheck the ID and workspace scope.
409Conflict — usually a uniqueness violationRead the error.code; either retry with different input or merge state.
429Rate limit exceededBack off (exponential, with jitter).
5xxDalea side errorRetry with backoff; persistent 5xx is worth filing a support ticket.

Error responses always have this shape:

{
  "error": {
    "code": "validation.required_field",
    "message": "Field 'title' is required.",
    "field": "title"
  }
}

Reacting to changes

Native event webhooks are on the roadmap but not yet available. Until they ship, the supported pattern is polling the REST API on a schedule. Most list endpoints accept an updated_after filter:

# Every 5 minutes, fetch result batches closed since the last poll
since = load_last_seen_timestamp()
resp = requests.get(
    f"https://dalea.app/api/v1/result-batches",
    headers=auth_headers,
    params={"workspace_id": ws, "status": "closed", "updated_after": since},
)
for batch in resp.json()["items"]:
    handle_closed_batch(batch)
save_last_seen_timestamp(now())

Five minutes is a reasonable default for most workflows; tighten if you have truly time-sensitive needs. Always persist the cursor (updated_after timestamp) durably so a restart doesn't replay history.

What you can do

The OpenAPI spec at /api-docs is the canonical list of operations with stable, published paths. Headline domains:

  • Auth — sign in, manage sessions, organisations, workspaces, members, invitations, OAuth clients and API keys, audit trail review.
  • Documents — documents and folders, projects, blocks (read/append/insert/update/delete/outline), version history, comments and notifications, in-app marketplace gateway.
  • Data — environments, tables, columns, objects, naming schemes, saved queries, result batches, schema imports, Benchling integration, archive lifecycle.
  • Inventory — item types, containers, items, lots, placements, sessions (receiving, staging, checkout), labels, GS1, inventory import.
  • AI — agentic chat (SSE), conversations, MCP tool bridge, approvals, usage.
  • Storage — file uploads (multipart, base64, from URL), presigned downloads.
  • Search — workspace full-text search and activity feed.
  • Marketplace (dalea.market only) — packages, profiles, reviews, discussions, stars, reports.

Idiomatic patterns

The exact paths, request shapes, and response schemas live in the OpenAPI spec at /api-docs — always check there before writing code. The patterns below are conceptual; substitute the operation IDs you find in the spec.

Listing entities is always an HTTP GET with optional filter and pagination params:

resp = requests.get(
    f"https://dalea.app/api/v1/<resource>",
    headers=auth_headers,
    params={"workspace_id": ws, "limit": 200},
)

Creating entities is POST with a JSON body. Anything that mutates state takes an audit_reason field — supply a short string explaining the change, because it lands in the audit log forever.

Bulk operations exist for high-volume surfaces (registering many objects, recording many results). They follow the same pattern but accept arrays under keys like objects or records. Look for endpoints with /bulk in the path in the spec.

Reactive workflows — until native webhooks ship, poll the relevant list endpoint with updated_after set to the timestamp you last saw.

The spec is the source of truth

Every operation in the public REST API is documented at /api-docs with full request and response schemas. If something looks ambiguous, check there before guessing — the spec is generated from the same handlers your call hits.

What's next