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

# Extensions API: list, install and upgrade

> Read the citext, pg_trgm and pgcrypto catalog for a DBHost database, then install or upgrade one extension as an idempotent operation.

DBHost supports three PostgreSQL 16 extensions: `citext`, `pg_trgm` and
`pgcrypto`. These routes read the catalog for one database and install or
upgrade one extension at a time. For what each extension is for, see
[PostgreSQL extensions](/guides/postgres-extensions).

* API access requires a Pro or Business plan. See
  [Authentication](/api-reference/authentication).
* A selected-database key works for the databases it is assigned to. A
  full-account key works for every database you own.
* Install and upgrade work only on databases with
  [managed SQL roles](/guides/database-roles). A single-login database returns
  the catalog but cannot install or upgrade.

All three routes respond with `Cache-Control: no-store`.

## List extensions

`GET /api/v1/databases/{id}/extensions`

The database must be active. The response always lists the three extensions
in the order `citext`, `pg_trgm`, `pgcrypto`.

```http theme={null}
GET /api/v1/databases/{id}/extensions
Authorization: Bearer <api-key>
```

```json 200 OK theme={null}
{
  "version": 1,
  "databaseId": "550e8400-e29b-41d4-a716-446655440000",
  "databaseName": "my-app",
  "databaseOid": 16412,
  "postgresVersionNum": 160009,
  "extensions": [
    {
      "name": "citext",
      "available": true,
      "defaultVersion": "1.6",
      "installedVersion": "1.6",
      "installedSchema": "public",
      "trusted": true,
      "catalogProfile": "sql_verified",
      "restoreQualified": true
    },
    {
      "name": "pg_trgm",
      "available": true,
      "defaultVersion": "1.6",
      "installedVersion": null,
      "installedSchema": null,
      "trusted": true,
      "catalogProfile": "sql_verified",
      "restoreQualified": true
    },
    {
      "name": "pgcrypto",
      "available": true,
      "defaultVersion": "1.3",
      "installedVersion": null,
      "installedSchema": null,
      "trusted": true,
      "catalogProfile": "sql_verified",
      "restoreQualified": true
    }
  ]
}
```

| Field | Description |
| - | - |
| `databaseOid` | PostgreSQL object ID of the database |
| `postgresVersionNum` | PostgreSQL server version as a number, for example `160009` for 16.9 |
| `available` | The extension is present on the server. When `false`, the version, schema and `trusted` fields are `null`. |
| `defaultVersion` | Version the server would install |
| `installedVersion` | Version installed in this database, or `null` |
| `installedSchema` | Schema the extension is installed in, or `null` |
| `trusted` | Whether PostgreSQL marks the extension as trusted, or `null` when not available |
| `catalogProfile` | `sql_verified` when DBHost has verified this version on the server, otherwise `candidate` |
| `restoreQualified` | `true` when DBHost can install or upgrade this extension and keep it through a restore |

DBHost installs `citext` 1.6, `pg_trgm` 1.6 and `pgcrypto` 1.3 in the
`public` schema.

## Install or upgrade an extension

`POST /api/v1/databases/{id}/extensions/operations`

Requests one install or upgrade. The change runs as an asynchronous operation.

```http theme={null}
POST /api/v1/databases/{id}/extensions/operations
Authorization: Bearer <api-key>
Idempotency-Key: install-citext-0001
Content-Type: application/json

{"extension":"citext","kind":"install"}
```

### Headers

| Header | Required | Rule |
| - | - | - |
| `Idempotency-Key` | Yes | 8 to 128 characters from `A-Z`, `a-z`, `0-9`, `.`, `_`, `:` and `-` |

Reuse the same key after a timeout or an uncertain response. The same key
returns the same operation and never starts a second change. A new key
requests a new operation. Reusing a key with a different extension or kind is
rejected with `409 EXTENSION_MUTATION_UNAVAILABLE`.

### Request body

The body must contain exactly these two fields and nothing else.

| Field | Type | Values |
| - | - | - |
| `extension` | string | `citext`, `pg_trgm` or `pgcrypto` |
| `kind` | string | `install` or `upgrade` |

* `install` requires that the extension is not installed in the database.
* `upgrade` requires that the extension is installed in the `public` schema at
  a version other than the supported version.
* The catalog entry must be `available`, `trusted` and `restoreQualified`, and
  its `defaultVersion` must be the supported version.

