> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Admin API

The Admin API is how you manage Amp. Through it, you register and deploy [datasets](/get-started/concepts/datasets),
follow the jobs that extract and transform their data, register [providers](/providers), and check on workers. It's an
HTTP API that takes and returns JSON, served by the [controller](/running-amp/controller), the service that schedules
work.

You rarely call it by hand: [`ampctl`](/clis/ampctl) is its command-line client, and the
[TypeScript](/sdks/typescript/admin-api) and [Python](/sdks/python/admin) SDKs have clients for it. The pages in this
section show both `ampctl` and the API for each task. This page covers what every endpoint has in common.

## Prerequisites

* A running controller: `ampd solo` on your machine (see [Solo Mode](/running-amp/solo-mode)), or `ampd controller`
  in a deployment (see [Controller](/running-amp/controller)).
* `curl` and [`jq`](https://jqlang.org), for the examples on this page.

## Address

The controller serves the Admin API on port `1610` of every network interface, so on your machine it's
`http://localhost:1610`. To change it, set
[`controller.admin_api_addr`](/running-amp/configuration-reference#controlleradmin_api_addr) in `ampd`'s configuration
file.

Paths in this reference are relative to that address. For example, listing workers on a fresh solo install, which runs
one worker called `worker`:

:::terminal
```bash
curl http://localhost:1610/workers
```

```json
{ "workers": [{ "node_id": "worker", "heartbeat_at": "2026-10-30T16:20:15.123456+00:00" }] }
```
:::

`ampctl` uses `http://localhost:1610` too. To point it elsewhere, pass `--admin-url` or set `AMP_ADMIN_URL`:

```bash
export AMP_ADMIN_URL=https://amp-admin.example.com
ampctl worker list
```

## Requests and responses

* **Bodies are JSON.** Send `Content-Type: application/json` with a request body.
* **Datasets are named by namespace, name, and revision** in paths:
  `/datasets/acme/eth_mainnet/versions/1.0.0` is the dataset `acme/eth_mainnet@1.0.0`. A revision is a version like
  `1.0.0`, `latest` (the highest version), `dev` (the last manifest registered, tagged or not), or a manifest's
  64-character hash. See [Versioning](/datasets/versioning).
* **Block numbers that end a range are decimal strings**, like `"end_block": "20000000"`, so that no JSON parser rounds
  them. Block numbers in responses are integers, except in a job's descriptor, which repeats the request's fields as
  they came, like `"end_block": "24000099"` in `GET /jobs`.
* **Timestamps are RFC 3339 strings**, like `2026-10-30T16:20:15.123456+00:00`. Amp's own are in UTC; a build's
  `commit_timestamp` keeps the commit's offset.
* **Repeating a create is often safe.** Deploying a dataset version that's already materializing returns the existing
  materialization, and creating a job that's already active, with the same options, returns that job. Each endpoint's
  reference says how it behaves.

## Errors

A request that fails returns a `4xx` or `5xx` status and, apart from
[a few failures](/managing-amp/admin-api#failures-that-arent-json), a body with two fields:

* `error_code`: what went wrong, like `DATASET_NOT_FOUND`. Codes don't change between releases, so branch on them in
  your code.
* `error_message`: a description for people. Messages can change, so only display or log them.

:::terminal
```bash
curl -i http://localhost:1610/datasets/acme/eth_mainnet/versions/9.9.9
```

```text
HTTP/1.1 404 Not Found
content-type: application/json

{"error_code":"DATASET_NOT_FOUND","error_message":"Dataset 'acme/eth_mainnet' at revision '9.9.9' not found"}
```
:::

The statuses mean the same thing on every endpoint:

| Status                      | Meaning                                                                                                                                                                           |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`           | The path, query, or body is malformed: fix the request before retrying. Or no worker can take a job (`NO_WORKERS_AVAILABLE`, `WORKER_NOT_AVAILABLE`): start one, or pick another. |
| `404 Not Found`             | The resource doesn't exist, or it's in a namespace you can't access (see [Access](/managing-amp/admin-api#access)).                                                                                      |
| `409 Conflict`              | The request conflicts with the current state, like deleting something still in use.                                                                                               |
| `412 Precondition Failed`   | A config map changed since you read it. Read it again, then retry.                                                                                                                |
| `413 Payload Too Large`     | A config map document is over 1 MiB.                                                                                                                                              |
| `500 Internal Server Error` | Amp failed to complete the request, often because it couldn't reach the metadata database or storage.                                                                             |

Each endpoint's reference lists its statuses and error codes.

### Failures that aren't JSON

These failures have no `error_code`, so check a response's `content-type` before you parse it:

* **Lineage's query parameters:**
  [`GET /datasets/{namespace}/{name}/versions/{revision}/lineage`](/managing-amp/admin-api/datasets#get-a-datasets-lineage)
  answers a query parameter it can't read, like `direction=sideways`, with a `400` in plain text.
* **Revisions' request bodies:** [`POST /revisions`](/managing-amp/admin-api/revisions#register-a-revision),
  [`POST /revisions/deactivate`](/managing-amp/admin-api/revisions#deactivate-a-table), and
  [`POST /revisions/{id}/activate`](/managing-amp/admin-api/revisions#activate-a-revision) answer a body they can't read
  in plain text: `400` if it isn't JSON, `413` if it's over 2 MiB, `415` without `Content-Type: application/json`, and
  `422` if a field is missing or isn't valid.
* **A malformed `amp-capabilities` header:** any request gets a `400` with an empty body, before an endpoint runs.
* **Requests no endpoint takes:** a path the API doesn't have gets a `404` with an empty body, and a method the path
  doesn't take gets a `405` with an empty body.

:::terminal
```bash
curl -i "http://localhost:1610/datasets/acme/eth_mainnet/versions/1.0.0/lineage?direction=sideways"
```

```text
HTTP/1.1 400 Bad Request
content-type: text/plain; charset=utf-8

Failed to deserialize query string: direction: unknown variant `sideways`, expected one of `upstream`, `downstream`, `both`
```
:::

## Pagination

Most lists come back whole. These come back in pages:

| Endpoint                                        | Page size                            | Next page                                           |
| ----------------------------------------------- | ------------------------------------ | --------------------------------------------------- |
| `GET /jobs`                                     | `limit`, 50 by default, at most 1000 | Pass the response's `next_cursor` as `last_job_id`. |
| `GET /materialized-datasets`                    | `limit`, 50 by default, at most 1000 | Pass the response's `next_cursor` as `last_id`.     |
| `GET /materialized-datasets/{namespace}/{name}` | `limit`, 50 by default, at most 100  | Pass the response's `next_cursor` as `last_id`.     |
| `GET /revisions`                                | `limit`, 100 by default              | Pass the last revision's `id` as `last_id`.         |

A page with fewer items than `limit` is the last one. For example, jobs come newest first, and each page's
`next_cursor` is the ID of its last job. To read them three at a time:

:::terminal
```bash
curl -s 'http://localhost:1610/jobs?limit=3&status=all' | jq -c '{ids: [.jobs[].id], next_cursor}'
```

```text
{"ids":[512,511,510],"next_cursor":510}
```
:::

:::terminal
```bash
curl -s 'http://localhost:1610/jobs?limit=3&status=all&last_job_id=510' | jq -c '{ids: [.jobs[].id], next_cursor}'
```

```text
{"ids":[509,508,507],"next_cursor":507}
```
:::

## Access

The Admin API doesn't authenticate callers or encrypt traffic. Anyone who can reach port `1610` can manage your Amp
deployment. Keep it on a private network, or put it behind a gateway that authenticates callers and terminates TLS.
`ampctl` sends a bearer token to such a gateway when you pass `--auth-token` or set `AMP_AUTH_TOKEN`.

### Namespaces

A gateway can also limit what each caller reaches, by namespace (the part of a dataset's name before the `/`, like
`acme`). It passes the caller's grants to Amp in the `amp-capabilities` header, as JSON:

```json
{
  "capabilities": [
    { "name": "namespace.read", "scope": ["acme"] },
    { "name": "namespace.write", "scope": ["acme"] }
  ]
}
```

* `namespace.read` lets the caller see what's in the listed namespaces: datasets, their materializations, manifests,
  and config maps.
* `namespace.write` lets the caller change them: register, deploy, stop, and delete.

With the header, a namespace the caller isn't granted answers `404 Not Found`, as if it didn't exist, so callers can't
discover other tenants. Lists only include what the caller can read, and an empty `capabilities` array grants nothing.
Deploying a [derived dataset](/datasets/derived-datasets) also needs `namespace.read` on every namespace it reads from.

Without the header, a request is unrestricted. So the gateway must set the header itself, replacing any that a caller
sends, or callers could grant themselves any namespace.

Only the datasets, materialized datasets, config maps, and schema endpoints check namespaces, and of the manifest
endpoints, listing and registering. The others (the two dataset restore endpoints, the rest of the manifest endpoints,
jobs, workers, providers, revisions, and files) don't, so only let operators reach them.

## Endpoints

| Endpoints                                                              | What they're for                                                                    |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| [Datasets](/managing-amp/admin-api/datasets)                           | Register datasets, list their versions, read manifests and lineage, restore, remove |
| [Materialized datasets](/managing-amp/admin-api/materialized-datasets) | Deploy dataset versions, follow their progress, stop them                           |
| [Manifests](/managing-amp/admin-api/manifests)                         | Store, list, and prune manifests by their hash                                      |
| [Providers](/managing-amp/admin-api/providers)                         | Register, list, and remove providers                                                |
| [Jobs](/managing-amp/admin-api/jobs)                                   | Create, follow, stop, and remove jobs                                               |
| [Workers](/managing-amp/admin-api/workers)                             | List workers and check their heartbeats                                             |
| [Revisions](/managing-amp/admin-api/revisions)                         | Manage the physical revisions of tables (In development)                        |
| [Config maps](/managing-amp/admin-api/config-maps)                     | Store configuration documents by namespace and name (Unstable)               |
| [Files](/managing-amp/admin-api/files)                                 | Read the metadata of a table's files                                                |
| [Schema](/managing-amp/admin-api/schema)                               | Check a derived dataset's SQL and infer its tables' schemas                         |

## OpenAPI specification

The reference is generated from the Admin API's OpenAPI 3.1 specification, in Amp's repository at
[`docs/schemas/openapi/admin.spec.json`](https://github.com/edgeandnode/amp/blob/v0.0.42/docs/schemas/openapi/admin.spec.json).
To call the API from a language without an Amp SDK, generate a client from it.
