> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erdo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# REST API

> Use Erdo's REST API to query, write, and manage your data programmatically

# REST API

Erdo's REST API lets you integrate your data platform into any application. Query datasets, write data, manage conversations, create automations, and more — all via standard HTTP requests.

**Base URL:** `https://api.erdo.ai`

**OpenAPI spec:** the **API Reference** section in the sidebar is generated
from the full machine-readable OpenAPI spec — every `/v1` endpoint with its
request and response schemas. Point a code generator or a coding agent at the
spec to build a typed client.

**Authentication:** All requests require a Bearer token in the `Authorization` header.

```bash theme={null}
curl https://api.erdo.ai/v1/datasets \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Getting an API Key

Click your profile in the bottom-left corner of Erdo and go to **API Keys**. Create a new key and copy the token — it's shown only once.

An API key is an **account-level credential**: it acts as *you*, and works in any organization you're a member of. The organization stored on the key is only its **default org** — the one used when a request doesn't name one. It is not a hard scope.

To act in a specific organization, send the `X-Organization-ID` header with the org's id or slug. The backend validates on every request that you're a member of that org, so a key can never reach an org you don't belong to:

```bash theme={null}
# Uses the key's default org
curl https://api.erdo.ai/v1/datasets \
  -H "Authorization: Bearer YOUR_API_KEY"

# Targets a specific org you belong to
curl https://api.erdo.ai/v1/datasets \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Organization-ID: acme"
```

Manage keys from the [CLI](/cli#api-tokens) too: `erdo token create --name ci`,
`erdo token list`, `erdo token revoke <id>`. Set a command's org with
`erdo --org acme <command>` or change the active default with `erdo org use acme`.

### Project context

Projects are optional work context inside an organization. To run a request in
one, send its UUID as `X-Project-ID` together with the organization header:

```bash theme={null}
curl https://api.erdo.ai/v1/datasets \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-Organization-ID: acme" \
  -H "X-Project-ID: 01234567-89ab-cdef-0123-456789abcdef"
```

The project must belong to the active organization. Erdo rejects an invalid,
inaccessible, or cross-organization project on authenticated API and MCP
requests rather than silently falling back to organization-wide scope.
Project-aware list endpoints such as datasets and conversations narrow to
resources attached to the project; new conversations, datasets, and workflow
outputs are attached when the caller has contributor access. Endpoints that are
explicitly organization-wide remain organization-wide.

List IDs with `GET /v1/projects` (or `erdo project list`) and pass the same
context to the CLI with `erdo --org acme --project <uuid> <command>`.

### Scoped Tokens

For building apps where your end-users interact with Erdo, use [scoped tokens](/mcp/overview#scoped-tokens--external-users) to restrict access to specific datasets and threads.

### API Surface

API keys and scoped tokens work on the documented API surface: every `/v1`
endpoint, the `/mcp` endpoint, and the endpoints used by the published SDKs
(scoped-token minting, thread messaging, agent invocation, dataset reads).
Requests to any other path return `403 permission_denied` — other routes are
internal to the Erdo app and not a stable contract to build against. If you
need a capability that isn't on `/v1` yet, tell us rather than coupling to an
internal route.

***

## Datasets

### List Datasets

```
GET /v1/datasets
```

Returns all datasets in your organization.

| Parameter | Type   | Description                       |
| --------- | ------ | --------------------------------- |
| `limit`   | number | Max results (default 20, max 100) |
| `offset`  | number | Pagination offset                 |

Each thread includes its canonical title, creator ID, creator name and email
when that user is still in the organization, and its creation source. New
threads without an explicit name are titled from their first user message.

```bash theme={null}
curl "https://api.erdo.ai/v1/datasets?limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "datasets": [
    {
      "id": "uuid",
      "slug": "my-org.sales-data",
      "name": "Sales Data",
      "description": "Monthly sales figures",
      "type": "file",
      "status": "active"
    }
  ],
  "total": 42
}
```

### Configure an Integration Dataset

```
POST /v1/integration-datasets/{dataset_id}/configure
```

Update an existing integration dataset using the same generic segments returned by Discover Tables. Setting `enable_sync` materializes sync-capable integrations through Erdo's canonical data platform; it does not create a snapshot dataset.

| Parameter     | Type      | Description                                                |
| ------------- | --------- | ---------------------------------------------------------- |
| `segments`    | string\[] | Segment names or ids to include                            |
| `enable_sync` | boolean   | Enable canonical data-platform sync after saving the scope |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/integration-datasets/DATASET_ID/configure" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"segments": ["ACCOUNT_OR_SCHEMA_ID"], "enable_sync": true}'
```

