---
title: "REST API Reference"
description: "REST endpoints for Datarelix: connections, schema discovery, runs, artifacts, conversations, dashboards, and billing, with verified paths and methods."
canonical: https://docs.datarelix.ai/api/rest/
---

# REST API Reference

Every action in the Datarelix UI is a REST endpoint, and the same endpoints are available programmatically. Base URL: `https://app.datarelix.ai`. Most routes live under `/api/v1`; sign-in routes are at the root (`/auth/...`). An interactive reference (Swagger / ReDoc) and the OpenAPI schema are served by the app itself.

## Authentication

Two credentials are accepted, and either one is enough:

- **A browser session cookie** — set when you sign in. This is what the app itself uses; no key involved.
- **An `X-API-Key` header** — for server-to-server calls.

```bash
curl -H "X-API-Key: $DATARELIX_API_KEY" \
  https://app.datarelix.ai/api/v1/connections
```

API keys are **issued on request, not self-serve** — there is no key-generation screen in the app. [Contact us](https://datarelix.ai/contact/) if you need one. Full details, including what happens when both credentials are present, are in [Authentication](/api/auth/).

## Execution

```
POST   /api/v1/run                  # run a question, returns the finished RunResult
POST   /api/v1/run/stream           # same, as an SSE stream of progress + artifacts
GET    /api/v1/runs                 # ?connection_id= &status= &limit= (max 100) &offset=
GET    /api/v1/runs/{run_id}
```

Note the path is `run` (singular) for execution and `runs` (plural) for history. There is **no** endpoint to cancel a run — a run is cancelled by closing the SSE stream. (The one cancel endpoint in the API belongs to background schema-discovery jobs, below.)

Request body for both execution routes:

| Field | Type | Notes |
| --- | --- | --- |
| `question` | string | Required, 1–2000 characters. |
| `connection_id` | string | Required. |
| `conversation_id` | string | Optional — continues an existing conversation instead of starting one. |
| `chart_requested` | boolean | Optional, default `false`. Set when the user asked for a chart. |
| `selected_tables_override` | string[] | Optional — restrict this run's table scope. |
| `selected_columns_override` | object | Optional — table name → column names, for this run only. |

## Artifacts

```
GET    /api/v1/artifacts/{artifact_id}
GET    /api/v1/artifacts/{artifact_id}/download
GET    /api/v1/artifacts/{artifact_id}/render          # ?format=png|svg — server-side chart render
GET    /api/v1/runs/{run_id}/artifacts/{artifact_id}/csv      # ?rerun=true to bypass the cache
POST   /api/v1/runs/{run_id}/artifacts/{artifact_id}/refresh
```

CSV export and refresh are addressed **through their run** — the run id is part of the path, and it's what ownership is checked against.

## Connections

```
GET    /api/v1/connections                        # ?tag=
POST   /api/v1/connections
GET    /api/v1/connections/{id}
PATCH  /api/v1/connections/{id}
DELETE /api/v1/connections/{id}
POST   /api/v1/connections/{id}/test
POST   /api/v1/connections/{id}/duplicate
POST   /api/v1/connections/{id}/paused            # body: { "paused": true | false }
PUT    /api/v1/connections/{id}/tags
GET    /api/v1/connections/dialects               # supported engines + auth modes + availability
GET    /api/v1/connections/auth-modes             # ?dialect=postgres (required)
```

`GET /api/v1/connections/dialects` is the authoritative list of engines and the auth modes each one supports — read it rather than hard-coding a list.

`POST /api/v1/connections/{id}/paused` is how you free a connection slot without deleting anything; see [Billing & plans](/guides/billing/).

## Schema

```
GET    /api/v1/connections/{id}/schemas           # schemas / datasets / databases in scope
GET    /api/v1/schema/{connection_id}/tables
GET    /api/v1/schema/{connection_id}/tables/{table_name}
GET    /api/v1/connections/{id}/metadata/status   # is the analyzed metadata stale?
PUT    /api/v1/connections/{id}/metadata          # curated table/column descriptions, keys
PUT    /api/v1/connections/{id}/schema-overrides  # key/relationship overrides used when planning
PUT    /api/v1/connections/{id}/schema-selection  # persisted table + column selection
PUT    /api/v1/connections/{id}/selected-tables
```

Table listing lives under `/api/v1/schema/{connection_id}/...`, not under the connection. There is no `/connections/{id}/schema` endpoint.

## Schema discovery

```
POST   /api/v1/connections/{id}/introspect              # structure only, no AI
POST   /api/v1/connections/{id}/discover                # AI schema analysis, blocking
POST   /api/v1/connections/{id}/discover/stream         # AI schema analysis, SSE
POST   /api/v1/connections/{id}/discover/cross-schema   # relationships only, reuses metadata
POST   /api/v1/connections/{id}/discover/jobs           # start (or re-attach to) a background job
GET    /api/v1/connections/{id}/discover/jobs/active
GET    /api/v1/connections/{id}/discover/jobs/{job_id}
GET    /api/v1/connections/{id}/discover/jobs/{job_id}/stream
POST   /api/v1/connections/{id}/discover/jobs/{job_id}/cancel   # 202, best-effort
```

Background jobs are the path the app uses: a job survives navigating away, and the stream can be re-attached. Starting a fresh AI schema analysis consumes one from your monthly allowance; resuming or retrying a job that reuses existing discovery does not.

## Conversations

```
GET    /api/v1/conversations                       # ?connection_id= &limit= (max 100) &offset=
GET    /api/v1/conversations/{id}
GET    /api/v1/conversations/{id}/runs
DELETE /api/v1/conversations/{id}
DELETE /api/v1/conversations/{id}/runs/from/{from_turn_index}
```

The last one is the truncate operation — it's a `DELETE` addressed at a turn index, and it removes that turn and everything after it. This is what "edit an earlier message and re-ask" does under the covers.

## Query templates

```
GET    /api/v1/connections/{id}/templates
POST   /api/v1/connections/{id}/templates
PUT    /api/v1/templates/{template_id}
DELETE /api/v1/templates/{template_id}
```

## Dashboards

```
GET    /api/v1/dashboards
POST   /api/v1/dashboards
GET    /api/v1/dashboards/{id}
PUT    /api/v1/dashboards/{id}
DELETE /api/v1/dashboards/{id}
POST   /api/v1/dashboards/{id}/refresh
POST   /api/v1/dashboards/{id}/items
PUT    /api/v1/dashboards/{id}/items/reorder
PUT    /api/v1/dashboards/{id}/items/{item_id}
DELETE /api/v1/dashboards/{id}/items/{item_id}
POST   /api/v1/dashboards/{id}/items/{item_id}/refresh
```

Updating a dashboard is `PUT`, not `PATCH`. Reordering is `PUT .../items/reorder` — a nested route under `items`, not a top-level `reorder`.

## Chart styling

```
GET    /api/v1/conversations/{id}/style
POST   /api/v1/conversations/{id}/style              # apply a style patch directly, no AI
GET    /api/v1/conversations/{id}/charts/restyle     # ?run_id= &artifact_id=
GET    /api/v1/style/presets
```

## Billing and workspace

```
GET    /api/v1/billing/summary        # plan, status, usage this month, seats
POST   /api/v1/billing/checkout       # returns a Stripe Checkout URL
POST   /api/v1/billing/portal         # returns a Stripe customer portal URL
GET    /api/v1/workspace              # your workspace: roster for owners, summary for members
POST   /api/v1/workspace/invites
DELETE /api/v1/workspace/invites/{invite_id}
POST   /api/v1/workspace/invites/accept
DELETE /api/v1/workspace/members/{member_user_id}
```

Workspace routes only do anything on a Team plan. Plan behaviour is described in [Billing & plans](/guides/billing/); the error codes these return are in [Error codes](/api/errors/).

`POST /api/v1/billing/webhook` also exists — it is Stripe's callback, authenticated by signature, and is not callable by you.

## Account

```
GET    /auth/me
GET    /auth/me/onboarding-status
GET    /auth/providers            # which sign-in methods are enabled
POST   /auth/email/register
POST   /auth/email/login
POST   /auth/email/forgot-password
POST   /auth/email/reset-password
POST   /auth/email/verify
POST   /auth/refresh
POST   /auth/logout
```

These are at the root, not under `/api/v1`. See [Authentication](/api/auth/).

## Streaming a run

`POST /api/v1/run/stream` returns server-sent events. Each event has a name and a JSON payload.

```bash
curl -N -X POST https://app.datarelix.ai/api/v1/run/stream \
  -H "X-API-Key: $DATARELIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "conn_abc",
    "question": "Top 10 customers by revenue last quarter",
    "chart_requested": true
  }'
```

Events you'll see:

| Event | Payload |
| --- | --- |
| `status` | `{ phase: "building_context" \| "planning" \| "executing" }` |
| `step_start` | `{ index, op, ref }` |
| `step_complete` | `{ index, status, elapsed_ms }` |
| `error` | `{ type, message, recoverable }` |
| `complete` | The full run result |

The connection is kept alive with periodic heartbeat comments, so don't treat a quiet stream as a dead one. To continue a conversation, pass `conversation_id` in the request body.

Failures are described by [error codes](/api/errors/).

## Idempotency

Run creation is not idempotent — each call creates a new run. To replay a question, re-issue the same payload; to replay it *in place* within a conversation, truncate from the turn index first (`DELETE /api/v1/conversations/{id}/runs/from/{from_turn_index}`) and then ask again.

## Rate limits and quotas

Two different things throttle you, and they return different bodies:

- **Rate limit** — a fixed per-minute request limit, currently 20 requests per minute, counted per API key (or per signed-in user when you're using a session). Exceeding it returns `429` with a plain-string detail and `Retry-After`, `X-RateLimit-Limit`, and `X-RateLimit-Remaining` headers.
- **Plan quotas** — monthly questions, monthly AI schema analyses, connection limits. These return a structured body with a `code` you can branch on. See [Error codes](/api/errors/).

Branch on the response body's `code`, never on the status alone — `429` is shared by the rate limiter and the quota system.