Requests that do not meet these conditions, or that arrive while a restore is
running on the database, return `409 EXTENSION_MUTATION_UNAVAILABLE`.

### Response

`202 Accepted` while the operation is `queued` or `running`, or `200 OK` if it
has already reached a terminal status (for example on a replay). The
`Location` header points to the operation status route.

```json 202 Accepted theme={null}
{
  "operation": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "type": "extension",
    "status": "queued",
    "stage": "queued",
    "progress": 0,
    "databaseId": "550e8400-e29b-41d4-a716-446655440000",
    "databaseName": "my-app",
    "idempotencyKey": "<derived-key>",
    "message": null,
    "result": null,
    "error": null,
    "queuedAt": "2026-10-14T09:00:00.000Z",
    "startedAt": null,
    "completedAt": null,
    "updatedAt": "2026-10-14T09:00:00.000Z"
  }
}
```

`idempotencyKey` is a value DBHost derives from your header. It is not the
header value you sent.

## Get an extension operation

`GET /api/v1/databases/{id}/extensions/operations/{operationId}`

Returns the current state of one extension operation for this database. While
the operation is open, each read also checks for progress, so poll this route
until `status` is `succeeded`, `failed` or `canceled`.

```http theme={null}
GET /api/v1/databases/{id}/extensions/operations/{operationId}
Authorization: Bearer <api-key>
```

```json 200 OK theme={null}
{
  "operation": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "type": "extension",
    "status": "succeeded",
    "stage": "completed",
    "progress": 100,
    "databaseId": "550e8400-e29b-41d4-a716-446655440000",
    "databaseName": "my-app",
    "idempotencyKey": "<derived-key>",
    "message": "Extension change completed.",
    "result": {
      "extension": "citext",
      "installedVersion": "1.6",
      "schema": "public"
    },
    "error": null,
    "queuedAt": "2026-10-14T09:00:00.000Z",
    "startedAt": "2026-10-14T09:00:00.000Z",
    "completedAt": "2026-10-14T09:00:06.000Z",
    "updatedAt": "2026-10-14T09:00:06.000Z"
  }
}
```

`result` is set after success and contains `extension`, `installedVersion` and
`schema`. A failure before any change to the database reports
`error.code` `EXTENSION_PREPARATION_FAILED`. If the outcome cannot be confirmed
yet, the operation stays `running` until DBHost confirms it; keep polling with
the same operation ID rather than submitting a new request.

`stage` values are informational and may change; base client logic on
`status`. See [Get operation](/api-reference/operations/get) for operations in
general.

## Errors

| Status | Code | Description |
| - | - | - |
| 400 | `VALIDATION_ERROR` | Invalid database or operation ID, a body that is not exactly `extension` and `kind`, an unsupported value, or a missing or malformed `Idempotency-Key` |
| 400 | `INVALID_JSON` | Request body is not valid JSON |
| 401 | `UNAUTHORIZED` | Missing or invalid API key |
| 403 | `PLAN_API_ACCESS_REQUIRED` | The account is not on Pro or Business |
| 404 | `DATABASE_NOT_FOUND` | Database does not exist or is not visible to this key |
| 404 | `OPERATION_NOT_FOUND` | No extension operation with this ID for this database |
| 409 | `EXTENSION_MUTATION_UNAVAILABLE` | The database or extension does not meet the install or upgrade conditions, a restore is running, or the idempotency key belongs to a different request |
| 413 | `PAYLOAD_TOO_LARGE` | Request body is larger than 64 KB |
| 429 | `RATE_LIMITED` | Too many requests; retry after `Retry-After` seconds |
| 503 | `EXTENSION_CATALOG_UNAVAILABLE` | The catalog could not be read, for example because the database is not active |
| 503 | `EXTENSION_MUTATION_UNAVAILABLE` | The change could not be requested right now; retry with the same key |
| 503 | `MAINTENANCE` | Scheduled maintenance; retry after `Retry-After` seconds |

During maintenance the body is `{"error":"MAINTENANCE","message":"..."}`
instead of the standard error envelope.

The dashboard offers the same actions on the database page; see
[Databases](/dashboard/databases). The CLI equivalents are
`dbhost databases extensions <id>`,
`dbhost databases extensions install|upgrade <id> <extension> --idempotency-key KEY`
and `dbhost databases extensions status <id> <operation-id>`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.