### Create Dataset

```
POST /v1/datasets-create
```

Create a new empty dataset. Uses your organization's default storage backend. After creation, use [Write Rows](#write-rows) to add data.

| Parameter      | Type   | Description                                                |
| -------------- | ------ | ---------------------------------------------------------- |
| `name`         | string | Name for the dataset                                       |
| `description`  | string | Optional description                                       |
| `instructions` | string | Optional instructions for AI agents analyzing this dataset |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/datasets-create" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Daily Metrics", "description": "Automated metrics from our monitoring pipeline"}'
```

```json theme={null}
{
  "id": "uuid",
  "slug": "my-org.daily-metrics",
  "name": "Daily Metrics",
  "description": "Automated metrics from our monitoring pipeline",
  "type": "file",
  "status": "active"
}
```

### Upload a File as a Dataset

```
POST /v1/datasets-upload
```

Upload a file and create a dataset from it in one call. The file's schema is
extracted before the response returns, so the dataset is immediately queryable.
Prefer this over Create Dataset + Write Rows when your data already exists as a
file.

| Parameter        | Type   | Description                                                                                                                       |
| ---------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `filename`       | string | Filename with extension — drives type detection (`.csv`, `.tsv`, `.xlsx`, `.json`, `.jsonl`, `.pdf`, `.docx`, `.txt`, `.md`, ...) |
| `content_base64` | string | The raw file bytes, base64-encoded (max 20 MB decoded; use the web app's resumable upload for larger files)                       |
| `name`           | string | Optional display name; defaults to the filename                                                                                   |
| `description`    | string | Optional description shown to AI agents analyzing the dataset                                                                     |

```bash theme={null}
# base64 -w0 keeps the output on one line (GNU coreutils wraps at 76 cols by
# default, which would break the JSON string); on macOS `base64 -i leads.csv`.
curl -X POST "https://api.erdo.ai/v1/datasets-upload" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"leads.csv\", \"name\": \"Leads\", \"content_base64\": \"$(base64 -w0 leads.csv)\"}"
```

`ready` reports whether schema extraction succeeded; when `false`, the file
stored but is not yet queryable (a `warning` explains why).

```json theme={null}
{
  "dataset_id": "uuid",
  "slug": "my-org.leads",
  "name": "Leads",
  "ready": true
}
```

### Delete Dataset

```
DELETE /v1/datasets/:slug
```

Permanently delete a dataset and all its data. Requires admin permission on the dataset.

```bash theme={null}
curl -X DELETE "https://api.erdo.ai/v1/datasets/my-org.daily-metrics" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Search Datasets

```
GET /v1/datasets-search
```

Search datasets by name.

| Parameter | Type   | Description |
| --------- | ------ | ----------- |
| `query`   | string | Search text |

```bash theme={null}
curl "https://api.erdo.ai/v1/datasets-search?query=revenue" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Get Dataset Schema

```
GET /v1/datasets/:id/schema
```

Get detailed schema for a dataset including column names, types, statistics, and sample data. Call this before writing data to understand the column structure.

```bash theme={null}
curl "https://api.erdo.ai/v1/datasets/my-dataset-uuid/schema" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### Query Dataset (SQL)

```
POST /v1/datasets/:slug/query
```

Run a SQL query against a dataset. The SQL dialect depends on the storage backend (PostgreSQL, ClickHouse, or DuckDB for file datasets).

| Parameter      | Type   | Description                                                                       |
| -------------- | ------ | --------------------------------------------------------------------------------- |
| `query`        | string | SQL query to execute                                                              |
| `resource_key` | string | Resource key from the dataset schema; selects a table in a multi-resource dataset |
| `limit`        | number | Max rows (default 100)                                                            |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/datasets/my-org.sales-data/query" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"resource_key": "orders", "query": "SELECT * FROM orders WHERE revenue > 10000 ORDER BY date DESC", "limit": 50}'
```

```json theme={null}
{
  "columns": ["date", "revenue", "orders"],
  "rows": [["2025-03-15", "42300", "156"], ["2025-03-14", "38900", "142"]],
  "row_count": 2
}
```

<Note>
  For file datasets (CSV/Excel), the table is always named `data` regardless of
  the resource key. For database, warehouse, and synchronized API integration
  datasets, use the actual resource or table name from the schema — passing
  `resource_key` selects which resource to query, and that resource's name is the
  SQL table name.
</Note>

### Query Dataset (Natural Language)

```
POST /v1/datasets/:slug/query-nl
```

Query a dataset using natural language. Erdo generates and executes the correct SQL for you.

| Parameter  | Type   | Description               |
| ---------- | ------ | ------------------------- |
| `question` | string | Natural language question |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/datasets/my-org.sales-data/query-nl" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"question": "What were the top 5 products by revenue last month?"}'
```

