API Reference
Every Definite on-prem deployment exposes an HTTP API under /api/v1. It is the
same surface the web UI and the definite CLI use — the CLI's
definite run … subcommands are thin wrappers over these endpoints. Anything
you can do in the UI or the CLI you can do directly over HTTP, which is how you
drive the deployment from a script, a notebook, or a CI job.
Base URL
The API is served at your deployment's hostname:
https://<your-deployment-host>/api/v1
Behind the production ingress, only /api/*, /health, /mcp, and the OAuth
/.well-known/* paths reach the API; everything else serves the web UI. (The
FastAPI app also generates an OpenAPI schema at /openapi.json and interactive
docs at /docs, but those sit at the root and are shadowed by the UI through
the ingress — they're only reachable when you talk to the API container
directly, e.g. a kubectl port-forward to http://localhost:8000. Treat the
tables below as the canonical reference.)
Conventions
- Requests and responses are JSON. Send
Content-Type: application/jsonon any request with a body. - Authentication is a bearer token in the
Authorizationheader (see below). - Errors use standard HTTP status codes with a JSON body
{"detail": "..."}. Common codes:400bad request,401missing/expired token,403not permitted,404not found,413response too large,422validation error.
Authentication
All endpoints except POST /api/v1/auth/login require a bearer token:
Authorization: Bearer <token>
There are two kinds of token. Both are validated the same way, so either works on any authenticated endpoint.
Session tokens (interactive)
Exchange an email and password for a session token. Sessions are opaque (~43 chars) and expire after about two weeks.
curl -sS -X POST https://<host>/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"..."}'
# → {"token":"...","email":"you@example.com","expires_at":"2026-06-12T..."}
This is the local-Postgres auth path. On OIDC/SSO deployments, users sign in
through the browser; for programmatic access on those deployments, use an API
token (below). definite login stores a session token in
~/.definite/credentials.json for the CLI — see CLI reference.
API tokens (long-lived, for scripts & CI)
API tokens are long-lived bearer keys, prefixed def_, ideal for automation
that shouldn't re-enter a password or babysit a 2-week expiry. Create one in the
UI under Settings → API tokens, or over the API:
# Authenticated with a session token (or another API token):
curl -sS -X POST https://<host>/api/v1/auth/tokens \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"ci-pipeline","expires_at":null}'
# → {"token":"def_<32 hex>", "record":{"id":"...","name":"ci-pipeline","prefix":"def_xxxxxxxx",...}}
The full def_… string is shown once, at creation — store it somewhere safe.
expires_at is optional; null means the key never expires. Use the returned
token exactly like a session token:
curl -sS https://<host>/api/v1/queries \
-H "Authorization: Bearer def_..." \
-H "Content-Type: application/json" \
-d '{"sql":"SELECT 1 AS ok"}'
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/auth/tokens | List your API tokens (metadata only — never the secret) |
POST | /api/v1/auth/tokens | Create a token; body {name, scopes?, expires_at?}; returns the secret once |
DELETE | /api/v1/auth/tokens/{id} | Revoke a token immediately |
Scopes
A token's optional scopes array restricts what it can do. Leave it empty for
full access (the token carries its creator's role, the historical behavior). A
non-empty set narrows the token: it may only reach routes whose scope it
holds, and never more than the owner's role allows (effective access is role AND
scope — a scope can't grant something the owner's role wouldn't). Unknown scope
names are rejected with 422. Scopes are coarse and router-level; for
per-resource limits, prefer a dedicated low-role user plus content grants.
| Scope | Grants |
|---|---|
query:read | Run SQL queries and read the data catalog |
pipelines:read | View automation pipelines, transformations, scripts, agents, and their runs |
pipelines:write | Create, edit, archive, and delete pipelines, transformations, scripts, and agents |
pipelines:run | Trigger and cancel pipeline, transformation, and agent runs |
integrations:read | View integrations and connector metadata |
integrations:manage | Create, edit, test, and reveal integration credentials; manage event sources and OAuth |
docs:read | View docs, data apps, drive files, and projects |
docs:write | Create and edit docs, data apps, drive files, and projects; load data |
semantic:read | Read and query the semantic layer and ontology |
fi:use | Use Fi (threads, runs, memories) and the Fi sandbox |
admin | Administer the workspace: settings, users, tokens, license, and infrastructure |
# A read + trigger token for a pipeline chain (nothing else):
curl -sS -X POST https://<host>/api/v1/auth/tokens \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"reconcile-bot","scopes":["pipelines:read","pipelines:run"]}'
A scoped token hitting a route outside its scopes gets 403 naming the missing
scope. The same scopes apply to the MCP server: an unscoped def_ key
keeps working unchanged, and a scoped one may only call tools its scopes cover.
Back-compat. Tokens minted before scope enforcement shipped keep full access — their stored scopes (if any) are ignored, not retroactively enforced. Only tokens created after this release enforce their scopes.
Permissions caveat. An unscoped API token inherits the full permissions of the user who created it — an admin's token is an admin token, including DDL/DML through the query endpoint. Treat a
def_key as equivalent to its owner's password and revoke it the moment it's no longer needed.
Identity
curl -sS https://<host>/api/v1/auth/me -H "Authorization: Bearer $TOKEN"
# → {"user_id":"...","email":"...","is_admin":true,"role":"admin","actor_kind":"api_token","client_side_query":false}
actor_kind is login for a session token and api_token for a def_ key.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/auth/login | Email + password → session token (the only unauthenticated endpoint) |
GET | /api/v1/auth/me | The user behind the current token |
POST | /api/v1/auth/logout | Revoke the current session token |
POST | /api/v1/auth/me/password | Change your own password |
Running SQL
The core data endpoint. Runs one statement against the lakehouse and returns JSON rows.
curl -sS https://<host>/api/v1/queries \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql":"SELECT count(*) AS n FROM main.orders"}'
{
"columns": ["n"],
"rows": [{"n": 42}],
"row_count": 1,
"truncated": false,
"row_limit": 200000,
"duration_ms": 37
}
- Request body:
{"sql": "...", "compute_profile": "<name>"}.compute_profileis optional and routes the query through a declared compute profile; omit it for the default. - Read-only
SELECTstatements are open to any authenticated user (subject to table-level data-access grants). DDL/DML (CREATE/INSERT/UPDATE/…) is admin-only through this endpoint and returns emptyrows/columns. - Responses are capped at 200,000 rows / 100 MB; a larger result is
truncated (
truncated: true) or rejected with413. Narrow the query. duration_msis measured server-side around query execution and result materialization. It does not include client network latency.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/queries | Run one SQL statement; body {sql, compute_profile?} |
GET | /api/v1/queries/recent | Recently-run statements (deduped by SQL) |
GET | /api/v1/queries/saved | List saved queries |
POST | /api/v1/queries/saved | Save a query; body {name, sql} |
PATCH | /api/v1/queries/saved/{id} | Rename / edit a saved query |
DELETE | /api/v1/queries/saved/{id} | Delete a saved query |
Browsing the catalog
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/catalog/tables | List lakehouse schemas + tables |
GET | /api/v1/catalog/tables/{schema}/{name} | Table columns + metadata |
GET | /api/v1/catalog/tables/{schema}/{name}/preview | A small sample of rows |
Automations
Scheduled / triggered pipelines. See Automations.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/automations/pipelines | List pipelines (?include=last_runs&limit_runs=1 for recent runs) |
POST | /api/v1/automations/pipelines | Create a pipeline; body {name, definition, enabled, cron_schedule?, run_after_pipeline_id?, ...} |
GET | /api/v1/automations/pipelines/{id} | Get one pipeline (full definition) |
PATCH | /api/v1/automations/pipelines/{id} | Update name / definition / schedule / run-after dependency / enabled |
DELETE | /api/v1/automations/pipelines/{id} | Delete a pipeline |
POST | /api/v1/automations/pipelines/{id}/runs | Queue a manual run; body {trigger_type, lane?} |
GET | /api/v1/automations/runs/{run_id} | Run status, per-step output, and logs |
POST | /api/v1/automations/runs/{run_id}/cancel | Cancel a queued/running run (kills in-flight python steps) |
Reusable Python scripts that pipeline steps reference live under
/api/v1/automation-scripts (GET/POST/GET {id}/PATCH {id}/DELETE {id}).
Integrations
Saved source/destination connections. Secrets are write-only — list and get never return them. See Integrations.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/integrations/types | Available integration types + their config schema |
GET | /api/v1/integrations | List saved integrations (metadata only) |
POST | /api/v1/integrations | Create an integration |
GET | /api/v1/integrations/{id_or_name} | Get one integration's public config |
PATCH | /api/v1/integrations/{id_or_name} | Update config / secrets |
DELETE | /api/v1/integrations/{id_or_name} | Delete an integration |
POST | /api/v1/integrations/{id_or_name}/test | Run the server-side connection test |
Autonomous agents
Probe → decide → act agents. See Autonomous Agents.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/agents | List agents |
POST | /api/v1/agents | Create an agent |
GET | /api/v1/agents/{id} | Get one agent |
PATCH | /api/v1/agents/{id} | Update an agent |
DELETE | /api/v1/agents/{id} | Delete an agent |
POST | /api/v1/agents/{id}/runs | Queue a manual run |
GET | /api/v1/agents/{id}/runs | List recent runs for an agent |
GET | /api/v1/agent-runs/{run_id} | One run's probe result, decision, and action |
Fi (the agent)
Fi runs follow a thread → run → poll lifecycle. Dispatching a run is fire-and-forget; poll the thread for results.
# 1. New thread
TID=$(curl -sS https://<host>/api/v1/fi/threads -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{}' | jq -r .thread.id)
# 2. Dispatch a run
curl -sS https://<host>/api/v1/fi/threads/$TID/runs -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{"prompt":"how many rows are in main.orders?"}'
# 3. Poll the thread (or stream events, below)
curl -sS https://<host>/api/v1/fi/threads/$TID -H "Authorization: Bearer $TOKEN"
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/fi/threads | Create a thread; body {title?} |
GET | /api/v1/fi/threads | List threads |
GET | /api/v1/fi/threads/{id} | Get a thread (messages, title, status) |
POST | /api/v1/fi/threads/{id}/runs | Dispatch a run; body {prompt} |
GET | /api/v1/fi/threads/{id}/runs/{run_id} | One run's status (queued, running, succeeded, failed, stopped) |
GET | /api/v1/fi/threads/{id}/events | SSE stream of lifecycle events (not JSON — use curl -sN --max-time N) |
POST | /api/v1/fi/threads/{id}/stop | Stop the active run |
Data apps
Single-file React dashboards. See Data Apps for the lifecycle and manifest, and Data App Embedding for iframe sharing.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/data-apps | List data apps |
GET | /api/v1/data-apps?stale=true | List data apps built against an older runtime |
POST | /api/v1/data-apps | Upload an app; body includes the built index.html + manifest |
GET | /api/v1/data-apps/{slug} | App metadata |
GET | /api/v1/data-apps/{slug}/source | Download the app's stored source |
POST | /api/v1/data-apps/{slug}/query | Run one of the app's named SQL resources |
POST | /api/v1/data-apps/{slug}/rebuild | Rebuild an app with stored source against the current runtime |
POST | /api/v1/admin/data-apps/rebuild-all | Admin-only bulk rebuild for apps with stored source |
DELETE | /api/v1/data-apps/{slug} | Delete an app |
Files & loading data
Upload a local file to object storage, optionally registering it as a lakehouse
table. The CLI wraps this as definite run load.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/loads/presign | Get a presigned upload URL for a file |
POST | /api/v1/loads/register | Register an uploaded file as a lakehouse table |
POST | /api/v1/drive/presign | Presigned upload URL for a Drive file |
GET | /api/v1/drive/files | List Drive files |
GET | /api/v1/drive/files/{id}/download-url | Presigned download URL |
Stream ingest
Land JSON rows into any lakehouse table with real, typed columns, with
append / merge / replace semantics and optional table autocreate. See
Stream Ingest. For append-only product events into a fixed
schema, use Event Ingest instead.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/stream | Ingest rows into a table; body {data, config:{table, mode?, primary_key?, create?}} |
Send a one-off transactional email through the deployment's configured email
service (EMAIL_MODE=smtp or definite_cloud; see config → email). Any valid bearer token works — a session token or a def_
API token — the same auth as stream ingest; it is not admin-gated. If email is
disabled or unconfigured the route returns 503 with a clear message (not a
500); a delivery failure from the underlying mailer returns 502.
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/email/send | Send an email; body {to:[…], subject, html_body?, text_body?, attachments?} |
POST | /v3/email/message | SDK-compat alias; body {toEmails:[…], subject, body} (body is HTML) |
Caps mirror the cloud relay: 1 to 10 recipients, subject ≤ 200 chars, each
body ≤ 1 000 000 chars, ≤ 5 attachments. At least one of html_body / text_body
is required. Each attachment is {filename, content_type?, content_base64}.
curl -sS -X POST "$BASE_URL/api/v1/email/send" \
-H "Authorization: Bearer $DEFINITE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"to":["ops@example.com"],"subject":"Nightly load done","html_body":"<p>All green.</p>"}'
# → {"ok": true}
POST /v3/email/message is a compatibility alias for the cloud
POST /v3/email/message route the Definite Python SDK's send_email_message()
calls. A cloud pipeline becomes portable by pointing the SDK at this
deployment's base URL with an on-prem def_ token — no code change:
from definite_sdk.client import DefiniteClient
client = DefiniteClient(api_key="def_…", api_url="https://<your-deployment>")
client.message_client().send_email_message(
to_emails=["ops@example.com"], subject="Hi", body="<p>Hi</p>"
)
To send email from inside an automation pipeline, use the
send_email step instead — it delivers through the
same service without an HTTP hop or a token.
Lakehouse maintenance
Compaction, snapshot expiry, and orphan cleanup on the DuckLake tables. See Backup & Restore.
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/lakehouse/maintenance/stats | File / snapshot statistics |
POST | /api/v1/lakehouse/maintenance/preview | Dry-run a maintenance operation |
POST | /api/v1/lakehouse/maintenance/runs | Run an operation and poll it |
GET/PUT | /api/v1/lakehouse/maintenance/schedule | Read / set the maintenance schedule |
Event ingest sources
Browser-safe event ingest uses a separate public write key, not a def_
token — see Event Ingest. The management endpoints are
admin-only and bearer-authed:
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/events/sources | List event sources |
POST | /api/v1/events/sources | Create a source (returns its write key once) |
POST | /api/v1/events/sources/{id}/rotate | Rotate the write key |
DELETE | /api/v1/events/sources/{id} | Revoke a source |
Other surfaces
| Area | Prefix | Notes |
|---|---|---|
| License | /api/v1/license | GET the live entitlement (plan, expiry, features) |
| Workspace settings | /api/v1/workspace | Workspace info, branding, support access (admin) |
| Members & roles | /api/v1/auth/users | List/manage users and app roles (admin) |
| Permissions & sharing | /api/v1/grants, /api/v1/data-access | Content ACLs and table-level data-access roles |
| Semantic layer | /api/v1/semantic | Semantic models and queries |
| Transformations | /api/v1/transformations | SQL transformation models |
| MCP server | /mcp | Model Context Protocol endpoint (separate from /api) — see MCP Server |
Example: a benchmark script in Python
No CLI, no subprocess — just httpx against the API. Mirrors what
definite run query does under the hood.
import os
import time
import httpx
BASE = os.environ["DEFINITE_API_URL"].rstrip("/") # https://<host>
TOKEN = os.environ["DEFINITE_TOKEN"] # a def_ API token or session token
def run_query(sql: str) -> dict:
with httpx.Client(timeout=600.0) as client:
resp = client.post(
f"{BASE}/api/v1/queries",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"sql": sql},
)
resp.raise_for_status() # 401 bad token · 400 bad SQL · 413 result too large
return resp.json()
if __name__ == "__main__":
t0 = time.perf_counter()
result = run_query("SELECT count(*) AS n FROM main.orders")
print(result["rows"], f"({time.perf_counter() - t0:.3f}s)")
See also
- CLI reference — the
definiteCLI wraps these endpoints - Permissions — app roles, content sharing, table-level data access
- MCP Server — connect an LLM client over the Model Context Protocol