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:
- 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.
- 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:
| Status | What it means | Typical fix |
|---|---|---|
400 | Bad request — malformed input | Read the error.message; fix the payload. |
401 | Missing or invalid token | Check the Bearer header; rotate the key if expired. |
403 | Authenticated but not allowed | Your role doesn't grant this action. |
404 | Not found, or you don't have permission to know it exists | Check the ID and workspace scope. |
409 | Conflict — usually a uniqueness violation | Read the error.code; either retry with different input or merge state. |
429 | Rate limit exceeded | Back off (exponential, with jitter). |
5xx | Dalea side error | Retry 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.
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.