The response carries both the SQL Erdo wrote and what it returned:

```json theme={null}
{
  "sql": "SELECT product, SUM(amount) AS revenue FROM data GROUP BY 1 ORDER BY 2 DESC LIMIT 5",
  "columns": ["product", "revenue"],
  "rows": [["Widget", "48210"], ["Gizmo", "31775"]],
  "row_count": 2,
  "output": "Queried my-org.sales-data — 2 rows returned. ...",
  "success": true
}
```

`rows` holds the values in `columns` order — the same tabular shape
`/v1/datasets/:slug/fetch` returns, so both dataset reads are consumed the same
way — and `output` is that result rendered to read. `row_count` is how many rows
the query matched and can exceed the number of rows returned, which is capped at
1000; compare the two to tell whether you have the whole result. When the
dataset's saved filters shaped the read, `applied_filters` names them, so a count
is never reported as the whole truth when it excludes something. On failure,
`success` is `false` and `error` says why.

### Fetch Dataset Contents

```
POST /v1/datasets/:slug/fetch
```

Fetch raw contents of a dataset without writing SQL.

| Parameter   | Type   | Description             |
| ----------- | ------ | ----------------------- |
| `limit`     | number | Max rows (default 1000) |
| `sql_query` | string | Optional SQL filter     |

### Get Dataset Context

```
GET /v1/dataset-context
```

Get detailed context for multiple datasets at once — schemas, column types, statistics, and sample data.

| Parameter       | Type      | Description                          |
| --------------- | --------- | ------------------------------------ |
| `dataset_slugs` | string\[] | Specific dataset slugs (empty = all) |
| `limit`         | number    | Max datasets (default 10)            |

***

## Writing Data

Write data into your datasets from any application. Rows are written to whatever storage backend the dataset uses (Postgres, ClickHouse, or CSV file storage).

### Write Rows

```
POST /v1/datasets/:slug/rows
```

Write or upsert rows to a dataset.

| Parameter    | Type      | Description                                                 |
| ------------ | --------- | ----------------------------------------------------------- |
| `rows`       | object\[] | Array of row objects with column names as keys              |
| `key_column` | string    | Optional. Column to upsert on (update existing, insert new) |

**Append rows (no key column):**

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/datasets/my-org.metrics/rows" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {"date": "2025-03-17", "revenue": 42300, "orders": 156},
      {"date": "2025-03-18", "revenue": 45100, "orders": 163}
    ]
  }'
```

**Upsert rows (with key column):**

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/datasets/my-org.metrics/rows" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {"date": "2025-03-17", "revenue": 43500, "orders": 160}
    ],
    "key_column": "date"
  }'
```

```json theme={null}
{
  "rows_affected": 2
}
```

**Storage-specific behavior:**

| Storage        | Append        | Upsert (`key_column`)                     | Notes                                    |
| -------------- | ------------- | ----------------------------------------- | ---------------------------------------- |
| **Postgres**   | Insert rows   | `ON CONFLICT DO UPDATE`                   | Key column must have a unique constraint |
| **ClickHouse** | Batch insert  | Insert (use ReplacingMergeTree for dedup) |                                          |
| **CSV/File**   | Append to CSV | Not supported (always appends)            | New columns are added automatically      |

### Delete Rows

```
DELETE /v1/datasets/:slug/rows
```

Delete rows from a dataset. Works for both file (CSV) and database-backed datasets.

| Parameter    | Type      | Description                                |
| ------------ | --------- | ------------------------------------------ |
| `key_column` | string    | Column to match against                    |
| `keys`       | string\[] | Values to delete. Empty = delete all rows. |

```bash theme={null}
curl -X DELETE "https://api.erdo.ai/v1/datasets/my-org.metrics/rows" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key_column": "date", "keys": ["2025-03-17"]}'
```

