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/json on any request with a body.
  • Authentication is a bearer token in the Authorization header (see below).
  • Errors use standard HTTP status codes with a JSON body {"detail": "..."}. Common codes: 400 bad request, 401 missing/expired token, 403 not permitted, 404 not found, 413 response too large, 422 validation 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"}'
MethodPathPurpose
GET/api/v1/auth/tokensList your API tokens (metadata only — never the secret)
POST/api/v1/auth/tokensCreate 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.

ScopeGrants
query:readRun SQL queries and read the data catalog
pipelines:readView automation pipelines, transformations, scripts, agents, and their runs
pipelines:writeCreate, edit, archive, and delete pipelines, transformations, scripts, and agents
pipelines:runTrigger and cancel pipeline, transformation, and agent runs
integrations:readView integrations and connector metadata
integrations:manageCreate, edit, test, and reveal integration credentials; manage event sources and OAuth
docs:readView docs, data apps, drive files, and projects
docs:writeCreate and edit docs, data apps, drive files, and projects; load data
semantic:readRead and query the semantic layer and ontology
fi:useUse Fi (threads, runs, memories) and the Fi sandbox
adminAdminister 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.

MethodPathPurpose
POST/api/v1/auth/loginEmail + password → session token (the only unauthenticated endpoint)
GET/api/v1/auth/meThe user behind the current token
POST/api/v1/auth/logoutRevoke the current session token
POST/api/v1/auth/me/passwordChange 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_profile is optional and routes the query through a declared compute profile; omit it for the default.
  • Read-only SELECT statements 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 empty rows/columns.
  • Responses are capped at 200,000 rows / 100 MB; a larger result is truncated (truncated: true) or rejected with 413. Narrow the query.
  • duration_ms is measured server-side around query execution and result materialization. It does not include client network latency.
MethodPathPurpose
POST/api/v1/queriesRun one SQL statement; body {sql, compute_profile?}
GET/api/v1/queries/recentRecently-run statements (deduped by SQL)
GET/api/v1/queries/savedList saved queries
POST/api/v1/queries/savedSave 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

MethodPathPurpose
GET/api/v1/catalog/tablesList lakehouse schemas + tables
GET/api/v1/catalog/tables/{schema}/{name}Table columns + metadata
GET/api/v1/catalog/tables/{schema}/{name}/previewA small sample of rows

Automations

Scheduled / triggered pipelines. See Automations.

MethodPathPurpose
GET/api/v1/automations/pipelinesList pipelines (?include=last_runs&limit_runs=1 for recent runs)
POST/api/v1/automations/pipelinesCreate 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}/runsQueue 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}/cancelCancel 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.

MethodPathPurpose
GET/api/v1/integrations/typesAvailable integration types + their config schema
GET/api/v1/integrationsList saved integrations (metadata only)
POST/api/v1/integrationsCreate 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}/testRun the server-side connection test

Autonomous agents

Probe → decide → act agents. See Autonomous Agents.

MethodPathPurpose
GET/api/v1/agentsList agents
POST/api/v1/agentsCreate 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}/runsQueue a manual run
GET/api/v1/agents/{id}/runsList 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"
MethodPathPurpose
POST/api/v1/fi/threadsCreate a thread; body {title?}
GET/api/v1/fi/threadsList threads
GET/api/v1/fi/threads/{id}Get a thread (messages, title, status)
POST/api/v1/fi/threads/{id}/runsDispatch 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}/eventsSSE stream of lifecycle events (not JSON — use curl -sN --max-time N)
POST/api/v1/fi/threads/{id}/stopStop the active run

Data apps

Single-file React dashboards. See Data Apps for the lifecycle and manifest, and Data App Embedding for iframe sharing.

MethodPathPurpose
GET/api/v1/data-appsList data apps
GET/api/v1/data-apps?stale=trueList data apps built against an older runtime
POST/api/v1/data-appsUpload an app; body includes the built index.html + manifest
GET/api/v1/data-apps/{slug}App metadata
GET/api/v1/data-apps/{slug}/sourceDownload the app's stored source
POST/api/v1/data-apps/{slug}/queryRun one of the app's named SQL resources
POST/api/v1/data-apps/{slug}/rebuildRebuild an app with stored source against the current runtime
POST/api/v1/admin/data-apps/rebuild-allAdmin-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.

MethodPathPurpose
POST/api/v1/loads/presignGet a presigned upload URL for a file
POST/api/v1/loads/registerRegister an uploaded file as a lakehouse table
POST/api/v1/drive/presignPresigned upload URL for a Drive file
GET/api/v1/drive/filesList Drive files
GET/api/v1/drive/files/{id}/download-urlPresigned 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.

MethodPathPurpose
POST/api/v1/streamIngest rows into a table; body {data, config:{table, mode?, primary_key?, create?}}

Email

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.

MethodPathPurpose
POST/api/v1/email/sendSend an email; body {to:[…], subject, html_body?, text_body?, attachments?}
POST/v3/email/messageSDK-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.

MethodPathPurpose
GET/api/v1/lakehouse/maintenance/statsFile / snapshot statistics
POST/api/v1/lakehouse/maintenance/previewDry-run a maintenance operation
POST/api/v1/lakehouse/maintenance/runsRun an operation and poll it
GET/PUT/api/v1/lakehouse/maintenance/scheduleRead / 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:

MethodPathPurpose
GET/api/v1/events/sourcesList event sources
POST/api/v1/events/sourcesCreate a source (returns its write key once)
POST/api/v1/events/sources/{id}/rotateRotate the write key
DELETE/api/v1/events/sources/{id}Revoke a source

Other surfaces

AreaPrefixNotes
License/api/v1/licenseGET the live entitlement (plan, expiry, features)
Workspace settings/api/v1/workspaceWorkspace info, branding, support access (admin)
Members & roles/api/v1/auth/usersList/manage users and app roles (admin)
Permissions & sharing/api/v1/grants, /api/v1/data-accessContent ACLs and table-level data-access roles
Semantic layer/api/v1/semanticSemantic models and queries
Transformations/api/v1/transformationsSQL transformation models
MCP server/mcpModel 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 definite CLI wraps these endpoints
  • Permissions — app roles, content sharing, table-level data access
  • MCP Server — connect an LLM client over the Model Context Protocol