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

# Destinations

Status: Unstable.

A destination is a database that [exports](/managing-amp/pipelines) write datasets to. Amp writes to PostgreSQL and
Snowflake, through ADBC (Arrow Database Connectivity, a standard way to send Arrow data, the format Amp works in, to a
database).

You register a destination with Amp as a [provider](/managing-amp/providers) of kind `adbc-postgres` or
`adbc-snowflake`. Its configuration says where the database is and how to write to it, but never holds a credential:
each connection detail names an environment variable instead, which the [worker](/managing-amp/workers) reads when an
export starts. Exports refer to a destination by its name: `--provider warehouse`.

## 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/provider), for the `ampctl` examples. It sends its requests to `http://localhost:1610` unless
  you pass `--admin-url` or set `AMP_ADMIN_URL`.
* A PostgreSQL or Snowflake database that the machines running workers can reach, and a role in it that can create
  tables in the schemas Amp writes to. On Snowflake, the role also needs to use the warehouse, and to create stages in
  the schema the data goes to.
* A way to set environment variables for `ampd`: Amp reads the credentials from the environment of the process running
  the worker.
* To export to it, the ADBC driver for your database, which `ampd` loads when an export starts (see
  [Install ADBC drivers](/running-amp/installation#install-adbc-drivers)). Registering a destination doesn't need it.

## Register a PostgreSQL destination

::::steps
### Create the schemas

Amp creates the tables it writes, but not the schemas they go in. Create one schema for the exported data, and one for
`amp_export_state`, the table where Amp records how far each export got (see [Export state](/managing-amp/pipelines/destinations#export-state)). This
example uses `eth` and the default state schema, `amp`:

```bash
psql "$WAREHOUSE_URL" -c 'CREATE SCHEMA IF NOT EXISTS eth; CREATE SCHEMA IF NOT EXISTS amp;'
```

Here `WAREHOUSE_URL` is your database's connection URL, like
`postgresql://amp:your-password@localhost:5432/analytics`.

### Write its configuration

Save the destination's configuration as a TOML file, `warehouse.toml`:

```toml [warehouse.toml]
kind = "adbc-postgres"

# Where and how to connect: environment variable references, never values
host = "${AMP_ADBC_WAREHOUSE_HOST}"
port = "${AMP_ADBC_WAREHOUSE_PORT}"
database = "${AMP_ADBC_WAREHOUSE_DATABASE}"
username = "${AMP_ADBC_WAREHOUSE_USERNAME}"
password = "${AMP_ADBC_WAREHOUSE_PASSWORD}"

# TLS: use the defaults unless the variables are set
ssl_mode = { default = true }
ssl_cert = { default = true }
ssl_key = { default = true }
ssl_root_cert = { default = true }
ssl_crl = { default = true }

# Where the data and Amp's bookkeeping go
data_schema = "eth"
state_schema = "amp"
application_name = "amp_export"
```

The destination's name will be `warehouse`, so its variables start with `AMP_ADBC_WAREHOUSE_` (see
[Names and aliases](/managing-amp/pipelines/destinations#names-and-aliases)). Every connection setting must be declared, either as its variable or with
`{ default = true }` (see [Connection settings](/managing-amp/pipelines/destinations#connection-settings)).

### Set the environment variables

Set the variables in the environment `ampd` runs in, then restart it, so its worker can read them:

```bash
export AMP_ADBC_WAREHOUSE_HOST=localhost
export AMP_ADBC_WAREHOUSE_PORT=5432
export AMP_ADBC_WAREHOUSE_DATABASE=analytics
export AMP_ADBC_WAREHOUSE_USERNAME=amp
export AMP_ADBC_WAREHOUSE_PASSWORD=your-password
ampd solo
```

Set the port even when it's PostgreSQL's usual `5432`: declared as its variable, it's required, and the export fails
when it's unset. Only `port = { default = true }` falls back to a default, `5433`.

In distributed mode, set them on every worker that may run an export. The controller doesn't need them.

### Register it

:::code-group
```bash [ampctl]
ampctl provider register warehouse ./warehouse.toml
```

```bash [Admin API]
curl -X POST http://localhost:1610/providers \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "warehouse",
    "kind": "adbc-postgres",
    "host": "${AMP_ADBC_WAREHOUSE_HOST}",
    "port": "${AMP_ADBC_WAREHOUSE_PORT}",
    "database": "${AMP_ADBC_WAREHOUSE_DATABASE}",
    "username": "${AMP_ADBC_WAREHOUSE_USERNAME}",
    "password": "${AMP_ADBC_WAREHOUSE_PASSWORD}",
    "ssl_mode": { "default": true },
    "ssl_cert": { "default": true },
    "ssl_key": { "default": true },
    "ssl_root_cert": { "default": true },
    "ssl_crl": { "default": true },
    "data_schema": "eth",
    "state_schema": "amp",
    "application_name": "amp_export"
  }'
```
:::

The Admin API answers `201 Created`. Amp checks the configuration's shape when you register it, but not the connection:
it doesn't have the variables. A wrong password or an unreachable host shows when an export starts, as the export job's
error.

### Check it

Your destination is listed with the other providers:

:::terminal
```bash
ampctl provider list
```

```text
Providers:
  mainnet_rpc (evm-rpc)
  warehouse (adbc-postgres)
```
:::

`ampctl provider inspect warehouse` prints its configuration, with its references as you wrote them.
::::

You can now export to it: see [Export a dataset](/managing-amp/pipelines#export-a-dataset).

## Register a Snowflake destination

:::warning
At Amp v0.0.42, every export to Snowflake fails as it starts, with
`IO: [Snowflake] 002028 (42601): SQL compilation error: ambiguous column name 'NETWORK'`, and its job retries without
writing a row. Amp runs a statement on its [state table](/managing-amp/pipelines/destinations#export-state) that Snowflake rejects. Registering a
destination works. Amp's next release will have the fix.
:::

A Snowflake destination works the same way, with Snowflake's connection settings. This one is named `snowflake_prod`,
and logs in with a key pair, which is how a service user like Amp's logs in to Snowflake:

:::steps
### Create the key pair

Generate a private key in PKCS#8 form, and its public key:

```bash
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
```

### Create the user and schemas

In Snowflake, create a service user with the contents of `rsa_key.pub`, without its `-----BEGIN` and `-----END` lines,
and the schemas. Amp creates the tables it writes, but not the schemas they go in. An existing user can take the key
with `ALTER USER … SET RSA_PUBLIC_KEY='…'`. Snowflake's [key-pair authentication
guide](https://docs.snowflake.com/en/user-guide/key-pair-auth) has the details.

```sql
CREATE USER AMP_EXPORTER TYPE = SERVICE DEFAULT_ROLE = AMP_EXPORTER RSA_PUBLIC_KEY = 'MIIBIjANBgkqh…';
GRANT ROLE AMP_EXPORTER TO USER AMP_EXPORTER;
CREATE SCHEMA IF NOT EXISTS ANALYTICS.ETH;
CREATE SCHEMA IF NOT EXISTS ANALYTICS.AMP;
```

The role also needs the privileges listed in [Prerequisites](/managing-amp/pipelines/destinations#prerequisites). When a schema doesn't exist, or the role
can't use it, the export's job retries with `The requested schema does not exist or not authorized`.

### Write its configuration

```toml [snowflake_prod.toml]
kind = "adbc-snowflake"

account = "${AMP_ADBC_SNOWFLAKE_PROD_ACCOUNT}"
user = "${AMP_ADBC_SNOWFLAKE_PROD_USER}"
warehouse = "${AMP_ADBC_SNOWFLAKE_PROD_WAREHOUSE}"
database = "${AMP_ADBC_SNOWFLAKE_PROD_DATABASE}"
role = "${AMP_ADBC_SNOWFLAKE_PROD_ROLE}"

# Key-pair authentication
auth_type = "${AMP_ADBC_SNOWFLAKE_PROD_AUTH_TYPE}"
password = { default = true }
private_key = "${AMP_ADBC_SNOWFLAKE_PROD_PRIVATE_KEY}"
private_key_passphrase = { default = true }
token = { default = true }

data_schema = "ETH"
state_schema = "AMP"
```

### Set the environment variables

Set the variables in the environment `ampd` runs in, then restart it, as for PostgreSQL:

```bash
export AMP_ADBC_SNOWFLAKE_PROD_ACCOUNT=myorg-myaccount
export AMP_ADBC_SNOWFLAKE_PROD_USER=AMP_EXPORTER
export AMP_ADBC_SNOWFLAKE_PROD_WAREHOUSE=EXPORT_WH
export AMP_ADBC_SNOWFLAKE_PROD_DATABASE=ANALYTICS
export AMP_ADBC_SNOWFLAKE_PROD_ROLE=AMP_EXPORTER
export AMP_ADBC_SNOWFLAKE_PROD_AUTH_TYPE=jwt
export AMP_ADBC_SNOWFLAKE_PROD_PRIVATE_KEY="$(cat rsa_key.p8)"
ampd solo
```

### Register it

```bash
ampctl provider register snowflake_prod ./snowflake_prod.toml
```
:::

For an encrypted key, declare `private_key_passphrase = "${AMP_ADBC_SNOWFLAKE_PROD_PRIVATE_KEY_PASSPHRASE}"` instead,
and set that variable too.

The key must be in PKCS#8 form, starting with `-----BEGIN PRIVATE KEY-----`, or `-----BEGIN ENCRYPTED PRIVATE KEY-----`
for an encrypted one. Amp rejects another form, like an RSA key starting with `-----BEGIN RSA PRIVATE KEY-----`, when an
export starts: `value of environment variable AMP_ADBC_SNOWFLAKE_PROD_PRIVATE_KEY is not valid for its config field`.
Convert one with `openssl pkcs8 -topk8 -nocrypt -in rsa_key.pem -out rsa_key.p8`.

`auth_type` chooses the credential the session logs in with:

| `auth_type`           | Credential it uses                                                                |
| --------------------- | --------------------------------------------------------------------------------- |
| `jwt`                 | `private_key`, a PKCS#8 PEM key, and `private_key_passphrase` when it's encrypted |
| `pat`                 | `token`, a programmatic access token                                              |
| `oauth`               | `token`, an OAuth access token                                                    |
| `snowflake` (default) | `password`                                                                        |

Amp reads every credential the configuration declares, whichever one `auth_type` chooses. A credential declared as its
variable, like `private_key` above, is required: when its variable is unset, the export fails as it starts, and its job
ends `FATAL`, with an error that says
`environment variable AMP_ADBC_SNOWFLAKE_PROD_PRIVATE_KEY is required by the provider config but is not set`. Declare
the credentials you don't use with `{ default = true }`. One declared that way and left unset, or one set to an empty
value, isn't sent. If it's the one `auth_type` chooses, the driver refuses to log in when an export starts, and the job
retries, with an error like
`IO: trying to use keypair authentication, but PrivateKey was not provided in the driver config`, or
`IO: [Snowflake] 260002: password is empty` for a password.

Snowflake folds unquoted names to uppercase, so `data_schema = "eth"` and `"ETH"` name the same schema. Write a name in
double quotes, like `'"eth"'`, to keep its case.

:::note
Snowflake destinations only support the `append` [reorg policy](/managing-amp/pipelines#reorg-policies). Once exports to
Snowflake work, they can write a microbatch's rows twice when an export crashes (see [Exactly-Once
Delivery](/managing-amp/pipelines/exactly-once)).
:::

### Log in with a password

Snowflake is [phasing out sign-in with a password alone](https://docs.snowflake.com/en/user-guide/security-mfa-rollout),
on a schedule that varies by account and excludes trial and reader accounts. Once the rollout reaches an account, a new
user can't be `TYPE = LEGACY_SERVICE`, and a person's password needs a second factor, which an export can't provide. In
any account, a user of `TYPE = SERVICE` can't have a password. Only use a password if your account still lets that user
sign in with one.
In `snowflake_prod.toml`, replace the authentication lines with:

```toml [snowflake_prod.toml]
# Password authentication: auth_type defaults to snowflake
auth_type = { default = true }
password = "${AMP_ADBC_SNOWFLAKE_PROD_PASSWORD}"
private_key = { default = true }
private_key_passphrase = { default = true }
token = { default = true }
```

Set the password's variable in place of the key's, in the environment `ampd` runs in, and restart it:

```bash
export AMP_ADBC_SNOWFLAKE_PROD_PASSWORD=your-password
unset AMP_ADBC_SNOWFLAKE_PROD_AUTH_TYPE AMP_ADBC_SNOWFLAKE_PROD_PRIVATE_KEY
ampd solo
```

Then register the file. If `snowflake_prod` is already registered, registering it again replaces its configuration (see
[Versions](/managing-amp/pipelines/destinations#versions)):

```bash
ampctl provider register snowflake_prod ./snowflake_prod.toml
```

## Configuration

Both kinds take the same structure: `kind`, the connection settings, which are all secret, and settings that aren't. The
[PostgreSQL](/managing-amp/pipelines/postgresql) and [Snowflake](/managing-amp/pipelines/snowflake) references list
every field, with its type, default, and the values its variable accepts.

### Names and aliases

A destination's name is lowercase letters, digits, and underscores, starting with a letter or an underscore, and at most
32 characters, like `warehouse`. Its alias is the name in uppercase, `WAREHOUSE`, and names its environment variables:
`AMP_ADBC_<ALIAS>_<FIELD>`, like `AMP_ADBC_WAREHOUSE_PASSWORD`. Each destination has its own variables, unless it
[shares another's](/managing-amp/pipelines/destinations#sharing-credentials).

You don't write the alias in the configuration. If you do, as `alias = "WAREHOUSE"`, it must be exactly the name in
uppercase: registration fails with `ADBC_ALIAS_MISMATCH` otherwise.

### Connection settings

Every connection setting is a secret: the host, port, database, user, credentials, and TLS files for PostgreSQL; the
account, user, warehouse, database, role, authentication type, and credentials for Snowflake. You can't write a value
for one. Each must be declared in one of two ways:

* **A reference to its variable**, `"${AMP_ADBC_WAREHOUSE_HOST}"`. The variable must be set: an export fails when it
  isn't. A reference must name the setting's own variable: `"${MY_HOST}"`, or another destination's variable, is
  refused.
* **A table** saying how to resolve it:

  | Key        | Type                                        | Default                                  | Description                                                                                                                        |
  | ---------- | ------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
  | `default`  | boolean                                     | `false`                                  | When the variable is unset, use the setting's default instead of failing, like `localhost` for the PostgreSQL host.                |
  | `required` | boolean                                     | `true`, or `false` when `default = true` | Whether an unset variable fails the export. Combining `default = true` with `required = true` is refused.                          |
  | `using`    | string, or table with `alias` and `version` | none                                     | Read the variable of another destination instead, like `{ using = "WAREHOUSE" }`. See [Sharing credentials](/managing-amp/pipelines/destinations#sharing-credentials). |

A setting written as a plain value, like `password = "hunter2"`, fails registration with `ADBC_INVALID_SECRET_FIELDS`,
and Amp doesn't repeat the value in the error. So does leaving one out.

Each variable's value is checked when the export reads it. A PostgreSQL host must be a host name or an IPv4 address, a
port a number from 0 to 65535, and `AMP_ADBC_WAREHOUSE_SSL_MODE` one of `disable`, `allow`, `prefer`, `require`,
`verify_ca`, or `verify_full`. The field references list what each one accepts.

### Other settings

These aren't secrets, so they hold their values directly:

| Field               | Type                                  | Default                                     | Description                                                                                                             |
| ------------------- | ------------------------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `data_schema`       | string                                | `public` (PostgreSQL), `PUBLIC` (Snowflake) | The schema exports write their tables to. On Snowflake, also the schema Amp stages its writes in.                       |
| `state_schema`      | string                                | `amp` (PostgreSQL), `AMP` (Snowflake)       | The schema of the [`amp_export_state`](/managing-amp/pipelines/destinations#export-state) table.                                                            |
| `application_name`  | string                                | none                                        | PostgreSQL only: the name the connection reports, shown in `pg_stat_activity` and the server's logs, like `amp_export`. |
| `timeout_secs`      | integer, at least 1                   | `30`                                        | How long a connection attempt or statement may take, in seconds, before it fails.                                       |
| `pool`              | table                                 | see below                                   | How many connections Amp opens, like `pool = { max_connections = 8 }`.                                                  |
| `export_state_mode` | `upsert`, `append`, or `upsert_retry` | `upsert`                                    | How `amp_export_state` records each commit. See [Export state](/managing-amp/pipelines/destinations#export-state).                                          |

`pool` has two fields:

| Field                  | Type                | Default | Description                                                                        |
| ---------------------- | ------------------- | ------- | ---------------------------------------------------------------------------------- |
| `max_connections`      | integer, at least 1 | `4`     | How many connections Amp holds to the database at once, across all workers.        |
| `acquire_timeout_secs` | integer, at least 1 | `30`    | How long, in seconds, an export waits for one of those connections before failing. |

Each exported table holds a connection for as long as its export runs, so an export of five tables needs five.
Raise `max_connections` to the number of tables you export at once through the destination, within what the database
allows.

A value written `${NAME}` in one of these settings, like `data_schema = "${EXPORT_SCHEMA}"`, is read from the worker's
environment when the export starts.

### Sharing credentials

When two destinations use the same credentials, like two schemas of one database, the second can read the first's
variables rather than needing its own. `using` names the destination to read from, by alias:

```toml [warehouse_staging.toml]
kind = "adbc-postgres"
host = { using = "WAREHOUSE" }
port = { using = "WAREHOUSE" }
database = { using = "WAREHOUSE" }
username = { using = "WAREHOUSE" }
password = { using = "WAREHOUSE" }
ssl_mode = { default = true }
ssl_cert = { default = true }
ssl_key = { default = true }
ssl_root_cert = { default = true }
ssl_crl = { default = true }
data_schema = "eth_staging"
state_schema = "amp_staging"
```

This destination, `warehouse_staging`, reads `AMP_ADBC_WAREHOUSE_HOST` and the rest. The destination it names must
already be registered: registration fails with `ADBC_INVALID_SECRET_FIELDS` otherwise. `using` also accepts a lowercase
name, like `"warehouse"`, which it uppercases.

To protect it from changes to the destination it reads, pin a version of that destination, as a requirement like Cargo's
or npm's:

```toml
password = { using = { alias = "WAREHOUSE", version = "^1" } }
```

## Versions

Amp gives each destination a version, `1.0.0` when you first register it. Registering the same name again with a
changed configuration replaces it, and raises the version by what changed:

| Change                                      | Version   | Example           |
| ------------------------------------------- | --------- | ----------------- |
| `kind`                                      | Major     | `1.2.3` → `2.0.0` |
| How a connection setting is declared        | Minor     | `1.2.3` → `1.3.0` |
| Anything else, like `data_schema` or `pool` | Patch     | `1.2.3` → `1.2.4` |
| Nothing                                     | Unchanged | `1.2.3`           |

Changing a variable's value doesn't change the configuration, so it doesn't change the version.

Versions protect what depends on a destination:

* A `using` pin must be met when you register the destination that declares it (`ADBC_VERSION_REQ_UNSATISFIED`).
* An update that would break another destination's pin is refused (`ADBC_VERSION_REQ_CONFLICT`). The error names the
  destinations that pin it.
* An export job can require a version with `ampctl job create adbc --provider-version '^1'` (see
  [Jobs](/managing-amp/jobs)).

The Admin API doesn't return a destination's version. Count your registrations, or keep pins loose, like `^1`.

### When changes take effect

An export reads its destination's configuration and variables when it starts, and keeps them while it runs. A changed
configuration, or a rotated password, reaches an export the next time its job starts: on its next retry, or when you
stop it and create it again (see [Changing an export](/managing-amp/pipelines#changing-an-export)). Restart `ampd`
after changing a variable, so the worker sees its new value.

Register destinations through `ampctl provider register` or `POST /providers`. Amp stores each configuration as a TOML
file in its providers directory, [`providers_dir`](/running-amp/configuration-reference#providers_dir). A file you put
there yourself is only checked and taken into account when the controller next starts: until then, exports can't use it.

## Export state

Amp keeps track of each export in a table it creates in the destination, `amp_export_state`, in `state_schema`. For each
destination table and source network, it records the last block written, `last_committed_block`, and that block's hash,
`resume_hash`. Amp writes each microbatch's rows and the state that acknowledges them together, and an export that
starts again reads the state to know where to continue (see [Resuming](/managing-amp/pipelines/resuming)).

`export_state_mode` sets the table's shape. Choose it when you register the destination: Amp creates the table once and
never changes its shape, so switching between `upsert` and the others later breaks the destination's exports. To change
it, register a new destination.

| Mode           | Rows                                                                            | Use it for                                                                     |
| -------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `upsert`       | One per destination table and network, updated at each commit.                  | The default. Compact, with no history.                                         |
| `append`       | One per commit, numbered by `commit_id`. A retried commit can add a second row. | A history of every commit, and the `replace` and `soft-delete` reorg policies. |
| `upsert_retry` | One per commit, like `append`, but a retried commit replaces its earlier row.   | The same, without duplicate rows. Switching between it and `append` is safe.   |

Its columns, in the `upsert` mode:

| Column                 | Description                                                               |
| ---------------------- | ------------------------------------------------------------------------- |
| `target_schema`        | The destination table's schema.                                           |
| `target_table`         | The destination table.                                                    |
| `network`              | The source network the row is about.                                      |
| `last_committed_block` | The last block written to the table, for that network.                    |
| `resume_hash`          | That block's hash, which lets a restarted export detect reorgs it missed. |
| `updated_at`           | When the row was last written.                                            |

The `append` and `upsert_retry` modes add `commit_id`, the commit's number, which the `_amp_commit_id` and
`_amp_invalidated_by` columns of exported rows refer to; `event_kind`, what the commit did (`insert`, `delete`, `reorg`,
or `snapshot`); `block_range_start` and `block_range_end`, the block range it covered; and `row_count`.

To see how far each table's export got:

```sql
SELECT target_schema, target_table, network, last_committed_block, updated_at
FROM amp.amp_export_state
ORDER BY target_schema, target_table;
```

That query reads the default `upsert` mode. In the `append` and `upsert_retry` modes, a table's newest row, the one with
the highest `commit_id`, holds its position. Don't edit this table: an export that finds no row for a table starts it
over from the beginning, writing its rows a second time.

## Column types

Amp creates each destination table with the source table's columns, converting their types to the database's:

* **PostgreSQL** uses standard types, so it works with any PostgreSQL, including Aurora and Cloud SQL. Unsigned 64-bit
  integers become `NUMERIC(20, 0)`, smaller unsigned integers the next larger signed type, and lists, structs, and maps
  `JSON`.
* **Snowflake** stores every integer as `BIGINT` (`NUMBER(38, 0)`), unsigned 64-bit integers as `NUMERIC(20, 0)`, every
  float as `DOUBLE PRECISION`, timestamps as `TIMESTAMP_LTZ`, binary columns as `BINARY`, lists as `ARRAY`, and structs
  and maps as JSON text in `VARCHAR`. It can't hold 256-bit decimals, intervals, or `Null` columns: an export of a table
  with one fails. Column names are stored in uppercase, so `block_num` becomes `BLOCK_NUM`.

An existing destination table must have the columns Amp expects, checked before the first row is written.

## Removing a destination

:::code-group
```bash [ampctl]
ampctl provider remove warehouse
```

```bash [Admin API]
curl -X DELETE http://localhost:1610/providers/warehouse
```
:::

Removing a destination deletes its configuration, not the database's tables. It's refused while an export is connected
to it (`PROVIDER_IN_USE`), and while another destination reads it with `using` (`PROVIDER_REFERENCED`, naming them).
Removing one that doesn't exist succeeds. An export that still names it fails when its job next starts.

## Errors

Registering or removing a destination can fail with these codes, in the `error_code` field of the Admin API's response:

| Code                           | Status | Cause and fix                                                                                                                                   |
| ------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_REQUEST_BODY`         | 400    | The body isn't valid JSON, or the name isn't lowercase letters, digits, and underscores.                                                        |
| `ADBC_INVALID_ALIAS`           | 400    | No alias can be made from the name: it's over 32 characters, or has no letter or digit. Or the configuration's `alias` is malformed.            |
| `ADBC_ALIAS_MISMATCH`          | 400    | The configuration's `alias` isn't the name in uppercase. Remove it, or fix it.                                                                  |
| `ADBC_INVALID_SECRET_FIELDS`   | 400    | A connection setting has a plain value, a reference to the wrong variable, or is missing, or `using` names a destination that isn't registered. |
| `INVALID_ADBC_CONFIG`          | 400    | A field has the wrong type or value, or a `${…}` reference is malformed. The message names the field.                                           |
| `ADBC_VERSION_REQ_UNSATISFIED` | 400    | A `using` pin doesn't match the version of the destination it reads.                                                                            |
| `DATA_CONVERSION_ERROR`        | 400    | The configuration can't be stored as TOML: it has a `null` value, which TOML can't hold.                                                        |
| `PROVIDER_CONFLICT`            | 409    | Someone registered the same name at the same moment. Try again.                                                                                 |
| `ADBC_VERSION_REQ_CONFLICT`    | 409    | The update would break the version another destination pins. The message names it.                                                              |
| `PROVIDER_KIND_CONFLICT`       | 409    | The name belongs to a destination, and the request registers another kind of provider. Remove the destination first.                            |
| `PROVIDER_REFERENCED`          | 409    | Removing: other destinations read this one with `using`. Update them first.                                                                     |
| `PROVIDER_IN_USE`              | 409    | Removing: an export is connected to it. Stop the export first.                                                                                  |

Codes with status `500` (`ADBC_VALIDATION_INTERNAL`, `PROVIDER_CATALOG_ERROR`, `STORE_ERROR`) mean Amp failed to check
or store the destination; its logs say why. The [Admin API reference](/managing-amp/admin-api/providers) lists every
code of every provider endpoint.