```json theme={null}
{
  "rows_affected": 1
}
```

### Update Schema

```
POST /v1/datasets/:slug/schema
```

Update a dataset's schema: add, remove, rename columns, or change column types. Operations are applied atomically — if any fails, none are applied. Supported for CSV file datasets only. After changes, column analysis is automatically refreshed.

| Parameter    | Type      | Description                |
| ------------ | --------- | -------------------------- |
| `operations` | object\[] | Array of schema operations |

Each operation object:

| Field         | Type   | Description                                                                                         |
| ------------- | ------ | --------------------------------------------------------------------------------------------------- |
| `type`        | string | `add_column`, `remove_column`, `rename_column`, or `alter_column_type`                              |
| `column`      | string | Target column name                                                                                  |
| `new_name`    | string | New name (for `rename_column` only)                                                                 |
| `column_type` | string | Type hint: `text`, `integer`, `float`, `date`, `boolean` (for `add_column` and `alter_column_type`) |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/datasets/my-org.metrics/schema" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "operations": [
      {"type": "add_column", "column": "region", "column_type": "text"},
      {"type": "rename_column", "column": "rev", "new_name": "revenue"},
      {"type": "alter_column_type", "column": "revenue", "column_type": "float"},
      {"type": "remove_column", "column": "temp_notes"}
    ]
  }'
```

```json theme={null}
{
  "columns_added": 1,
  "columns_removed": 1,
  "columns_renamed": 1,
  "columns_retyped": 1,
  "current_columns": ["date", "revenue", "region"]
}
```

***

## Ask Questions

### Ask a Data Question

```
POST /v1/ask
```

Ask a natural language question about your data. Invokes an AI agent that analyzes datasets, writes code, and returns a text answer.

<Warning>
  Can take 30 seconds to 2 minutes for complex questions.
</Warning>

| Parameter       | Type      | Description                           |
| --------------- | --------- | ------------------------------------- |
| `question`      | string    | The data question                     |
| `dataset_slugs` | string\[] | Optional. Scope to specific datasets. |
| `timezone`      | string    | Optional. e.g. `America/New_York`     |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/ask" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"question": "What were total sales last quarter?", "dataset_slugs": ["my-org.sales-data"]}'
```

```json theme={null}
{
  "thread_id": "uuid",
  "status": "success",
  "answer": "Total sales last quarter were $1.2M, up 15% from the previous quarter..."
}
```

***

## Integrations

Connect third-party apps and data sources so Erdo can use them — in agent runs, data questions, and datasets. Credential-based integrations (databases, API keys, service accounts) connect in a single call. OAuth-based integrations return a `connect_url` for the user to authorize in a browser; polling the status endpoint completes the connection.

### List Integrations

```
GET /v1/integrations
```

Returns the integrations connected in your organization.

```bash theme={null}
curl "https://api.erdo.ai/v1/integrations" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "integrations": [
    {
      "id": "uuid",
      "app": "postgres",
      "name": "Production DB",
      "status": "active",
      "auth_type": "database",
      "type": "database"
    }
  ]
}
```

### Search Connectable Apps

```
GET /v1/integration-apps?query=slack
```

Search apps that can be connected: native integrations (databases, warehouses, APIs) and thousands of SaaS apps. The `auth_types` field tells you how connection works — `database`, `api_key`, `service_account`, and `basic` connect directly with credentials, while OAuth-based apps go through a browser authorization step.

| Parameter | Type   | Description                                                              |
| --------- | ------ | ------------------------------------------------------------------------ |
| `query`   | string | Optional search term. Empty lists native integrations plus popular apps. |

```json theme={null}
{
  "apps": [
    { "app": "slack", "name": "Slack", "auth_types": ["oauth"], "source": "pipedream" },
    { "app": "postgres", "name": "PostgreSQL", "auth_types": ["database"], "source": "native" }
  ]
}
```

### Connect an Integration

```
POST /v1/integrations-connect
```

