# Extra databases

More databases for one site (any site type with a database), each with its own daily backup you can restore.

## List extra databases

`GET /extra-databases` · scope `extradb:read`

Every extra database in your account (all sites), with its state, size, paid period and last backup. Credentials are shown on the site's Extra databases page, never here.

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/extra-databases" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/extra-databases", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/extra-databases", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "id": 311,
      "site_id": 12345,
      "site_domain": "example.com",
      "name": "site12345_shop",
      "label": "shop",
      "state": "ready",
      "state_label": "Ready",
      "read_only": false,
      "size_bytes": 5242880,
      "measured_at": "2026-09-24T06:00:00Z",
      "paid_until": "2026-11-01",
      "last_paid_day": "2026-10-31",
      "in_grace": false,
      "cancel_at_end": false,
      "last_backup_at": "2026-09-24T03:10:00Z",
      "restore_pending": false,
      "error": "",
      "created_at": "2026-09-24T09:00:00Z",
      "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/"
    }
  ]
}
```

Errors: `rate_limited`, `scope_missing`, `unauthorized`

## Extra databases of a site, price and allowance

`GET /sites/{site_id}/extra-databases` · scope `extradb:read`

Whether this site can have extra databases (every site type with a database), the price per database per month (before VAT, from the live price row), how many a site may have, the site's database allowance shared by its main and extra databases, and its extra databases. Buying or renewing is done on checkout_url in the browser: the API never starts a payment.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/extra-databases" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/extra-databases", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/extra-databases", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "available": true,
    "site_has_database": true,
    "sales_open": true,
    "included_with_staff_account": false,
    "price_usd_per_month": "1.00",
    "vat_percent": 0.0,
    "months_offered": [
      1,
      3,
      6,
      12
    ],
    "max_per_site": 10,
    "backups_kept_days": 7,
    "removed_copy_kept_days": 30,
    "grace_days": 7,
    "usage": {
      "main_bytes": 20971520,
      "extra_bytes": 5242880,
      "used_bytes": 26214400,
      "limit_mib": 2048,
      "over": false
    },
    "databases": [
      {
        "id": 311,
        "site_id": 12345,
        "site_domain": "example.com",
        "name": "site12345_shop",
        "label": "shop",
        "state": "ready",
        "state_label": "Ready",
        "read_only": false,
        "size_bytes": 5242880,
        "measured_at": "2026-09-24T06:00:00Z",
        "paid_until": "2026-11-01",
        "last_paid_day": "2026-10-31",
        "in_grace": false,
        "cancel_at_end": false,
        "last_backup_at": "2026-09-24T03:10:00Z",
        "restore_pending": false,
        "error": "",
        "created_at": "2026-09-24T09:00:00Z",
        "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/"
      }
    ],
    "checkout_url": "https://app.pbn.ltd/extra-databases/site/12345/"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## One extra database

`GET /sites/{site_id}/extra-databases/{db_id}` · scope `extradb:read`

One extra database, with its restore points (its own daily backups, newest first; the id is what extradb.restore takes).

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `db_id` | path | integer | yes | The extra database id. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 311,
    "site_id": 12345,
    "site_domain": "example.com",
    "name": "site12345_shop",
    "label": "shop",
    "state": "ready",
    "state_label": "Ready",
    "read_only": false,
    "size_bytes": 5242880,
    "measured_at": "2026-09-24T06:00:00Z",
    "paid_until": "2026-11-01",
    "last_paid_day": "2026-10-31",
    "in_grace": false,
    "cancel_at_end": false,
    "last_backup_at": "2026-09-24T03:10:00Z",
    "restore_pending": false,
    "error": "",
    "created_at": "2026-09-24T09:00:00Z",
    "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/",
    "restore_points": [
      {
        "id": "site12345_shop-20260924-0310.sql.gz",
        "taken_at": 1790219400,
        "bytes": 1048576
      }
    ]
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Add an extra database

`POST /sites/{site_id}/extra-databases` · scope `extradb:write`

Adds an extra database where your account gets them free of charge (created in a minute or two). Every other account buys one in the browser: this answers 402 payment_required with details.checkout_url and the price, and never starts a payment.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `label` | body | string | yes | The name suffix: 1 to 16 lowercase letters and digits. The database is called site<site id>_<label>. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/extra-databases" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/extra-databases", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/extra-databases", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "id": 311,
    "site_id": 12345,
    "site_domain": "example.com",
    "name": "site12345_shop",
    "label": "shop",
    "state": "ready",
    "state_label": "Ready",
    "read_only": false,
    "size_bytes": 5242880,
    "measured_at": "2026-09-24T06:00:00Z",
    "paid_until": "2026-11-01",
    "last_paid_day": "2026-10-31",
    "in_grace": false,
    "cancel_at_end": false,
    "last_backup_at": "2026-09-24T03:10:00Z",
    "restore_pending": false,
    "error": "",
    "created_at": "2026-09-24T09:00:00Z",
    "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/"
  }
}
```

