# REST API

> Create, query and manage DuckHouse databases over HTTP with an API key: run SQL, read schemas and set up Connections.

Source: https://duckhouse.co/docs/api

Everything the dashboard does is available over HTTPS: create and delete databases, run SQL, read schemas, and manage Connections. Requests and responses are JSON.

|  |  |
| --- | --- |
| Base URL | `https://duckhouse.co/api/v1` |
| Authentication | `Authorization: Bearer dhk_…`, an [API key](https://duckhouse.co/docs/tokens.md#api-keys) |
| Content type | `application/json` |

```bash
export DUCKHOUSE_API_KEY=dhk_your_key

curl https://duckhouse.co/api/v1/databases \
  -H "Authorization: Bearer $DUCKHOUSE_API_KEY"
```

## Errors

Failures return a matching HTTP status and a body of the form `{ "error": "what went wrong" }`.

| Status | Meaning |
| --- | --- |
| 400 | The request is malformed, or the SQL failed. The message says which. |
| 401 | The API key is missing, wrong, expired or revoked. |
| 403 | The key is valid but not allowed to do this, for example a write with a read-scoped key. |
| 404 | No such database or Connection in your organization. |
| 409 | The database is not in a state that allows this, for example it is still provisioning. |

## Databases

**`GET /databases`**

List your organization’s databases, newest first.

**`POST /databases`**

Create a database. Requires a full-scope key.

| Field | Type | Default |  |
| --- | --- | --- | --- |
| `name` | string | required | 1 to 50 characters. |
| `region` | string | `iad` | `iad`, `lax`, `fra` or `sin`. DuckLake runs in `iad` only; for DuckDB `iad` is recommended, since the dashboard, API and syncs run there. |
| `vmSize` | string | `shared-cpu-1x` | See [sizes](https://duckhouse.co/docs/databases.md#sizes). |
| `memoryMb` | integer | 1024 | 256 to 32768. |
| `ttlHours` | number | none | Delete the database automatically after this many hours. |
| `autoStop` | boolean | false | Stop when idle. See [stop when idle](https://duckhouse.co/docs/databases.md#stop-when-idle). |

```bash
curl -X POST https://duckhouse.co/api/v1/databases \
  -H "Authorization: Bearer $DUCKHOUSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "q3-analysis", "ttlHours": 72, "autoStop": true }'
```

```json
{
  "database": {
    "id": "cmfk2x9q10001",
    "name": "q3-analysis",
    "engine": "duckdb",
    "engineVersion": "1.5.5",
    "status": "provisioning",
    "region": "iad",
    "vmSize": "shared-cpu-1x",
    "memoryMb": 1024,
    "autoStop": true,
    "expiresAt": "2026-09-23T18:00:00.000Z",
    "connection": { "protocol": "quack", "uri": "quack:duckdb-a1b2c3d4e5.fly.dev:443" }
  },
  "token": "dh_…",
  "connection": {
    "uri": "quack:duckdb-a1b2c3d4e5.fly.dev:443",
    "sql": "ATTACH 'quack:duckdb-a1b2c3d4e5.fly.dev:443' AS q3_analysis (TOKEN 'dh_…');"
  }
}
```

The `token` is returned by this call and never again. A new database starts as `provisioning`; poll `GET /databases/:id` until its `status` is `running`, usually about a minute.

**`GET /databases/:id`**

One database, including its current status and connection address.

**`DELETE /databases/:id`**

Delete a database and everything in it, permanently. Requires a full-scope key.

**`GET /engines`**

The database types that can be created right now.

## Run a query

**`POST /databases/:id/query`**

Run one SQL statement and return its rows.

| Field | Type | Default |  |
| --- | --- | --- | --- |
| `sql` | string | required | One statement, up to 100 KB. |
| `readOnly` | boolean | false | Refuse anything that is not a plain query. Always on for read-scoped keys. |
| `maxRows` | integer | 10000 | Up to 100000. Extra rows are dropped and reported. |

```bash
curl -X POST https://duckhouse.co/api/v1/databases/$DB/query \
  -H "Authorization: Bearer $DUCKHOUSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "sql": "SELECT status, count(*) AS n FROM stripe.invoices GROUP BY status" }'
```

```json
{
  "columns": ["status", "n"],
  "types": ["VARCHAR", "BIGINT"],
  "rows": [
    { "status": "paid", "n": "1284" },
    { "status": "open", "n": "37" }
  ],
  "rowCount": 2,
  "truncated": false
}
```

-   The query runs on the server, so schema names, joins, `UPDATE` and `DELETE` all work as written. None of the [attachment limits](https://duckhouse.co/docs/querying.md) apply here.
-   Large integers, decimals, dates and timestamps are returned as strings, so that no precision is lost in JSON.
-   When `truncated` is true, the result was cut at `maxRows`. Aggregate or filter in SQL rather than paging through a large table; for bulk export, attach with DuckDB and `COPY` to a file.

**`GET /databases/:id/schema`**

Every table and view, with column names, types and nullability. Useful before generating SQL.

## Connections

The same operations as the [Connections](https://duckhouse.co/docs/connections.md) section of the dashboard. Creating, changing and deleting require a full-scope key.

**`GET /connectors`**

The available sources and the fields each one needs.

**`POST /databases/:id/connections/test`**

Check that a source can be reached, and list what could be synced, without saving anything.

**`GET /databases/:id/connections`**

The Connections feeding a database.

**`POST /databases/:id/connections`**

Create a Connection. The source is tested first, and the first sync starts immediately. Returns 409 if the database already has a Connection for a source that allows only one (every source except PostgreSQL, MySQL, Google Sheets and SFTP / FTP).

```bash
curl -X POST https://duckhouse.co/api/v1/databases/$DB/connections \
  -H "Authorization: Bearer $DUCKHOUSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "postgres",
    "name": "Production replica",
    "scheduleMinutes": 60,
    "config":  { "host": "db.example.com", "port": 5432, "database": "shop", "user": "duckhouse_reader" },
    "secrets": { "password": "…" }
  }'
```

**`GET /connections/:id`**

One Connection and the state of its last run. Secrets are never returned.

**`PATCH /connections/:id`**

Rename, pause or resume (status: paused or active), reschedule, or replace the configuration and secrets.

**`GET /connections/:id/streams`**

What the source has now, and the per-table choices this Connection has saved. Change them with PATCH and config.streams, which replaces the saved list: enabled: false turns a table off, and excludeColumns (PostgreSQL and MySQL) lists columns never read.

**`DELETE /connections/:id`**

Stop syncing. Tables already in the database are left in place.

**`POST /connections/:id/sync`**

Start a sync now. Returns immediately; follow progress through the runs.

**`GET /connections/:id/runs`**

Run history, most recent first, with rows synced per table and any error.

### A note on API keys

> API keys themselves are managed in the dashboard, not through the API: a key can never create another key. See [Tokens and API keys](https://duckhouse.co/docs/tokens.md#api-keys).