| Parameter     | Type   | Description                                                                                                                                                                                                                                                                                                  |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `app`         | string | App identifier from [Search Connectable Apps](#search-connectable-apps)                                                                                                                                                                                                                                      |
| `name`        | string | Optional display name for the connection                                                                                                                                                                                                                                                                     |
| `credentials` | object | Credentials for credential-based apps. Omit for OAuth apps.                                                                                                                                                                                                                                                  |
| `scopes`      | array  | Optional scopes to grant; defaults to all the app supports                                                                                                                                                                                                                                                   |
| `return_url`  | string | Optional absolute `http(s)` URL to send the user's browser to after they authorize an OAuth app at `connect_url`. Defaults to Erdo's data page; pass your own page when you embed the connect flow in your product so the user lands back on it. Ignored for credential-based apps that connect in one call. |

Connecting a database — created, verified, and activated in one call:

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/integrations-connect" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app": "postgres",
    "credentials": {
      "host": "db.example.com", "port": "5432",
      "database": "analytics", "username": "readonly", "password": "..."
    }
  }'
```

```json theme={null}
{ "app": "postgres", "integration_id": "uuid", "status": "active" }
```

Connecting an OAuth app — the response carries a `connect_url` to open in a browser. Pass `return_url` when you embed the flow in your own product so the user's browser lands back on your page (not Erdo's) after authorizing:

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/integrations-connect" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"app": "slack", "return_url": "https://portal.example.com/integrations/done"}'
```

```json theme={null}
{
  "app": "slack",
  "status": "pending",
  "connect_url": "https://...",
  "next_step": "Have the user open connect_url in a browser and authorize slack, then check the connection status for app=\"slack\" to confirm."
}
```

If verification fails for a credential-based app (wrong password, unreachable host), the call returns an error and nothing is left behind — fix the credentials and retry.

### Check Connection Status

```
GET /v1/integrations-connect/:app
```

Reports whether an app is connected. For browser-authorized apps this also completes any connection the user finished since the connect call — poll it after the user opens the `connect_url`.

```bash theme={null}
curl "https://api.erdo.ai/v1/integrations-connect/slack" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "app": "slack",
  "connected": true,
  "integrations": [
    { "id": "uuid", "app": "slack", "name": "Slack (team@acme.com)", "status": "connected" }
  ],
  "note": "1 new connection(s) confirmed."
}
```

### Discover Tables

```
GET /v1/integrations/:integration/tables
```

Discover what a connected database integration exposes. `:integration` is the app key (e.g. `postgres`) or, when several instances of the same app are connected, the integration id. Without `schema_name` it lists the selectable schemas (all SQL databases and warehouses). With `schema_name` it lists that schema's tables with columns, types, and row estimates — supported for SQL databases (Postgres, MySQL, and compatible); warehouses (BigQuery, Snowflake, ClickHouse) list schemas but not per-table columns here.

| Parameter     | Type   | Description                          |
| ------------- | ------ | ------------------------------------ |
| `schema_name` | string | Optional. Schema to list tables for. |

```bash theme={null}
curl "https://api.erdo.ai/v1/integrations/postgres/tables?schema_name=public" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "integration_id": "uuid",
  "app": "postgres",
  "schemas": [{ "id": "public", "name": "public", "type": "schema" }],
  "tables": [
    {
      "schema_name": "public",
      "table_name": "orders",
      "columns": [{ "name": "id", "data_type": "uuid", "is_nullable": false, "is_primary_key": true }],
      "estimated_row_count": 12840
    }
  ]
}
```

### Create a Dataset from an Integration

```
POST /v1/integration-datasets
```

Create a dataset backed by any connected integration that exposes queryable data. Database and warehouse integrations can query live against the source; sync-capable API integrations automatically materialize their provider resources through Erdo's data platform. Pick selectable scopes with Discover Tables first when the integration exposes them (some allow only one). Integrations with neither a direct query path nor dataset sync are rejected. Resource and column discovery runs in the background and the dataset schema appears after the first discovery/sync, after which [Query Dataset](#query-dataset-sql) works against the returned slug.