Errors: `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`, `unavailable`, `validation_failed`

## Cancel (or keep) an extra database at the end of its paid months

`POST /sites/{site_id}/extra-databases/{db_id}/cancel` · scope `extradb:write` · **destructive**

Cancelling stops the renewal reminders and removes the database (full copy first) when its paid months end; nothing more is charged. Send "cancel": false to keep it. Needs "confirm": true.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `db_id` | path | integer | yes | The extra database id. |
| `cancel` | body | boolean | no | true = remove it when its paid months end (a full copy is taken first); false = keep it (withdraw the cancellation). Default: `True`. |
| `confirm` | body | boolean | yes | Must be true. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id/cancel" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id/cancel", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id/cancel", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 311,
    "site_id": 12345,
    "site_domain": "example.com",
    "name": "site12345_shop",
    "label": "shop",
    "state": "ready",
    "state_label": "Ready",
    "read_only": false,
    "size_bytes": 5242880,
    "measured_at": "2026-09-24T06:00:00Z",
    "paid_until": "2026-11-01",
    "last_paid_day": "2026-10-31",
    "in_grace": false,
    "cancel_at_end": true,
    "last_backup_at": "2026-09-24T03:10:00Z",
    "restore_pending": false,
    "error": "",
    "created_at": "2026-09-24T09:00:00Z",
    "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## Remove an extra database now

`DELETE /sites/{site_id}/extra-databases/{db_id}` · scope `extradb:write` · **destructive**

Removes the database now. A full copy is taken first and kept for the days in removed_copy_kept_days (ask support to put it back). Needs "confirm": true and the database name typed exactly in "name". Paid months are not refunded.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `db_id` | path | integer | yes | The extra database id. |
| `name` | body | string | yes | The database name, typed exactly (e.g. site12345_shop) - the same check as the page. |
| `confirm` | body | boolean | yes | Must be true. |

### Example

```bash
curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id", {
  method: "DELETE",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "id": 311,
    "site_id": 12345,
    "site_domain": "example.com",
    "name": "site12345_shop",
    "label": "shop",
    "state": "deleting",
    "state_label": "Being removed",
    "read_only": false,
    "size_bytes": 5242880,
    "measured_at": "2026-09-24T06:00:00Z",
    "paid_until": "2026-11-01",
    "last_paid_day": "2026-10-31",
    "in_grace": false,
    "cancel_at_end": false,
    "last_backup_at": "2026-09-24T03:10:00Z",
    "restore_pending": false,
    "error": "",
    "created_at": "2026-09-24T09:00:00Z",
    "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## Restore an extra database from a backup

`POST /sites/{site_id}/extra-databases/{db_id}/restore` · scope `extradb:write` · **destructive**

Puts the database back as it was at the chosen daily backup: everything written since is replaced. A copy of the database as it is now is taken first. Usually done within a few minutes (you get an e-mail). Needs "confirm": true and the database name typed exactly in "name".

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `db_id` | path | integer | yes | The extra database id. |
| `backup` | body | string | yes | A restore point id from extradb.get (restore_points[].id). |
| `name` | body | string | yes | The database name, typed exactly (e.g. site12345_shop) - the same check as the page. |
| `confirm` | body | boolean | yes | Must be true. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id/restore" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id/restore", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/extra-databases/db_id/restore", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "id": 311,
    "site_id": 12345,
    "site_domain": "example.com",
    "name": "site12345_shop",
    "label": "shop",
    "state": "ready",
    "state_label": "Ready",
    "read_only": false,
    "size_bytes": 5242880,
    "measured_at": "2026-09-24T06:00:00Z",
    "paid_until": "2026-11-01",
    "last_paid_day": "2026-10-31",
    "in_grace": false,
    "cancel_at_end": false,
    "last_backup_at": "2026-09-24T03:10:00Z",
    "restore_pending": true,
    "error": "",
    "created_at": "2026-09-24T09:00:00Z",
    "manage_url": "https://app.pbn.ltd/extra-databases/site/12345/"
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`
