> **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.

# Config Maps

Status: Unstable.

A config map is a named configuration document that Amp stores, like an RPC endpoint's settings, and that you manage
through the Admin API.

In this release, no part of Amp loads config maps yet. You can create, update, list, and delete them, as this page
shows, but RPC endpoints and export destinations are configured as [providers](/managing-amp/providers), which Amp
stores on their own.

This page explains how config maps work and how to manage them. To write their documents, see
[Config Map Format](/managing-amp/config-maps/format). To keep credentials out of them, see
[Secrets in Config Maps](/managing-amp/config-maps/secrets).

## Prerequisites

* Amp running, with its [Admin API](/managing-amp/admin-api) reachable. [Solo mode](/running-amp/solo-mode) serves it
  at `http://localhost:1610`.
* [`ampctl`](/clis/ampctl/configmap) installed, for the `ampctl` examples. It sends its requests to
  `http://localhost:1610` unless you pass `--admin-url` or set `AMP_ADMIN_URL`.
* Access to the namespace you work in, if your Admin API sits behind a gateway that checks it (see
  [Access](/managing-amp/config-maps#access)). Give `ampctl` your token with `--auth-token` or `AMP_AUTH_TOKEN`.

## How config maps work

### Namespaces and names

A config map's address is its namespace and name, written `namespace/name`, like `acme/mainnet_rpc`. Every command
refers to a config map this way.

* **Namespace**: the tenant the config map belongs to, so the same name can exist once in each namespace. Lowercase
  letters, digits, and underscores. `_` is the global namespace.
* **Name**: a lowercase letter or an underscore, followed by lowercase letters, digits, and underscores, like
  `mainnet_rpc`.

### Kinds

Each config map has a kind, a label saying what its document configures: `evm-rpc` for an EVM RPC provider,
`adbc-postgres` for a PostgreSQL destination, and so on. The kind is any non-empty string, sent beside the document
rather than inside it.

Amp doesn't check a document against its kind: any document is accepted, listed, and returned like any other, so a
successful write doesn't mean the configuration is valid. A misspelled kind is stored as you wrote it, too.

### Generations

A config map holds one document at a time. Each write that changes the document or the kind creates a new
**generation** of the config map, with a new generation ID, and discards the previous one. Amp keeps no history, so
there's no going back to an earlier generation.

Reading `acme/mainnet_rpc` always returns its current generation.

## Create a config map

Write the document in a TOML or JSON file. This one describes an EVM RPC endpoint, with its token kept in an
environment variable (see [Secrets in Config Maps](/managing-amp/config-maps/secrets)):

```toml [mainnet_rpc.toml]
kind = "evm-rpc"
network = "mainnet"
url = "https://ethereum-rpc.example.com"
auth_token = "${{ env.MAINNET_RPC_TOKEN }}"
rate_limit_per_minute = 600
```

Then create the config map from it:

:::code-group
```bash [ampctl]
ampctl configmap create mainnet_rpc --namespace acme --from-file ./mainnet_rpc.toml
```

```bash [Admin API]
curl -X POST http://localhost:1610/configmaps \
  -H 'Content-Type: application/json' \
  -d '{
    "namespace": "acme",
    "name": "mainnet_rpc",
    "kind": "evm-rpc",
    "data": {
      "kind": "evm-rpc",
      "network": "mainnet",
      "url": "https://ethereum-rpc.example.com",
      "auth_token": "${{ env.MAINNET_RPC_TOKEN }}",
      "rate_limit_per_minute": 600
    }
  }'
```
:::

`ampctl` converts a TOML file to JSON before sending it. It prints the generation the config map now holds:

```text
✓ ConfigMap created
  Name: acme/mainnet_rpc
  Kind: evm-rpc
  Id: 019a3f2c-5b8e-7d41-9c2a-6e1f0b7d3a95
```

The Admin API answers `201 Created`, with the generation ID:

```json
{ "id": "019a3f2c-5b8e-7d41-9c2a-6e1f0b7d3a95" }
```

Creating replaces a config map that already exists under the name, rather than failing. `ampctl` then says
`✓ ConfigMap updated (the name was already bound)`, and the Admin API answers `200 OK`. To change a config map only if
it exists, [update it](/managing-amp/config-maps#update-a-config-map) instead.

### Options

`ampctl configmap create` (alias `register`) takes the config map's name, then:

| Option                    | Type   | Default                     | Description                                                                                                                                                                                      |
| ------------------------- | ------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--namespace <NAMESPACE>` | string | required                    | The namespace to create it in, like `--namespace acme`.                                                                                                                                          |
| `--from-file <FILE>`      | path   | required                    | The document: a local path, or a `file://`, `s3://`, `gs://`, or `az://` URL, like `--from-file s3://my-bucket/configmaps/mainnet_rpc.toml`. Its extension, `.toml` or `.json`, sets its format. |
| `--kind <KIND>`           | string | the file's top-level `kind` | The config map's kind, like `--kind evm-rpc`. Required when the file has no top-level `kind` key. The key stays in the document either way.                                                      |

For a file in object storage, set the store's usual credentials in the environment, like `AWS_ACCESS_KEY_ID`,
`GOOGLE_APPLICATION_CREDENTIALS`, or `AZURE_STORAGE_ACCOUNT_NAME`.

Every `ampctl` command also takes these options:

| Option                      | Type   | Default                 | Description                                                                                |
| --------------------------- | ------ | ----------------------- | ------------------------------------------------------------------------------------------ |
| `--admin-url <ADMIN_URL>`   | URL    | `http://localhost:1610` | The Admin API to send requests to. Also read from `AMP_ADMIN_URL`.                         |
| `--auth-token <AUTH_TOKEN>` | string | none                    | A token sent as `Authorization: Bearer` to the Admin API. Also read from `AMP_AUTH_TOKEN`. |
| `--json`                    | flag   | off                     | Prints the result as JSON on one line, for scripts.                                        |

In the Admin API, the request body's four fields are all required: `namespace`, `name`, `kind`, and `data`, the document
as a JSON object. See [Create or update a config
map](/managing-amp/admin-api/config-maps#create-or-update-a-config-map).

## Update a config map

Updating replaces the whole document of a config map that exists. Nothing is merged, so send the complete document.
Edit the file, then update the config map, giving its full address:

:::terminal
```bash
ampctl configmap update acme/mainnet_rpc --from-file ./mainnet_rpc.toml
```

```text
✓ ConfigMap updated
  Name: acme/mainnet_rpc
  Kind: evm-rpc
  Id: 019a3f41-08d2-7a6c-b3f1-2c9e5d7a4b10
```
:::

The new ID shows that a new generation replaced the old one. If the document and kind are the same as what the config
map holds, nothing changes, and the ID stays the same:

```text
✓ ConfigMap unchanged (the document was equivalent)
  Name: acme/mainnet_rpc
  Kind: evm-rpc
  Id: 019a3f41-08d2-7a6c-b3f1-2c9e5d7a4b10
```

With `--json`, the result also gives the outcome, `replaced` or `unchanged`, and the ID the config map held before:

```json
{
  "id": "019a3f41-08d2-7a6c-b3f1-2c9e5d7a4b10",
  "previous_id": "019a3f2c-5b8e-7d41-9c2a-6e1f0b7d3a95",
  "namespace": "acme",
  "name": "mainnet_rpc",
  "kind": "evm-rpc",
  "outcome": "replaced"
}
```

`ampctl configmap update` takes the same `--from-file` and `--kind` options as `create`. The kind can differ from the
one the config map holds; changing it creates a new generation. Unlike `create`, `update` never creates a config map:
for one that doesn't exist, it fails with `config map 'acme/mainnet_rpc' not found`.

### Updates that race

`update` reads the generation the config map holds, then writes on the condition that it still holds it. If someone
else changes or deletes the config map in between, nothing is written, and the command fails:

```text
✗ config map 'acme/mainnet_rpc' changed or was deleted during the update; inspect it and retry
```

[Inspect the config map](/managing-amp/config-maps#inspect-a-config-map) to see what changed, then run the update again.

With the Admin API, send the generation ID you expect in an `If-Match` header, unquoted. The write goes ahead only if
the config map still holds that generation, and otherwise answers `412 Precondition Failed` with
`CONFIGMAP_PRECONDITION_FAILED`. An `If-Match` on a name that holds nothing fails the same way, rather than creating
it. Without the header, the last write wins.

```bash
curl -X POST http://localhost:1610/configmaps \
  -H 'Content-Type: application/json' \
  -H 'If-Match: 019a3f2c-5b8e-7d41-9c2a-6e1f0b7d3a95' \
  -d @mainnet_rpc.json
```

Here `mainnet_rpc.json` holds the same body as in [Create a config map](/managing-amp/config-maps#create-a-config-map).

### What a write does

Creating and updating send the same request, which ends one of three ways:

| Outcome   | Status        | What happened                                                                   | `ampctl` says                                   |
| --------- | ------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- |
| Created   | `201 Created` | The name held nothing. It now holds a new generation.                           | `created`                                       |
| Replaced  | `200 OK`      | The document or kind changed. A new generation replaced the previous one.       | `updated` (`replaced` with `--json`, on update) |
| Unchanged | `200 OK`      | The document and kind are equivalent to the current ones. The generation stays. | `unchanged` on update, `updated` on create      |

The response always carries the ID of the generation the config map holds afterwards. Compare it with the ID you had to
tell whether your write changed anything. That makes a write safe to repeat.

## List config maps

List the config maps of one namespace, sorted by name. A listing never includes their documents, since any of them could
hold a credential.

:::code-group
```bash [ampctl]
ampctl configmap list --namespace acme
```

```bash [Admin API]
curl 'http://localhost:1610/configmaps?namespace=acme'
```
:::

`ampctl` prints each config map's name, kind, and current generation ID:

```text
ConfigMaps in namespace 'acme':
  mainnet_rpc  evm-rpc  019a3f41-08d2-7a6c-b3f1-2c9e5d7a4b10
  polygon_rpc  evm-rpc  019a3f45-7c10-7e2b-8f4d-91a6c3e0b257
```

The Admin API, and `ampctl` with `--json`, also give when each config map was created and last changed. `created_by` and
`updated_by` are always `null` at v0.0.42, since Amp doesn't record who made a change yet:

```json
{
  "configmaps": [
    {
      "id": "019a3f41-08d2-7a6c-b3f1-2c9e5d7a4b10",
      "namespace": "acme",
      "name": "mainnet_rpc",
      "kind": "evm-rpc",
      "created_at": "2026-10-30T14:02:11.482913+00:00",
      "created_by": null,
      "updated_at": "2026-10-30T14:07:45.120554+00:00",
      "updated_by": null
    }
  ]
}
```

`ampctl configmap list` (alias `ls`) requires `--namespace`: there's no default namespace, and no listing across
namespaces. A namespace without config maps prints `No ConfigMaps found in namespace 'acme'` and isn't an error. See
[List config maps](/managing-amp/admin-api/config-maps#list-config-maps) for the endpoint.

## Inspect a config map

Read a config map's current generation, with its document:

:::code-group
```bash [ampctl]
ampctl configmap inspect acme/mainnet_rpc
```

```bash [Admin API]
curl http://localhost:1610/configmaps/acme/mainnet_rpc
```
:::

```text
Id:        019a3f41-08d2-7a6c-b3f1-2c9e5d7a4b10
Namespace: acme
Name:      mainnet_rpc
Kind:      evm-rpc
Data:
{
  "auth_token": "${{ env.MAINNET_RPC_TOKEN }}",
  "kind": "evm-rpc",
  "network": "mainnet",
  "rate_limit_per_minute": 600,
  "url": "https://ethereum-rpc.example.com"
}
```

The document comes back as stored: environment variable references aren't filled in, and nothing is hidden, so a
credential written into it is shown too. Its keys may come back in a different order from your file, and a key you
wrote twice comes back once.

The Admin API, and `ampctl` with `--json`, return the same fields as one object, with the document under `data`. To
save the document, for example to re-create the config map elsewhere:

```bash
ampctl configmap inspect acme/mainnet_rpc --json | jq .data > mainnet_rpc.json
```

`ampctl configmap inspect` (alias `get`) takes the config map's address and no other options. For one that doesn't
exist, it fails with `config map 'acme/mainnet_rpc' not found`. See
[Get a config map](/managing-amp/admin-api/config-maps#get-a-config-map) for the endpoint.

## Delete a config map

Deleting a config map removes it and its document for good. Save the document first if you may need it again.

:::terminal
```bash
ampctl configmap delete acme/mainnet_rpc
```

```text
⚠ Jobs that load acme/mainnet_rpc fail once it is deleted. Delete config map acme/mainnet_rpc? [y/N] y
✓ Config map acme/mainnet_rpc deleted
```
:::

No job loads config maps in this release, so the warning doesn't apply yet. Answering anything but `y` or `yes`
deletes nothing. `ampctl configmap delete` (alias `rm`) takes the config map's
address, and:

| Option        | Type | Default | Description                                           |
| ------------- | ---- | ------- | ----------------------------------------------------- |
| `-f, --force` | flag | off     | Deletes without asking for confirmation, for scripts. |

With the Admin API, send `DELETE` to the config map's address. It answers `204 No Content`, with no confirmation:

```bash
curl -X DELETE http://localhost:1610/configmaps/acme/mainnet_rpc
```

Deleting a config map that doesn't exist succeeds too, so it's safe to repeat. See
[Delete a config map](/managing-amp/admin-api/config-maps#delete-a-config-map) for the endpoint.

## Access

The Admin API checks access per namespace: reading or listing a namespace's config maps takes a read grant on it, and
creating, updating, or deleting takes a write grant. The global namespace, `_`, is no exception.

Grants come in the `amp-capabilities` header, which a gateway in front of the Admin API adds for each caller. A request
without the header can reach every namespace. That's the case in [solo mode](/running-amp/solo-mode), where `ampctl`
reaches the Admin API directly.

A namespace you have no grant on answers `404 Not Found` with `NAMESPACE_NOT_FOUND`, as if it had no config maps,
rather than `403 Forbidden`. That way, nobody learns which tenants exist. `ampctl` reports
`namespace 'acme' not found` for a listing or a delete, and `config map 'acme/mainnet_rpc' not found` for an inspect
or an update.

A namespace you have a grant on but that holds no config maps lists as empty, with `200 OK`.

## Errors

The Admin API returns errors as JSON, with a stable `error_code` to act on and an `error_message` for people:

```json
{
  "error_code": "CONFIGMAP_NOT_FOUND",
  "error_message": "config map 'acme/mainnet_rpc' not found"
}
```

| Code                            | Status | Cause                                                                                 | Fix                                                                                                                       |
| ------------------------------- | ------ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_BODY`                  | 400    | The body isn't JSON, a field is missing, or the namespace, name, or kind isn't valid. | Send all four fields, with a namespace and name made of lowercase letters, digits, and underscores, and a non-empty kind. |
| `INVALID_CONFIGMAP_DATA`        | 400    | The document's top level isn't a JSON object.                                         | Wrap the document in an object.                                                                                           |
| `INVALID_IF_MATCH`              | 400    | The `If-Match` value isn't a generation ID.                                           | Send the ID as returned, unquoted.                                                                                        |
| `INVALID_PATH`                  | 400    | The namespace or name in the URL isn't valid.                                         | Fix the address.                                                                                                          |
| `INVALID_QUERY_PARAMETERS`      | 400    | `GET /configmaps` has no `namespace` parameter, or it isn't valid.                    | Add `?namespace=acme`.                                                                                                    |
| `NAMESPACE_NOT_FOUND`           | 404    | You have no grant on the namespace.                                                   | Ask for access to it, or check its spelling.                                                                              |
| `CONFIGMAP_NOT_FOUND`           | 404    | No config map has this name in the namespace.                                         | Check the name with a [listing](/managing-amp/config-maps#list-config-maps).                                                                       |
| `CONFIGMAP_PRECONDITION_FAILED` | 412    | The config map doesn't hold the generation `If-Match` names. Nothing was written.     | Read it again, then retry with its current ID.                                                                            |
| `CONFIGMAP_TOO_LARGE`           | 413    | The document is over 1 MiB as JSON. Nothing was written.                              | Shrink the document, or split it into several config maps.                                                                |
| `APPLY_CONFIGMAP_ERROR`         | 500    | The write failed.                                                                     | Check the metadata database, then retry.                                                                                  |
| `GET_CONFIGMAP_ERROR`           | 500    | The read failed.                                                                      | Check the metadata database, then retry.                                                                                  |
| `LIST_CONFIGMAPS_ERROR`         | 500    | The listing failed.                                                                   | Check the metadata database, then retry.                                                                                  |
| `DELETE_CONFIGMAP_ERROR`        | 500    | The delete failed.                                                                    | Check the metadata database, then retry.                                                                                  |

`ampctl` checks the file before sending anything, and fails on a missing or unreadable file, a file that doesn't parse,
an extension other than `.toml` or `.json`, a top level that isn't an object, a document over 1 MiB, and a missing kind:

```text
✗ failed to load config map document
  → --kind is required because the file has no top-level string `kind` key
```

## Limitations

* No part of Amp loads config maps yet: they're stored, not used.
* Amp doesn't check documents against their kinds.
* There's no history: a write discards the previous generation for good.
* A listing never includes documents, so comparing several config maps means inspecting each one.
* Documents come back as stored, credentials included. Nothing stops you from storing a credential, and nothing hides
  one. See [Secrets in Config Maps](/managing-amp/config-maps/secrets).
* Writes don't record who made them yet: `created_by` and `updated_by` are `null`.