| Parameter     | Type      | Description                                                       |
| ------------- | --------- | ----------------------------------------------------------------- |
| `integration` | string    | App key (e.g. `postgres`) or integration id                       |
| `name`        | string    | Display name for the dataset                                      |
| `description` | string    | Optional description shown to AI agents                           |
| `schemas`     | string\[] | Selectable schema names (or ids) to include, from Discover Tables |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/integration-datasets" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"integration": "postgres", "name": "Production DB", "schemas": ["public"]}'
```

```json theme={null}
{
  "dataset_id": "uuid",
  "slug": "my-org.production-db",
  "name": "Production DB",
  "status": "active"
}
```

***

## Threads & Conversations

### List Threads

```
GET /v1/threads
```

| Parameter | Type   | Description              |
| --------- | ------ | ------------------------ |
| `limit`   | number | Max results (default 20) |
| `offset`  | number | Pagination offset        |

### Get Thread Messages

```
GET /v1/threads/:id/messages
```

Get all messages from a conversation thread. User-authored messages include the
author's canonical ID, name, and email when that user is still in the organization.
Each content item includes its persisted `content_type` and, when present,
`ui_content_type`. Use `ui_content_type` to select the presentation component for
structured content such as tool results and generated UI. UI-generation items
also include `created_by_invocation_id` when the renderer needs the originating
invocation to load referenced data.

### Create Thread

```
POST /v1/threads-create
```

| Parameter     | Type      | Description                      |
| ------------- | --------- | -------------------------------- |
| `name`        | string    | Optional thread name             |
| `dataset_ids` | string\[] | Optional dataset UUIDs to attach |

### Send Message

```
POST /v1/threads/:id/send
```

Send a message to a thread and get an AI-generated response. The optional
`context` is available to the agent for this turn without becoming part of the
visible user message in the thread. This lets an application supply the current
page, selection, or other structured state while preserving an honest transcript.
The persisted message is attributed to the authenticated user, including when
the request comes through an application using the REST or MCP surface.

<Warning>
  Can take 30 seconds to 2 minutes.
</Warning>

| Parameter   | Type    | Description                                                                               |
| ----------- | ------- | ----------------------------------------------------------------------------------------- |
| `message`   | string  | The message                                                                               |
| `context`   | string  | Optional application context for this turn. It is not stored as the visible user message. |
| `agent_key` | string  | Optional. Default: `erdo.data-question-answerer`                                          |
| `timezone`  | string  | Optional timezone                                                                         |
| `async`     | boolean | Optional. Start the run and return immediately.                                           |

***

## Memories & Skills

Memories store reusable knowledge and instructions that Erdo's AI uses in future conversations.

### Create Memory

```
POST /v1/memories
```

| Parameter     | Type      | Description                                     |
| ------------- | --------- | ----------------------------------------------- |
| `title`       | string    | Short title                                     |
| `content`     | string    | The content or instructions                     |
| `description` | string    | Brief description                               |
| `type`        | string    | `snippet` (knowledge) or `skill` (instructions) |
| `category`    | string    | Optional category                               |
| `tags`        | string\[] | Optional tags                                   |
| `dataset_ids` | string\[] | Optional associated dataset UUIDs               |

### Search Memories

```
GET /v1/memories-search
```

| Parameter | Type   | Description              |
| --------- | ------ | ------------------------ |
| `query`   | string | Search text              |
| `limit`   | number | Max results (default 10) |

### List Memories

```
GET /v1/memories
```

| Parameter  | Type   | Description                           |
| ---------- | ------ | ------------------------------------- |
| `type`     | string | Optional filter: `snippet` or `skill` |
| `category` | string | Optional category filter              |
| `limit`    | number | Max results (default 20)              |
| `offset`   | number | Pagination offset                     |

### Delete Memory

```
DELETE /v1/memories/:id
```

Soft delete — can be recovered.

***

## Artifacts

Artifacts are AI-generated outputs from agent runs — insights, metrics, alerts, and tables.

### List Artifacts

```
GET /v1/artifacts
```

| Parameter | Type   | Description                                                            |
| --------- | ------ | ---------------------------------------------------------------------- |
| `type`    | string | Optional: `insight`, `chart`, `metric`, `alert`, `table`, `suggestion` |
| `limit`   | number | Max results (default 20)                                               |
| `offset`  | number | Pagination offset                                                      |

### Get Artifact

```
GET /v1/artifacts/:id
```

***

## Screenshots

Capture a **public** web page to a PNG and get a signed, time-limited download URL.
Only works on URLs reachable without login (a marketing site, a published Erdo
page at `https://pages.erdo.ai/p/{id}`, etc.).

```
POST /v1/screenshot
```

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/screenshot" \
  -H "Authorization: Bearer $ERDO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://pages.erdo.ai/p/abc123", "full_page": true}'
```

Response: `signed_url`, `bucket_key`, `media_type`, `width`, `height`, `expires_at`.

***

## Pages

Deploy authenticated, data-wired HTML apps to Erdo over HTTP — the same surface as the `erdo_deploy_page` MCP tools, for CI and scripts. See the [Build Apps on Erdo](/apps/build-apps) guide for the page runtime, the `window.erdo` client, and the read/write model.

### Deploy Page

```
POST /v1/pages
```

| Parameter       | Type      | Description                                                                                                             |
| --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| `title`         | string    | Page title shown in Erdo                                                                                                |
| `html`          | string    | Full document or fragment. With the `react-tailwind` runtime, include a root element and put React code in `js`         |
| `css`           | string    | Optional. Stylesheet                                                                                                    |
| `js`            | string    | Optional. JavaScript/JSX; `window.erdo` is available                                                                    |
| `runtime`       | string    | Optional. `react-tailwind` (default) or `none`                                                                          |
| `dataset_slugs` | string\[] | Optional. Datasets the page reads — granted read access (you must be able to view them); unknown slugs are a hard error |
| `public`        | boolean   | Optional. `true` makes the page publicly viewable (view-only). Default `false`                                          |

Returns the page `id`, editor `url`, optional `public_url`, and structured `validation` results. A deploy with validation errors still saves — fix them and iterate.

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/pages" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Sales Dashboard",
    "html": "<div id=\"root\"></div>",
    "js": "const {rows} = await erdo.queryDataset(\"acme.sales\", {limit: 1000}); /* render */",
    "dataset_slugs": ["acme.sales"],
    "public": false
  }'
```

### Update Page

```
PUT /v1/pages/:pageID
```

Provided fields are **merged** — send only `js` to fix a script without resending `html`/`css`. Omit any field to keep its current value. `dataset_slugs` replaces the current grants; `public` toggles visibility. Returns fresh `validation` results.

### Validate Page

```
POST /v1/pages/validate
```

Dry-run validation without deploying: HTML structure, JS/JSX syntax and runtime smoke checks, `window.erdo` usage, and dataset-slug references. Accepts `html` (required), `css`, `js`, `runtime`, and `dataset_slugs`.

***

## Automations (Heartbeats)

Heartbeats are recurring agents that analyze your data on a schedule.

### List Heartbeats

```
GET /v1/heartbeats
```

| Parameter | Type   | Description              |
| --------- | ------ | ------------------------ |
| `limit`   | number | Max results (default 20) |
| `offset`  | number | Pagination offset        |

### Create Heartbeat

```
POST /v1/heartbeats
```

| Parameter             | Type      | Description                       |
| --------------------- | --------- | --------------------------------- |
| `name`                | string    | Name                              |
| `instructions`        | string    | What to do on each run            |
| `interval_minutes`    | number    | Run frequency (min 5)             |
| `description`         | string    | Optional description              |
| `timezone`            | string    | Optional (default UTC)            |
| `active_window_start` | string    | Optional. e.g. `09:00`            |
| `active_window_end`   | string    | Optional. e.g. `18:00`            |
| `active_days`         | number\[] | Optional. 0=Sun..6=Sat            |
| `dataset_ids`         | string\[] | Optional dataset UUIDs            |
| `effort`              | string    | Optional: `low`, `medium`, `high` |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/heartbeats" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Revenue monitor",
    "instructions": "Check daily revenue for anomalies and alert if down >10% vs last week",
    "interval_minutes": 60,
    "dataset_ids": ["dataset-uuid"],
    "active_window_start": "09:00",
    "active_window_end": "18:00",
    "timezone": "America/New_York"
  }'
```

### Run Heartbeat

```
POST /v1/heartbeats/:id/run
```

Trigger a heartbeat immediately, outside its schedule.

### Enable or Disable Heartbeat

```
POST /v1/heartbeats/:id/state
```

Pause a misbehaving automation so it stops running, or resume a paused one.

| Parameter | Type   | Description            |
| --------- | ------ | ---------------------- |
| `state`   | string | `active` or `disabled` |

```bash theme={null}
curl -X POST "https://api.erdo.ai/v1/heartbeats/HEARTBEAT_ID/state" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state": "disabled"}'
```

### List Heartbeat Executions

```
GET /v1/heartbeats/:id/executions
```

| Parameter | Type   | Description              |
| --------- | ------ | ------------------------ |
| `limit`   | number | Max results (default 10) |

***

## Rendering

### Render Chart

```
POST /v1/render/chart
```

Render a data visualization. Supports bar, line, pie, histogram, and scatter charts. See [MCP docs](/mcp/overview#erdo_render_chart) for full schema.

### Render Table

```
POST /v1/render/table
```

Render a data table. See [MCP docs](/mcp/overview#erdo_render_table) for full schema.
