# API Reference

> SatsRail REST API documentation. Non-custodial Bitcoin payment infrastructure, on-chain or Lightning.

Source: https://www.satsrail.com/developers/

Generated from the OpenAPI 3.0.3 spec: https://www.satsrail.com/developers/data/satsrail-openapi.json

Base URL: `https://app.satsrail.com`

## Authentication

Send the key as `Authorization: Bearer <key>`.

- `merchant_api_key` (bearer) — Merchant secret API key: `sk_live_* / sk_test_*`
- `public_embed_key` (bearer) — Publishable embed key for client-side integrations: `pk_live_*`

## Errors

Errors share one shape. This is a `401`:

```json
{
  "error": {
    "code": "missing_token",
    "message": "Missing API token. Include 'Authorization: Bearer YOUR_API_KEY' header.",
    "status": 401,
    "request_id": "16801347-037f-4ccb-ae22-78100ecde612"
  }
}
```

## Endpoints

Merchant API:

- `POST /api/v1/m/access/verify` — verify
- `GET /api/v1/m/api_tokens/{id}/usage` — Get API token usage
- `GET /api/v1/m/catalog` — show
- `POST /api/v1/m/invoices/generate` — generate
- `GET /api/v1/m/invoices/{id}` — show
- `GET /api/v1/m/invoices/{id}/qr` — qr
- `GET /api/v1/m/invoices/{id}/status` — status
- `GET /api/v1/m/media_keys` — index
- `POST /api/v1/m/media_keys` — create
- `GET /api/v1/m/media_keys/{id}` — show
- `PATCH /api/v1/m/media_keys/{id}` — update
- `DELETE /api/v1/m/media_keys/{id}` — destroy
- `POST /api/v1/m/media_keys/{id}/clear_old_key` — clear_old_key
- `GET /api/v1/m/media_keys/{id}/key` — key
- `POST /api/v1/m/media_keys/{id}/products` — link_products
- `DELETE /api/v1/m/media_keys/{id}/products/{product_id}` — unlink_product
- `POST /api/v1/m/media_keys/{id}/rotate` — rotate
- `GET /api/v1/m/merchant` — show
- `GET /api/v1/m/orders` — index
- `POST /api/v1/m/orders` — create
- `GET /api/v1/m/orders/{id}` — show
- `PATCH /api/v1/m/orders/{id}` — update
- `DELETE /api/v1/m/orders/{id}` — destroy
- `POST /api/v1/m/payment_requests` — create
- `GET /api/v1/m/payment_requests/{id}` — show
- `GET /api/v1/m/payment_requests/{id}/status` — status
- `GET /api/v1/m/payments` — index
- `GET /api/v1/m/payments/{id}` — show
- `GET /api/v1/m/products` — List products
- `POST /api/v1/m/products` — Create a product
- `GET /api/v1/m/products/{id}` — Get a product
- `PATCH /api/v1/m/products/{id}` — Update a product
- `DELETE /api/v1/m/products/{id}` — Archive a product
- `POST /api/v1/m/products/{product_id}/media_keys` — link_to_product
- `DELETE /api/v1/m/products/{product_id}/media_keys/{id}` — unlink_from_product
- `POST /api/v1/m/sessions` — create
- `POST /api/v1/m/sessions/activate` — activate
- `GET /api/v1/m/wallets` — index
- `GET /api/v1/m/wallets/{id}` — show
- `GET /api/v1/m/webhooks` — index
- `POST /api/v1/m/webhooks` — create
- `GET /api/v1/m/webhooks/{id}` — show
- `PATCH /api/v1/m/webhooks/{id}` — update
- `DELETE /api/v1/m/webhooks/{id}` — destroy

Public API:

- `POST /api/v1/pub/access/verify` — verify
- `GET /api/v1/pub/categories` — index
- `GET /api/v1/pub/channels` — index
- `POST /api/v1/pub/checkout_sessions` — create
- `GET /api/v1/pub/creators` — index
- `GET /api/v1/pub/exchanges` — index
- `GET /api/v1/pub/subscription_plans` — index

## Merchant API

### POST /api/v1/m/access/verify

Access: verify

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Request body (`application/json`):

- `access_token` (string, required)
- `media_key_id` (string)

```json
{
  "access_token": "eyJfcmFpbHMiOnsiZGF0YSI6eyJvcmRlcl9pZCI6IjIxNGQ1YmU4LTA1ZDktNGJiNC04MjRmLWE0NmJhZTg0NDY0YyIsInByb2R1Y3RfaWQiOiI1ZmU3NjIzZi1mMzVkLTQ1MzktOWViZC1lNjUxMGUwZjMxMTUiLCJtZXJjaGFudF9pZCI6Ijk0Y2EzNDJhLTA2ODAtNGM0NS1iZjI5LTc1ZDU2MzRhMDBjMyIsImV4cCI6MTc5MDcwNzg3N30sImV4cCI6IjIwMjYtMDktMjlUMTg6NTE6MTcuOTc5WiIsInB1ciI6ImFjY2Vzc190b2tlbiJ9fQ--26622505cab02bb993c21b1af075be31c8ca75ff77574d77543a8bc6c0b6fb06",
  "media_key_id": "86471e94-02b3-4596-8b5a-fefddab8460a"
}
```

Responses:

- `200` releases a linked media key when media_key_id is named (without it, no key is returned)
- `402` refuses media keys to a v1 token, which names no product

Example `200` response:

```json
{
  "valid": true,
  "remaining_seconds": 86399,
  "server_time": 1790621477,
  "expires_at": 1790707876,
  "product_id": "5fe7623f-f35d-4539-9ebd-e6510e0f3115",
  "order_id": "214d5be8-05d9-4bb4-824f-a46bae84464c",
  "media_key_id": "86471e94-02b3-4596-8b5a-fefddab8460a",
  "key": "EXAMPLEonly-media-key-base64url-32-bytes",
  "key_fingerprint": "db5a28ed53da2b953bdc39f08b362317ca358042a6cc5bb4d507a59bbbc3e136"
}
```

### GET /api/v1/m/api_tokens/{id}/usage

Api token: Get API token usage

Returns usage statistics and rate limit configuration for a specific API token. Only active tokens owned by the authenticated merchant are accessible.

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_...`

Path parameters:

- `id` (string, required): API token UUID

Responses:

- `200` returns token usage data
- `404` token not found or not owned by merchant
- `401` unauthorized

Example `200` response:

```json
{
  "id": "string",
  "name": "string",
  "token_type": "string",
  "rpm_limit": "integer",
  "monthly_request_count": "integer",
  "last_request_counted_at": "string",
  "last_used_at": "string",
  "created_at": "string"
}
```

### GET /api/v1/m/catalog

Catalog: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer pk_live_YOUR_PUBLISHABLE_KEY`

Responses:

- `200` allows access with publishable key
- `401` requires authentication

Example `200` response:

```json
{
  "object": "catalog",
  "merchant_id": "2657842b-5006-4e6e-8488-3158e7c4806e",
  "catalog_version": "2026-03-14T03:11:55Z",
  "catalog_refresh_minutes": 30,
  "product_types": [
    {
      "id": "49708280-8d66-4cd6-9378-8e256e337160",
      "name": "Default",
      "position": 0,
      "products": [],
      "tax_ids": []
    }
  ],
  "taxes": [],
  "discounts": [],
  "livemode": true
}
```

### POST /api/v1/m/invoices/generate

Invoice: generate

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Request body (`application/json`):

- `order_id` (string, required)

```json
{
  "order_id": "cf1e996d-c303-46ea-b95e-6a7315689e70"
}
```

Responses:

- `201` returns the existing invoice
- `401` requires authentication
- `404` returns not found error

Example `201` response:

```json
{
  "object": "invoice",
  "livemode": true,
  "id": "3117b19c-4fff-4eaf-b0e4-b7406fce5d85",
  "payment_method": "lightning",
  "amount_sats": 10000,
  "status": "pending",
  "expires_at": "2026-03-14T04:11:55.467Z",
  "paid_at": null,
  "order_id": "cf1e996d-c303-46ea-b95e-6a7315689e70",
  "created_at": "2026-03-14T03:11:55.467Z",
  "updated_at": "2026-03-14T03:11:55.467Z",
  "payment_request": "lnbc10n1pj9q...",
  "payment_hash": "2eee05aec53912f0a2836d0ff78a2d934c43a2cdd02825d9e8c6fad2be86c81f",
  "expired": false,
  "time_until_expiry": 3599,
  "metadata": {
    "provider": "lnurl_demo",
    "mode": "mock"
  },
  "order": {
    "object": "order",
    "livemode": true,
    "id": "cf1e996d-c303-46ea-b95e-6a7315689e70",
    "order_number": "ORD-1773457915-4",
    "total_amount_cents": 10000,
    "tax_amount_cents": 0,
    "discount_amount_cents": 0,
    "total_amount_sats": 100000,
    "status": "pending",
    "merchant_id": "ef16bf22-8597-49af-b4df-a00ada990124",
    "channel_id": null,
    "wallet_id": "c85c322b-878c-4e67-a264-2abf023d335f",
    "discount_id": null,
    "created_at": "2026-03-14T03:11:55.464Z",
    "updated_at": "2026-03-14T03:11:55.464Z",
    "tender_type": "bitcoin",
    "items": [
      {
        "name": "Item 1",
        "price_cents": 10000,
        "qty": 1
      }
    ],
    "po_number": "ORD-1773457915-4",
    "btc_usd_price_at_creation": "100000.0",
    "btc_usd_rate_source": "coinbase",
    "currency": "usd",
    "btc_fiat_price_at_creation": "100000.0",
    "metadata": {}
  }
}
```

### GET /api/v1/m/invoices/{id}

Invoice: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `857d59e6-fe7d-4929-a3d5-3b5d0e92e8d7`

Responses:

- `200` includes order with correct JSON structure
- `401` requires authentication
- `404` prevents access to other merchant's invoices

Example `200` response:

```json
{
  "object": "invoice",
  "livemode": true,
  "id": "371bb5ac-a9a3-4251-83e7-5c5e1b9584ee",
  "payment_method": "lightning",
  "amount_sats": 10000,
  "status": "pending",
  "expires_at": "2026-03-14T04:11:55.505Z",
  "paid_at": null,
  "order_id": "70f52843-3b70-42d8-9953-111f0e840773",
  "created_at": "2026-03-14T03:11:55.505Z",
  "updated_at": "2026-03-14T03:11:55.505Z",
  "payment_request": "lnbc30n1pj9q...",
  "payment_hash": "b0df78ea711a2db64e6eb6c552ffe00b58800e8d8406bc791e7f0afcfcacee73",
  "expired": false,
  "time_until_expiry": 3599,
  "metadata": {},
  "order": {
    "object": "order",
    "livemode": true,
    "id": "70f52843-3b70-42d8-9953-111f0e840773",
    "order_number": "ORD-1773457915-6",
    "total_amount_cents": 10000,
    "tax_amount_cents": 0,
    "discount_amount_cents": 0,
    "total_amount_sats": 100000,
    "status": "pending",
    "merchant_id": "39b83ee2-3906-45c3-b7c9-d055bea10e44",
    "channel_id": null,
    "wallet_id": "c1832e80-72b1-4ae3-a49e-22e1bfffbaef",
    "discount_id": null,
    "created_at": "2026-03-14T03:11:55.502Z",
    "updated_at": "2026-03-14T03:11:55.502Z",
    "tender_type": "bitcoin",
    "items": [
      {
        "name": "Item 1",
        "price_cents": 10000,
        "qty": 1
      }
    ],
    "po_number": "ORD-1773457915-6",
    "btc_usd_price_at_creation": "100000.0",
    "btc_usd_rate_source": "coinbase",
    "currency": "usd",
    "btc_fiat_price_at_creation": "100000.0",
    "metadata": {}
  }
}
```

### GET /api/v1/m/invoices/{id}/qr

Invoice: qr

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `9f024127-e229-49a6-998c-057f832241a6`

Responses:

- `200` generates QR code with payment_request
- `401` requires authentication
- `404` prevents access to other merchant's invoice QR

A `200` response is `image/svg+xml`.

### GET /api/v1/m/invoices/{id}/status

Invoice: status

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `4b37eb1a-c52a-49e2-a327-c9d355c8d4c4`

Responses:

- `200` returns expired status
- `401` requires authentication
- `404` prevents checking status of other merchant's invoice

Example `200` response:

```json
{
  "status": "expired",
  "paid": false,
  "expired": true,
  "paid_at": null,
  "expires_at": "2026-03-14T02:11:55.758Z",
  "payment_method": "lightning",
  "payment": {
    "object": "payment",
    "livemode": true,
    "id": "fd1eba72-cc81-4267-8173-e129c6ced838",
    "amount_sats": 10000,
    "preimage": "preimage_0788fb6dc7e7b0554af6f0c90fc12b79_1",
    "confirmed_at": "2026-03-14T03:11:55.735Z",
    "order_id": "9f2a1e84-7748-41ac-aec9-44e74d44d928",
    "invoice_id": "7f0aecaf-d021-479f-a568-ff3a43ce40a0",
    "created_at": "2026-03-14T03:11:55.735Z",
    "updated_at": "2026-03-14T03:11:55.735Z",
    "channel_id": null,
    "payment_hash": "e838979f44ef5bc6e69005352881862ee56c0f4849a07062b91e5b46d93b47c1",
    "settled_at": "2026-03-14T03:11:55.735Z",
    "btc_usd_price_at_payment": "100000.0",
    "usd_value_at_payment": "10.0",
    "usd_variance_cents": -9000,
    "variance_percentage": "-90.0",
    "currency": "usd",
    "btc_fiat_price_at_payment": "100000.0",
    "fiat_value_at_payment": "10.0",
    "fiat_variance_cents": -9000,
    "confirmations": 0,
    "required_confirmations": 1,
    "metadata": {},
    "order": {
      "object": "order",
      "livemode": true,
      "id": "9f2a1e84-7748-41ac-aec9-44e74d44d928",
      "order_number": "ORD-1773457915-18",
      "total_amount_cents": 10000,
      "tax_amount_cents": 0,
      "discount_amount_cents": 0,
      "total_amount_sats": 100000,
      "status": "paid",
      "merchant_id": "cd2a76bd-53f5-4461-8129-660f18f955dc",
      "channel_id": null,
      "wallet_id": "2d425f9c-7817-4818-ab9a-26547c2b1405",
      "discount_id": null,
      "created_at": "2026-03-14T03:11:55.728Z",
      "updated_at": "2026-03-14T03:11:55.738Z",
      "tender_type": "bitcoin",
      "items": [
        {
          "name": "Item 1",
          "price_cents": 10000,
          "qty": 1
        }
      ],
      "po_number": "ORD-1773457915-18",
      "btc_usd_price_at_creation": "100000.0",
      "btc_usd_rate_source": "coinbase",
      "currency": "usd",
      "btc_fiat_price_at_creation": "100000.0",
      "metadata": {}
    }
  }
}
```

### GET /api/v1/m/media_keys

Media key: index

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Responses:

- `200` lists media keys
- `403` refuses keys that bridge an OAuth-connected assistant

Example `200` response:

```json
{
  "object": "list",
  "data": [
    {
      "object": "media_key",
      "livemode": true,
      "id": "9849fec0-44ae-40ac-99ba-293bb1ee7cff",
      "merchant_id": "95d63ac6-1625-4105-9dad-dafe5b12a9ce",
      "name": "Video key",
      "key_fingerprint": "2736be3e2cff047837483b34d47efe4ff8811da318b56856fab2a5be8c048beb",
      "rotation_pending": false,
      "browser_delivery": false,
      "product_ids": [
        "01109d09-a655-457a-ad78-37d62166ee9d"
      ],
      "metadata": {},
      "created_at": "2026-09-28T21:18:58.454Z",
      "updated_at": "2026-09-28T21:18:58.454Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total_count": 1,
    "total_pages": 1,
    "next_page": null,
    "prev_page": null
  }
}
```

### POST /api/v1/m/media_keys

Media key: create

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Request body (`application/json`):

- `media_key` (object)
- `media_key.name` (string)
- `media_key.browser_delivery` (boolean)
- `media_key.product_ids` (string[])

```json
{
  "media_key": {
    "name": "Video key",
    "browser_delivery": true,
    "product_ids": [
      "6f9d703d-09ac-4625-8cd3-3ec856f74fd2"
    ]
  }
}
```

Responses:

- `201` links products by id or slug in the same request
- `404` creates nothing when a product is unknown or belongs to another merchant

Example `201` response (lists cut to their first item):

```json
{
  "object": "media_key",
  "livemode": true,
  "id": "9e5e7fe3-e975-416f-b695-56c16241cbd3",
  "merchant_id": "96c3e316-923e-4e8a-a86b-b16ce0aafa55",
  "name": null,
  "key_fingerprint": "aa6453240caa89e1083273f6273e314c919e7fbbae567418e904c3739efe9793",
  "rotation_pending": false,
  "browser_delivery": false,
  "product_ids": [
    "2fa6f3b5-a44d-47f4-a41f-1eb520471f0a"
  ],
  "metadata": {},
  "created_at": "2026-09-28T21:18:52.050Z",
  "updated_at": "2026-09-28T21:18:52.050Z"
}
```

### GET /api/v1/m/media_keys/{id}

Media key: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `not-a-uuid`

Responses:

- `200` lists and shows the merchant's keys with their products
- `404` hides other merchants' keys

Example `200` response (lists cut to their first item):

```json
{
  "object": "media_key",
  "livemode": true,
  "id": "24da3441-b528-4024-a644-92bb5128e8b5",
  "merchant_id": "1266078b-ab5d-46b8-bf3c-24b15bdbbe5d",
  "name": "Video key",
  "key_fingerprint": "f1e9efc16cb7e202cfd05203c19925ddae2cad821dfb8c03bc941370b755591e",
  "rotation_pending": false,
  "browser_delivery": false,
  "product_ids": [
    "8c0b7625-0818-4920-a5df-c8895d606352"
  ],
  "metadata": {},
  "created_at": "2026-09-28T21:18:53.166Z",
  "updated_at": "2026-09-28T21:18:53.166Z"
}
```

### PATCH /api/v1/m/media_keys/{id}

Media key: update

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `6f517730-8e25-40b2-b024-723419718568`

Request body (`application/json`):

- `media_key` (object, required)
- `media_key.browser_delivery` (boolean, required)
- `media_key.key` (string)

```json
{
  "media_key": {
    "browser_delivery": true,
    "key": "attacker-key"
  }
}
```

Responses:

- `200` updates a media key
- `400` hides other merchants' keys and needs a body
- `403` requires manage access for key custody and links

Example `200` response:

```json
{
  "object": "media_key",
  "livemode": true,
  "id": "b1d7b020-17eb-48a4-8f83-1f221e2348d7",
  "merchant_id": "fb5c4efa-4d5b-488b-8f18-05e51b0029ab",
  "name": "Video key",
  "key_fingerprint": "a481a639027046fd752f8c52a1c96fce1cadde7be781ca18bc41ace53022f8e5",
  "rotation_pending": false,
  "browser_delivery": true,
  "product_ids": [],
  "metadata": {},
  "created_at": "2026-09-28T21:18:58.174Z",
  "updated_at": "2026-09-28T21:18:58.209Z"
}
```

### DELETE /api/v1/m/media_keys/{id}

Media key: destroy

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `5fe33e01-dcf1-485a-8a54-275baeb68180`

Responses:

- `204` destroys a key and its links

### POST /api/v1/m/media_keys/{id}/clear_old_key

Media key: clear_old_key

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `c0d8d917-e602-49cb-b93a-6ee7a3a601f6`

Request body (`application/json`):

```json
{}
```

Responses:

- `200` rotates, keeps the previous key until cleared, and notifies webhooks

Example `200` response:

```json
{
  "object": "media_key",
  "livemode": true,
  "id": "c0d8d917-e602-49cb-b93a-6ee7a3a601f6",
  "merchant_id": "bc850edb-56fb-48db-aece-bb73ad182886",
  "name": "Video key",
  "key_fingerprint": "4d04688ea3f056330e55f2714870a4be618fa6f55ef782d318118ee504da93b7",
  "rotation_pending": false,
  "browser_delivery": false,
  "product_ids": [],
  "metadata": {},
  "created_at": "2026-09-28T21:18:54.607Z",
  "updated_at": "2026-09-28T21:18:54.683Z"
}
```

### GET /api/v1/m/media_keys/{id}/key

Media key: key

Header parameters:

- `Authorization` (string, required), e.g. `Bearer pk_live_YOUR_PUBLISHABLE_KEY`

Path parameters:

- `id` (string, required), e.g. `1d266228-5d2b-484e-9369-db7f749cd313`

Responses:

- `200` returns key material only from the key endpoint
- `403` refuses publishable keys

Example `200` response:

```json
{
  "object": "media_key_secret",
  "id": "46db8bcc-10ac-47fa-98c4-d84e2a6a4aa1",
  "key": "EXAMPLEonly-media-key-base64url-32-bytes",
  "key_fingerprint": "db5a28ed53da2b953bdc39f08b362317ca358042a6cc5bb4d507a59bbbc3e136",
  "previous_key": null,
  "previous_key_fingerprint": null
}
```

### POST /api/v1/m/media_keys/{id}/products

Media key: link_products

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `45ad786b-530f-424a-9ccd-3f965fd9f11f`

Request body (`application/json`):

- `product_ids` (string[], required)

```json
{
  "product_ids": [
    "c21a8a3b-12b3-414f-b9ec-5672875d187c"
  ]
}
```

Responses:

- `200` links products to a media key
- `400` requires a bounded list

Example `200` response (lists cut to their first item):

```json
{
  "object": "media_key",
  "livemode": true,
  "id": "45ad786b-530f-424a-9ccd-3f965fd9f11f",
  "merchant_id": "93492e20-9ec7-4a07-a85f-f190b8a34dc6",
  "name": "Video key",
  "key_fingerprint": "bfcd0c3f43088f9ac2a69832fda5be910f5025efde7ba145dc7a78dafee4d217",
  "rotation_pending": false,
  "browser_delivery": false,
  "product_ids": [
    "c21a8a3b-12b3-414f-b9ec-5672875d187c"
  ],
  "metadata": {},
  "created_at": "2026-09-28T21:18:59.049Z",
  "updated_at": "2026-09-28T21:18:59.049Z"
}
```

### DELETE /api/v1/m/media_keys/{id}/products/{product_id}

Media key: unlink_product

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `e06f85b8-9462-429c-b046-34b1d912f932`
- `product_id` (string, required), e.g. `589a3798-7e6d-49ff-b96d-5c6e932a7fa2`

Responses:

- `200` links from the key side and unlinks one product

Example `200` response:

```json
{
  "object": "media_key",
  "livemode": true,
  "id": "e06f85b8-9462-429c-b046-34b1d912f932",
  "merchant_id": "2dc5660c-ee8d-4199-8062-0c36c2aa7253",
  "name": "Video key",
  "key_fingerprint": "554779381f4352fca9c28017544444c9938c988074365054679366126288dd96",
  "rotation_pending": false,
  "browser_delivery": false,
  "product_ids": [
    "265b3db6-8157-4e0d-b4f4-71020b616b74"
  ],
  "metadata": {},
  "created_at": "2026-09-28T21:18:55.184Z",
  "updated_at": "2026-09-28T21:18:55.184Z"
}
```

### POST /api/v1/m/media_keys/{id}/rotate

Media key: rotate

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `a28a806e-1927-43ad-a316-b3cd70e8c00f`

Request body (`application/json`):

```json
{}
```

Responses:

- `200` rotates a media key

Example `200` response:

```json
{
  "object": "media_key_secret",
  "id": "a28a806e-1927-43ad-a316-b3cd70e8c00f",
  "key": "EXAMPLEonly-media-key-base64url-32-bytes",
  "key_fingerprint": "db5a28ed53da2b953bdc39f08b362317ca358042a6cc5bb4d507a59bbbc3e136",
  "previous_key": "EXAMPLEonly-previous-key-base64url-32-bytes",
  "previous_key_fingerprint": "8bc62288bb5e4910fbbd3560fbb75dcd09bc9fb9e39bf29c2dc8ab16c1603ee8"
}
```

### GET /api/v1/m/merchant

Merchant: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Responses:

- `200` allows API access
- `401` returns 401 for GET /api/v1/merchant (token revoked on terminate)
- `403` returns 403 forbidden for GET /api/v1/merchant

Example `200` response:

```json
{
  "object": "merchant",
  "id": "030f701e-19ec-429d-93b0-ce36a444c5da",
  "name": "Merchant 64",
  "email": "merchant64@example.com",
  "phone": "(555) 555-0100",
  "business_license": "LICENSE-001",
  "status": "active",
  "time_zone": "UTC",
  "currency": "usd",
  "created_at": "2026-03-14T03:11:56.312Z",
  "updated_at": "2026-03-14T03:11:56.312Z",
  "tip_options": [],
  "logo_url": null,
  "pos_theme": "dark",
  "pos_enabled": true,
  "privapaid_enabled": false,
  "channels": [],
  "wallets": [
    {
      "object": "wallet",
      "id": "d91382af-e191-4117-9c57-7e8a657800a6",
      "name": "Wallet 65",
      "status": "active",
      "enabled": true,
      "position": 67,
      "created_at": "2026-03-14T03:11:56.316Z",
      "updated_at": "2026-03-14T03:11:56.316Z",
      "operational": true,
      "lightning": {
        "lightning_address": "testmerchant70@phoenix.acinq.co"
      }
    }
  ],
  "taxes": [],
  "livemode": true
}
```

### GET /api/v1/m/orders

Order: index

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_YOUR_SECRET_KEY`

Query parameters:

- `per_page` (integer), e.g. `10`
- `q[created_at_gteq]` (string), e.g. `2026-03-07T03:11:56Z`
- `q[created_at_lteq]` (string), e.g. `2026-03-14T03:11:56Z`
- `q[merchant_id_eq]` (string), e.g. `724f689e-1e73-4462-947a-d8ff6e05b89b`
- `q[payments_payment_type_eq]` (integer), e.g. `1`
- `q[status_eq]` (string), e.g. `paid`

Responses:

- `200` test token only sees test orders
- `401` rejects requests without Bearer prefix
- `403` returns 403 forbidden for GET /api/v1/orders

Example `200` response:

```json
{
  "object": "list",
  "data": [
    {
      "object": "order",
      "livemode": false,
      "id": "5ae83d27-4639-416f-b06f-8550a9c9aa90",
      "order_number": "ORD-1773457918-114",
      "total_amount_cents": 10000,
      "tax_amount_cents": 0,
      "discount_amount_cents": 0,
      "total_amount_sats": 100000,
      "status": "pending",
      "merchant_id": "8e2a625e-d5c0-4654-b655-38ce54cb1f01",
      "channel_id": null,
      "wallet_id": "8c854cc8-d7d0-49eb-9c09-593bf3452d3f",
      "discount_id": null,
      "created_at": "2026-03-14T03:11:58.346Z",
      "updated_at": "2026-03-14T03:11:58.346Z",
      "tender_type": "bitcoin",
      "items": [
        {
          "name": "Item 1",
          "price_cents": 10000,
          "qty": 1
        }
      ],
      "po_number": "ORD-1773457918-114",
      "btc_usd_price_at_creation": "100000.0",
      "btc_usd_rate_source": "coinbase",
      "currency": "usd",
      "btc_fiat_price_at_creation": "100000.0",
      "metadata": {}
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total_count": 1,
    "total_pages": 1,
    "next_page": null,
    "prev_page": null
  }
}
```

### POST /api/v1/m/orders

Order: create

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_YOUR_SECRET_KEY`

Request body (`application/json`):

- `order` (object, required)
- `order.total_amount_cents` (integer, required)
- `order.customer_email` (string, email)
- `order.items` (object[], required)
- `order.items[].name` (string, required)
- `order.items[].price_cents` (integer, required)
- `order.items[].qty` (integer, required)
- `order.tender_type` (string)
- `order.currency` (string)
- `generate_invoice` (boolean)
- `mark_as_paid` (boolean)

```json
{
  "order": {
    "total_amount_cents": 5000,
    "items": [
      {
        "name": "Widget",
        "price_cents": 5000,
        "qty": 1
      }
    ],
    "tender_type": "cash",
    "currency": "usd"
  },
  "generate_invoice": true,
  "mark_as_paid": true
}
```

Responses:

- `201` creates orders with correct livemode flag from test token
- `401` requires authentication
- `422` returns validation errors with correct structure

Example `201` response:

```json
{
  "object": "order",
  "livemode": false,
  "id": "e6e9ba68-bde9-4938-8585-6d3df8aa5c08",
  "order_number": "ORD-6AE0989B55CA9F17",
  "total_amount_cents": 5000,
  "tax_amount_cents": 0,
  "discount_amount_cents": 0,
  "total_amount_sats": 50000,
  "status": "pending",
  "merchant_id": "1ba083e9-2701-43d5-b375-cceb65ecb130",
  "channel_id": null,
  "wallet_id": "42ae58e1-896a-4476-b286-a44ad10f8986",
  "discount_id": null,
  "created_at": "2026-03-14T03:11:58.382Z",
  "updated_at": "2026-03-14T03:11:58.382Z",
  "tender_type": "bitcoin",
  "items": [
    {
      "name": "Widget",
      "price_cents": 5000,
      "qty": 1
    }
  ],
  "po_number": "ORD-6AE0989B55CA9F17",
  "btc_usd_price_at_creation": "100000.0",
  "btc_usd_rate_source": "coinbase",
  "currency": "usd",
  "btc_fiat_price_at_creation": "100000.0",
  "metadata": {},
  "invoice": {
    "object": "invoice",
    "livemode": true,
    "id": "fff4313b-f93e-4321-baa2-6fcfa17fbb18",
    "payment_method": "lightning",
    "amount_sats": 10000,
    "status": "pending",
    "expires_at": "2026-03-14T03:26:56.679Z",
    "paid_at": null,
    "order_id": "a2158e12-b22d-42b7-8217-ee90f3b4ff01",
    "created_at": "2026-03-14T03:11:56.679Z",
    "updated_at": "2026-03-14T03:11:56.679Z",
    "payment_request": "lnbc10000u1ped5914a06083b199c797e26f359383d28c9679ba5ad59f2b73cb85a69691eee4",
    "payment_hash": "f911c798e78ad250c5b093e3f0838245d6bf0a4004371cd6c44a68caf63c4c18",
    "expired": false,
    "time_until_expiry": 899,
    "metadata": {
      "mode": "mock",
      "provider": "lnurl_demo"
    }
  },
  "cash_paid_at": "2026-03-14T03:11:57.031Z"
}
```

### GET /api/v1/m/orders/{id}

Order: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `bcb3cddc-2fc3-456a-8e93-ae9dceb3d50b`

Responses:

- `200` includes livemode field in order responses
- `401` requires authentication
- `404` live token cannot access test order by ID

Example `200` response:

```json
{
  "object": "order",
  "livemode": true,
  "id": "bcb3cddc-2fc3-456a-8e93-ae9dceb3d50b",
  "order_number": "ORD-1773457918-117",
  "total_amount_cents": 10000,
  "tax_amount_cents": 0,
  "discount_amount_cents": 0,
  "total_amount_sats": 100000,
  "status": "pending",
  "merchant_id": "b9475619-be78-4b05-b040-fa85171dab1a",
  "channel_id": null,
  "wallet_id": "a405197e-6f13-42d8-bd90-901db95a8efe",
  "discount_id": null,
  "created_at": "2026-03-14T03:11:58.466Z",
  "updated_at": "2026-03-14T03:11:58.466Z",
  "tender_type": "bitcoin",
  "items": [
    {
      "name": "Item 1",
      "price_cents": 10000,
      "qty": 1
    }
  ],
  "po_number": "ORD-1773457918-117",
  "btc_usd_price_at_creation": "100000.0",
  "btc_usd_rate_source": "coinbase",
  "currency": "usd",
  "btc_fiat_price_at_creation": "100000.0",
  "metadata": {},
  "merchant": {
    "object": "merchant",
    "id": "b9475619-be78-4b05-b040-fa85171dab1a",
    "name": "Merchant 176",
    "email": "merchant176@example.com",
    "phone": "(555) 555-0100",
    "business_license": "LICENSE-001",
    "status": "active",
    "time_zone": "UTC",
    "currency": "usd",
    "created_at": "2026-03-14T03:11:58.460Z",
    "updated_at": "2026-03-14T03:11:58.460Z",
    "tip_options": [],
    "logo_url": null,
    "pos_theme": "dark",
    "pos_enabled": true,
    "privapaid_enabled": false,
    "channels": [],
    "wallets": [
      {
        "object": "wallet",
        "id": "a405197e-6f13-42d8-bd90-901db95a8efe",
        "name": "Wallet 177",
        "status": "active",
        "enabled": true,
        "position": 179,
        "created_at": "2026-03-14T03:11:58.464Z",
        "updated_at": "2026-03-14T03:11:58.464Z",
        "operational": true,
        "lightning": {
          "lightning_address": "testmerchant182@phoenix.acinq.co"
        }
      }
    ],
    "taxes": []
  },
  "cash_paid_at": "2026-03-14T03:11:57.078Z",
  "payment": {
    "object": "payment",
    "livemode": true,
    "id": "4fcd5112-5dc7-428b-a58a-b4c94c861721",
    "amount_sats": null,
    "preimage": null,
    "confirmed_at": "2026-03-14T03:11:57.078Z",
    "order_id": "62037323-3c46-46b8-a04d-5320808d2326",
    "invoice_id": null,
    "created_at": "2026-03-14T03:11:57.079Z",
    "updated_at": "2026-03-14T03:11:57.079Z",
    "channel_id": null,
    "payment_hash": null,
    "settled_at": "2026-03-14T03:11:57.078Z",
    "usd_value_at_payment": "100.0",
    "currency": "usd",
    "fiat_value_at_payment": "100.0",
    "confirmations": 0,
    "required_confirmations": 1,
    "metadata": {}
  }
}
```

### PATCH /api/v1/m/orders/{id}

Order: update

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `72aa9f7f-4751-46ad-b208-155d5e81ece3`

Request body (`application/json`):

- `order` (object, required)
- `order.total_amount_cents` (integer)
- `order.currency` (string)
- `order.customer_email` (string, email)
- `order.items` (object[])
- `order.items[].name` (string, required)
- `order.items[].price_cents` (integer, required)
- `order.items[].qty` (integer, required)
- `order.metadata` (object)
- `order.metadata.ref` (string)
- `order.metadata.note` (string)
- `order.status` (string)
- `order.wallet_id` (string)
- `order.tender_type` (string)

```json
{
  "order": {
    "total_amount_cents": 9999,
    "currency": "eur",
    "items": [
      {
        "name": "New Item",
        "price_cents": 2000,
        "qty": 1
      }
    ],
    "metadata": {
      "ref": "123",
      "note": "sneaky"
    },
    "status": "paid",
    "wallet_id": "e3099d67-a21a-47a9-ac82-dc6a0d88c168",
    "tender_type": "cash"
  }
}
```

Responses:

- `200` marks cash order as paid via status update
- `401` requires authentication
- `404` prevents updating other merchant's orders

Example `200` response:

```json
{
  "object": "order",
  "livemode": true,
  "id": "72aa9f7f-4751-46ad-b208-155d5e81ece3",
  "order_number": "ORD-1773457917-80",
  "total_amount_cents": 10000,
  "tax_amount_cents": 0,
  "discount_amount_cents": 0,
  "total_amount_sats": 100,
  "status": "paid",
  "merchant_id": "b30a692c-48f0-460f-940c-bfbca552fc17",
  "channel_id": null,
  "wallet_id": null,
  "discount_id": null,
  "created_at": "2026-03-14T03:11:57.057Z",
  "updated_at": "2026-03-14T03:11:57.064Z",
  "tender_type": "cash",
  "items": [
    {
      "name": "Item 1",
      "price_cents": 10000,
      "qty": 1
    }
  ],
  "po_number": "ORD-1773457917-80",
  "btc_usd_price_at_creation": "100000.0",
  "btc_usd_rate_source": "coinbase",
  "currency": "usd",
  "btc_fiat_price_at_creation": "100000.0",
  "metadata": {
    "ref": "123",
    "note": "sneaky"
  },
  "cash_paid_at": "2026-03-14T03:11:57.064Z",
  "merchant": {
    "object": "merchant",
    "id": "b30a692c-48f0-460f-940c-bfbca552fc17",
    "name": "Merchant 102",
    "email": "merchant102@example.com",
    "phone": "(555) 555-0100",
    "business_license": "LICENSE-001",
    "status": "active",
    "time_zone": "UTC",
    "currency": "usd",
    "created_at": "2026-03-14T03:11:57.052Z",
    "updated_at": "2026-03-14T03:11:57.052Z",
    "tip_options": [],
    "logo_url": null,
    "pos_theme": "dark",
    "pos_enabled": true,
    "privapaid_enabled": false,
    "channels": [],
    "wallets": [
      {
        "object": "wallet",
        "id": "a2507cb3-e5f7-40a4-abc7-a5efe52bb2c7",
        "name": "Wallet 104",
        "status": "active",
        "enabled": true,
        "position": 106,
        "created_at": "2026-03-14T03:11:57.056Z",
        "updated_at": "2026-03-14T03:11:57.056Z",
        "operational": true,
        "lightning": {
          "lightning_address": "testmerchant109@phoenix.acinq.co"
        }
      }
    ],
    "taxes": []
  },
  "payment": {
    "object": "payment",
    "livemode": true,
    "id": "8da30daa-e106-4496-bb1a-959d05a7c0b6",
    "amount_sats": null,
    "preimage": null,
    "confirmed_at": "2026-03-14T03:11:57.064Z",
    "order_id": "72aa9f7f-4751-46ad-b208-155d5e81ece3",
    "invoice_id": null,
    "created_at": "2026-03-14T03:11:57.065Z",
    "updated_at": "2026-03-14T03:11:57.065Z",
    "channel_id": null,
    "payment_hash": null,
    "settled_at": "2026-03-14T03:11:57.064Z",
    "usd_value_at_payment": "100.0",
    "currency": "usd",
    "fiat_value_at_payment": "100.0",
    "confirmations": 0,
    "required_confirmations": 1,
    "metadata": {}
  }
}
```

### DELETE /api/v1/m/orders/{id}

Order: destroy

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `68caf49a-1084-445f-9d68-4621a8ea8c3d`

Responses:

- `204` returns no content status
- `401` requires authentication
- `404` prevents cancelling other merchant's orders

### POST /api/v1/m/payment_requests

Payment request: create

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Request body (`application/json`):

- `payment_request` (object, required)
- `payment_request.amount_cents` (integer, required)
- `payment_request.payment_method` (string, required)
- `payment_request.description` (string, required)
- `payment_request.customer_email` (string, email)

```json
{
  "payment_request": {
    "amount_cents": 10000,
    "payment_method": "lightning",
    "description": "Test payment"
  }
}
```

Responses:

- `201` returns lightning-specific fields with correct types
- `401` requires authentication
- `422` returns error for invalid payment method

Example `201` response:

```json
{
  "object": "payment_request",
  "id": "1d36a2e1-aa00-4400-b24c-4ff451b47289",
  "payment_method": "lightning",
  "amount_sats": 10000,
  "created_at": "2026-03-14T03:11:57Z",
  "expires_at": "2026-03-14T04:11:57Z",
  "payment_request": "lnbc200n1pj9q...",
  "payment_hash": "7475af0f76afbd075f49698ea177c8b08f1cf39a15dee677064677756b3d1554"
}
```

### GET /api/v1/m/payment_requests/{id}

Payment request: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `e39ebc0b-1dbf-4386-9daa-3d6dbfb33ba9`

Responses:

- `200` returns paid status with paid_at
- `401` requires authentication
- `404` prevents access to other merchant's payment requests

Example `200` response:

```json
{
  "object": "payment_request",
  "id": "e39ebc0b-1dbf-4386-9daa-3d6dbfb33ba9",
  "payment_method": "lightning",
  "amount_sats": 10000,
  "created_at": "2026-03-14T03:11:57Z",
  "expires_at": "2026-03-14T04:11:57Z",
  "payment_request": "lnbc250n1pj9q...",
  "payment_hash": "95e9191c6bde0074eaa855d51372136cb327c1327b184aa7df85d48d9b8d64a7",
  "status": "paid",
  "paid_at": "2026-03-14T03:11:57Z",
  "amount_paid_sats": null
}
```

### GET /api/v1/m/payment_requests/{id}/status

Payment request: status

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `eeb10982-feea-478e-8bab-18a0daa27751`

Responses:

- `200` returns paid_at timestamp
- `401` requires authentication
- `404` prevents access to other merchant's payment requests

Example `200` response:

```json
{
  "id": "eeb10982-feea-478e-8bab-18a0daa27751",
  "status": "paid",
  "paid_at": "2026-03-14T03:11:57Z",
  "amount_sats": 10000,
  "amount_paid_sats": null
}
```

### GET /api/v1/m/payments

Payment: index

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Query parameters:

- `per_page` (integer), e.g. `10`
- `q[amount_sats_gteq]` (integer), e.g. `10000`
- `q[confirmed_at_gteq]` (string), e.g. `2026-03-07T03:11:57Z`
- `q[confirmed_at_lteq]` (string), e.g. `2026-03-14T03:11:57Z`
- `q[order_id_eq]` (string), e.g. `cf9959e1-fa40-46f6-9e1f-37e46eb628c7`

Responses:

- `200` only returns payments for the authenticated merchant
- `401` requires authentication

Example `200` response:

```json
{
  "object": "list",
  "data": [
    {
      "object": "payment",
      "livemode": true,
      "id": "02b006ee-4702-48fa-98f7-77b5de05058b",
      "amount_sats": 10000,
      "preimage": "preimage_9befbda3e23e242eac5fa0db8ab881ae_44",
      "confirmed_at": "2026-03-14T03:11:57.610Z",
      "order_id": "5959dde2-8bcd-43d0-bbb4-6994e6bfea8d",
      "invoice_id": "975788bb-d881-4927-8f13-5d9cfaf5a166",
      "created_at": "2026-03-14T03:11:57.611Z",
      "updated_at": "2026-03-14T03:11:57.611Z",
      "channel_id": null,
      "payment_hash": "b33ebd4143de8da227917204b4480f38e2c66a093f7d5d07b40d1a45f69d03b6",
      "settled_at": "2026-03-14T03:11:57.610Z",
      "btc_usd_price_at_payment": "100000.0",
      "usd_value_at_payment": "10.0",
      "usd_variance_cents": -9000,
      "variance_percentage": "-90.0",
      "currency": "usd",
      "btc_fiat_price_at_payment": "100000.0",
      "fiat_value_at_payment": "10.0",
      "fiat_variance_cents": -9000,
      "confirmations": 0,
      "required_confirmations": 1,
      "metadata": {},
      "order": {
        "object": "order",
        "livemode": true,
        "id": "5959dde2-8bcd-43d0-bbb4-6994e6bfea8d",
        "order_number": "ORD-1773457917-104",
        "total_amount_cents": 10000,
        "tax_amount_cents": 0,
        "discount_amount_cents": 0,
        "total_amount_sats": 100000,
        "status": "paid",
        "merchant_id": "80161a5f-b712-4072-890b-bad1dc3f9b0a",
        "channel_id": null,
        "wallet_id": "2ce37d06-aec3-408a-8d99-619a37cef996",
        "discount_id": null,
        "created_at": "2026-03-14T03:11:57.607Z",
        "updated_at": "2026-03-14T03:11:57.607Z",
        "tender_type": "bitcoin",
        "items": [
          {
            "name": "Item 1",
            "price_cents": 10000,
            "qty": 1
          }
        ],
        "po_number": "ORD-1773457917-104",
        "btc_usd_price_at_creation": "100000.0",
        "btc_usd_rate_source": "coinbase",
        "currency": "usd",
        "btc_fiat_price_at_creation": "100000.0",
        "metadata": {}
      },
      "invoice": {
        "object": "invoice",
        "livemode": true,
        "id": "975788bb-d881-4927-8f13-5d9cfaf5a166",
        "payment_method": "lightning",
        "amount_sats": 10000,
        "status": "paid",
        "expires_at": "2026-03-14T04:11:57.609Z",
        "paid_at": null,
        "order_id": "5959dde2-8bcd-43d0-bbb4-6994e6bfea8d",
        "created_at": "2026-03-14T03:11:57.609Z",
        "updated_at": "2026-03-14T03:11:57.609Z",
        "payment_request": "lnbc380n1pj9q...",
        "payment_hash": "b33ebd4143de8da227917204b4480f38e2c66a093f7d5d07b40d1a45f69d03b6",
        "expired": false,
        "time_until_expiry": 3599,
        "metadata": {}
      }
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total_count": 1,
    "total_pages": 1,
    "next_page": null,
    "prev_page": null
  }
}
```

### GET /api/v1/m/payments/{id}

Payment: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `c1ecee92-955b-4fa0-894b-20fa391c15aa`

Responses:

- `200` includes timestamp information with correct types
- `401` requires authentication
- `404` prevents access to other merchant's payments

Example `200` response:

```json
{
  "object": "payment",
  "livemode": true,
  "id": "c1ecee92-955b-4fa0-894b-20fa391c15aa",
  "amount_sats": 10000,
  "preimage": "preimage_89a7358f79c0c62abef0b1b18d504354_50",
  "confirmed_at": "2026-03-14T03:11:57.740Z",
  "order_id": "46c49a03-b055-4d86-b406-15a2c612051f",
  "invoice_id": "752fe94f-25e6-4b4a-9323-9801bda1c9b2",
  "created_at": "2026-03-14T03:11:57.741Z",
  "updated_at": "2026-03-14T03:11:57.741Z",
  "channel_id": null,
  "payment_hash": "5a63a65dcbb2d6d87015a34a6163e4864b39657d6843abbc878b4aec54b00ac6",
  "settled_at": "2026-03-14T03:11:57.740Z",
  "btc_usd_price_at_payment": "100000.0",
  "usd_value_at_payment": "10.0",
  "usd_variance_cents": -9000,
  "variance_percentage": "-90.0",
  "currency": "usd",
  "btc_fiat_price_at_payment": "100000.0",
  "fiat_value_at_payment": "10.0",
  "fiat_variance_cents": -9000,
  "confirmations": 0,
  "required_confirmations": 1,
  "metadata": {},
  "order": {
    "object": "order",
    "livemode": true,
    "id": "46c49a03-b055-4d86-b406-15a2c612051f",
    "order_number": "ORD-1773457917-110",
    "total_amount_cents": 10000,
    "tax_amount_cents": 0,
    "discount_amount_cents": 0,
    "total_amount_sats": 100000,
    "status": "paid",
    "merchant_id": "f441a972-a1cd-47b9-8738-8ce101ba00f2",
    "channel_id": null,
    "wallet_id": "9d9411f0-0fd4-4a9c-af6d-b07390826a11",
    "discount_id": null,
    "created_at": "2026-03-14T03:11:57.736Z",
    "updated_at": "2026-03-14T03:11:57.736Z",
    "tender_type": "bitcoin",
    "items": [
      {
        "name": "Item 1",
        "price_cents": 10000,
        "qty": 1
      }
    ],
    "po_number": "ORD-1773457917-110",
    "btc_usd_price_at_creation": "100000.0",
    "btc_usd_rate_source": "coinbase",
    "currency": "usd",
    "btc_fiat_price_at_creation": "100000.0",
    "metadata": {}
  },
  "invoice": {
    "object": "invoice",
    "livemode": true,
    "id": "752fe94f-25e6-4b4a-9323-9801bda1c9b2",
    "payment_method": "lightning",
    "amount_sats": 10000,
    "status": "paid",
    "expires_at": "2026-03-14T04:11:57.739Z",
    "paid_at": null,
    "order_id": "46c49a03-b055-4d86-b406-15a2c612051f",
    "created_at": "2026-03-14T03:11:57.739Z",
    "updated_at": "2026-03-14T03:11:57.739Z",
    "payment_request": "lnbc440n1pj9q...",
    "payment_hash": "5a63a65dcbb2d6d87015a34a6163e4864b39657d6843abbc878b4aec54b00ac6",
    "expired": false,
    "time_until_expiry": 3599,
    "metadata": {}
  }
}
```

### GET /api/v1/m/products

Product: List products

Returns a paginated list of products for the authenticated merchant. Supports filtering by name, status, sku, and date range.

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_...`

Query parameters:

- `per_page` (integer), e.g. `25`
- `page` (integer), e.g. `1`
- `q[name_cont]` (string): Filter by name (contains)
- `q[status_eq]` (string): Filter by status, e.g. `active`
- `q[sku_eq]` (string): Filter by SKU
- `q[created_at_gteq]` (string): Created after (ISO 8601)
- `q[created_at_lteq]` (string): Created before (ISO 8601)

Responses:

- `200` returns paginated list of products
- `401` unauthorized — missing or invalid API token

Example `200` response:

```json
{
  "object": "string",
  "data": [
    {
      "object": "string",
      "livemode": "boolean",
      "id": "string",
      "name": "string",
      "description": "string",
      "slug": "string",
      "sku": "string",
      "price_cents": "integer",
      "currency": "string",
      "status": "string",
      "position": "integer",
      "resource_type": "string",
      "image_url": "string",
      "access_duration_seconds": "integer",
      "product_type_id": "string",
      "merchant_id": "string",
      "api_token_id": "string",
      "created_at": "string",
      "updated_at": "string",
      "formatted_price": "string",
      "payment_link_url": "string",
      "metadata": {}
    }
  ],
  "meta": {
    "current_page": "integer",
    "total_pages": "integer",
    "total_count": "integer",
    "per_page": "integer"
  }
}
```

### POST /api/v1/m/products

Product: Create a product

Creates a new product for the authenticated merchant. The product is automatically associated with the API token used to create it.

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_...`

Request body (`application/json`):

- `product` (object, required)
- `product.name` (string, required)
- `product.description` (string)
- `product.price_cents` (integer, required)
- `product.sku` (string)
- `product.status` (string), one of `active`, `archived`
- `product.product_type_id` (string, uuid)
- `product.resource_type` (string)
- `product.image_url` (string)
- `product.access_duration_seconds` (integer)
- `product.metadata` (object)

```json
{
  "product": {
    "name": "Premium Content",
    "description": "Access to premium content",
    "price_cents": 5000,
    "sku": "PREM-001",
    "resource_type": "digital",
    "image_url": "https://example.com/image.png"
  }
}
```

Responses:

- `201` product created successfully
- `422` validation errors
- `401` unauthorized — missing or invalid API token

Example `201` response:

```json
{
  "object": "string",
  "livemode": "boolean",
  "id": "string",
  "name": "string",
  "description": "string",
  "slug": "string",
  "sku": "string",
  "price_cents": "integer",
  "currency": "string",
  "status": "string",
  "position": "integer",
  "resource_type": "string",
  "image_url": "string",
  "access_duration_seconds": "integer",
  "product_type_id": "string",
  "merchant_id": "string",
  "api_token_id": "string",
  "created_at": "string",
  "updated_at": "string",
  "formatted_price": "string",
  "payment_link_url": "string",
  "metadata": {}
}
```

### GET /api/v1/m/products/{id}

Product: Get a product

Returns a single product by slug.

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_...`

Path parameters:

- `id` (string, required): Product slug, e.g. `premium-content`

Responses:

- `200` returns the product
- `404` product not found
- `401` unauthorized

Example `200` response:

```json
{
  "object": "string",
  "livemode": "boolean",
  "id": "string",
  "name": "string",
  "description": "string",
  "slug": "string",
  "sku": "string",
  "price_cents": "integer",
  "currency": "string",
  "status": "string",
  "position": "integer",
  "resource_type": "string",
  "image_url": "string",
  "access_duration_seconds": "integer",
  "product_type_id": "string",
  "merchant_id": "string",
  "api_token_id": "string",
  "created_at": "string",
  "updated_at": "string",
  "formatted_price": "string",
  "payment_link_url": "string",
  "metadata": {}
}
```

### PATCH /api/v1/m/products/{id}

Product: Update a product

Updates an existing product by slug.

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_...`

Path parameters:

- `id` (string, required): Product slug, e.g. `premium-content`

Request body (`application/json`):

- `product` (object, required)
- `product.name` (string, required)
- `product.description` (string)
- `product.price_cents` (integer, required)
- `product.sku` (string)
- `product.status` (string), one of `active`, `archived`
- `product.product_type_id` (string, uuid)
- `product.resource_type` (string)
- `product.image_url` (string)
- `product.access_duration_seconds` (integer)
- `product.metadata` (object)

```json
{
  "product": {
    "name": "Premium Content",
    "description": "Access to premium content",
    "price_cents": 5000,
    "sku": "PREM-001",
    "resource_type": "digital",
    "image_url": "https://example.com/image.png"
  }
}
```

Responses:

- `200` product updated successfully
- `404` product not found
- `422` validation errors
- `401` unauthorized

Example `200` response:

```json
{
  "object": "string",
  "livemode": "boolean",
  "id": "string",
  "name": "string",
  "description": "string",
  "slug": "string",
  "sku": "string",
  "price_cents": "integer",
  "currency": "string",
  "status": "string",
  "position": "integer",
  "resource_type": "string",
  "image_url": "string",
  "access_duration_seconds": "integer",
  "product_type_id": "string",
  "merchant_id": "string",
  "api_token_id": "string",
  "created_at": "string",
  "updated_at": "string",
  "formatted_price": "string",
  "payment_link_url": "string",
  "metadata": {}
}
```

### DELETE /api/v1/m/products/{id}

Product: Archive a product

Soft-deletes (archives) a product by slug. The product is not permanently removed.

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_test_...`

Path parameters:

- `id` (string, required): Product slug, e.g. `premium-content`

Responses:

- `204` product archived successfully
- `404` product not found
- `401` unauthorized

### POST /api/v1/m/products/{product_id}/media_keys

Media key: link_to_product

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `product_id` (string, required), e.g. `2109dc69-ea0c-4f00-8b8d-6f4430f539fd`

Request body (`application/json`):

- `media_key_ids` (string[], required)

```json
{
  "media_key_ids": [
    "a1d3271a-e83a-4885-80b6-cfd4ac4767b0"
  ]
}
```

Responses:

- `200` links media keys to a product
- `404` links nothing when any referenced key or product is not the merchant's

Example `200` response:

```json
{
  "object": "media_key_links",
  "product_id": "2109dc69-ea0c-4f00-8b8d-6f4430f539fd",
  "media_key_ids": [
    "a1d3271a-e83a-4885-80b6-cfd4ac4767b0"
  ]
}
```

### DELETE /api/v1/m/products/{product_id}/media_keys/{id}

Media key: unlink_from_product

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `067f1da0-3050-4863-9377-2e0036c5bfc4`
- `product_id` (string, required), e.g. `prod_72b688d24adb3768fc373240`

Responses:

- `200` links one product to many keys from the product side

Example `200` response:

```json
{
  "object": "media_key_links",
  "product_id": "42d59560-0bba-49f2-96c1-1caac37a60de",
  "media_key_ids": []
}
```

### POST /api/v1/m/sessions

Session: create

Request body (`application/json`):

- `email` (string, required)
- `password` (string, required)

```json
{
  "email": "user28@example.com",
  "password": "password123456"
}
```

Responses:

- `200` resets failed attempts on successful login
- `400` returns 400 for missing credentials
- `401` increments failed attempts on wrong password
- `423` rejects valid credentials when account is locked

Example `200` response:

```json
{
  "session_token": "eyJfcmFpbHMiOnsiZGF0YSI6eyJ1c2VyX2lkIjoiYjdjNDA4NGYtOWYxYi00MjRmLWE4ODMtZjQ2NWU1ZmRhZTE0IiwiZXhwIjoxNzczNDU5NzE4fSwicHVyIjoicG9zX3Nlc3Npb24ifX0=--dfc82ff6369ff136daace5c67630f30259ad9f92",
  "merchants": [
    {
      "id": "c0326624-c893-4701-a99e-bea7fa0b67ca",
      "name": "Merchant 153",
      "logo_url": null,
      "currency": "usd",
      "role": "owner"
    }
  ]
}
```

### POST /api/v1/m/sessions/activate

Session: activate

Request body (`application/json`):

- `session_token` (string, required)
- `merchant_id` (string, required)

```json
{
  "session_token": "eyJfcmFpbHMiOnsiZGF0YSI6eyJ1c2VyX2lkIjoiOTZjZDY3MzEtZGJkZi00MWEwLWEwNGYtNWQ1MjgzZTk0ZTU5IiwiZXhwIjoxNzczNDU5NzE4fSwicHVyIjoicG9zX3Nlc3Npb24ifX0=--981626a7484c548066b74e2ecd5b83b6cd664e53",
  "merchant_id": "c378f7fe-c4ab-47b4-9660-48865734bcfb"
}
```

Responses:

- `200` still allows activation (POS available to all plans)
- `400` returns 400 for missing params
- `401` returns 401 for invalid session token
- `403` returns 403 for non-operational merchant
- `404` returns 404 for merchant with no active API key

Example `200` response:

```json
{
  "api_key": "sk_live_YOUR_SECRET_KEY",
  "merchant": {
    "id": "c378f7fe-c4ab-47b4-9660-48865734bcfb",
    "name": "Merchant 165",
    "currency": "usd"
  }
}
```

### GET /api/v1/m/wallets

Wallet: index

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Query parameters:

- `q[enabled_eq]` (string), e.g. `true`
- `q[merchant_id_eq]` (string), e.g. `d81b5fe2-2df5-4709-ab99-b8a7e0c2b322`

Responses:

- `200` includes lightning section with lightning_address
- `401` requires authentication

Example `200` response:

```json
{
  "object": "list",
  "data": [
    {
      "object": "wallet",
      "id": "0251fcd0-5751-4a9c-b3f1-101117555196",
      "name": "Wallet 189",
      "status": "active",
      "enabled": true,
      "position": 191,
      "created_at": "2026-03-14T03:11:58.590Z",
      "updated_at": "2026-03-14T03:11:58.590Z",
      "operational": true,
      "lightning": {
        "lightning_address": "shop@phoenix.acinq.co"
      }
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total_count": 1,
    "total_pages": 1,
    "next_page": null,
    "prev_page": null
  }
}
```

### GET /api/v1/m/wallets/{id}

Wallet: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `21a10ea0-c9fe-406f-a02e-713ac4ef43d8`

Responses:

- `200` does not include display_name, status_color, or next_payment_address
- `401` requires authentication
- `404` returns 404 with correct error structure for non-existent wallet

Example `200` response:

```json
{
  "object": "wallet",
  "id": "b93639c1-8515-4935-adf7-020415a44411",
  "name": "Wallet 193",
  "status": "active",
  "enabled": true,
  "position": 195,
  "created_at": "2026-03-14T03:11:58.637Z",
  "updated_at": "2026-03-14T03:11:58.637Z",
  "operational": true,
  "lightning": {
    "lightning_address": "testmerchant198@phoenix.acinq.co"
  },
  "last_checked_at": null,
  "last_transaction_at": null,
  "last_error": null,
  "consecutive_failures": 0
}
```

### GET /api/v1/m/webhooks

Webhook: index

Header parameters:

- `Authorization` (string, required), e.g. `sk_live_YOUR_SECRET_KEY`

Query parameters:

- `q[active_eq]` (string), e.g. `true`
- `q[merchant_id_eq]` (string), e.g. `40cba8fd-87fc-45db-bb45-2de7fdceab8e`

Responses:

- `200` only returns webhooks for the authenticated merchant
- `401` rejects requests without Bearer prefix

Example `200` response (lists cut to their first item):

```json
{
  "object": "list",
  "data": [
    {
      "object": "webhook",
      "livemode": true,
      "id": "f0bdc2b8-8f71-4dcd-a4ad-dc81929e651f",
      "url": "https://example.com/webhooks",
      "description": "Test Webhook",
      "events": [
        "order.created"
      ],
      "active": true,
      "success_count": 0,
      "failure_count": 0,
      "last_triggered_at": null,
      "created_at": "2026-03-14T03:11:58.971Z",
      "updated_at": "2026-03-14T03:11:58.971Z",
      "health_percentage": 100,
      "metadata": {}
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total_count": 1,
    "total_pages": 1,
    "next_page": null,
    "prev_page": null
  },
  "available_events": [
    "order.created"
  ]
}
```

### POST /api/v1/m/webhooks

Webhook: create

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Request body (`application/json`):

- `webhook` (object, required)
- `webhook.url` (string, required)
- `webhook.events` (string[], required)
- `webhook.description` (string)

```json
{
  "webhook": {
    "url": "https://example.com/hook",
    "events": [
      "order.created"
    ],
    "description": "My test webhook"
  }
}
```

Responses:

- `201` creates a webhook with all available event types
- `401` requires authentication
- `422` returns validation errors for invalid url format

Example `201` response (lists cut to their first item):

```json
{
  "object": "webhook",
  "livemode": true,
  "id": "db705776-1673-4757-8732-fa46cd868d1c",
  "url": "https://example.com/hook",
  "description": null,
  "events": [
    "order.created"
  ],
  "active": true,
  "success_count": 0,
  "failure_count": 0,
  "last_triggered_at": null,
  "created_at": "2026-03-14T03:11:59.075Z",
  "updated_at": "2026-03-14T03:11:59.075Z",
  "health_percentage": 100,
  "metadata": {},
  "secret_key": "whsec_YOUR_WEBHOOK_SECRET"
}
```

### GET /api/v1/m/webhooks/{id}

Webhook: show

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `e3ae1c97-c3eb-48bc-9fcd-5d48065884db`

Responses:

- `200` does not expose secret_key on show
- `401` requires authentication
- `404` prevents access to other merchant's webhooks

Example `200` response (lists cut to their first item):

```json
{
  "object": "webhook",
  "livemode": true,
  "id": "982ebe1a-819e-4d95-981e-3c77eca16d23",
  "url": "https://example.com/webhooks",
  "description": "Test Webhook",
  "events": [
    "order.created"
  ],
  "active": true,
  "success_count": 0,
  "failure_count": 0,
  "last_triggered_at": null,
  "created_at": "2026-03-14T03:11:58.995Z",
  "updated_at": "2026-03-14T03:11:58.995Z",
  "health_percentage": 100,
  "metadata": {}
}
```

### PATCH /api/v1/m/webhooks/{id}

Webhook: update

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `07aed364-1bd1-4311-8872-a9694627b335`

Request body (`application/json`):

- `webhook` (object, required)
- `webhook.url` (string)
- `webhook.events` (string[])
- `webhook.active` (boolean)
- `webhook.description` (string)

```json
{
  "webhook": {
    "url": "https://updated.example.com/hook",
    "events": [
      "payment.confirmed"
    ],
    "active": false,
    "description": "Updated"
  }
}
```

Responses:

- `200` does not expose secret_key on update
- `401` requires authentication
- `404` prevents updating other merchant's webhooks
- `422` returns validation errors for invalid events

Example `200` response (lists cut to their first item):

```json
{
  "object": "webhook",
  "livemode": true,
  "id": "1ace2467-8c77-4f12-8139-54bc9fb206c8",
  "url": "https://example.com/webhooks",
  "description": "Updated",
  "events": [
    "order.created"
  ],
  "active": true,
  "success_count": 0,
  "failure_count": 0,
  "last_triggered_at": null,
  "created_at": "2026-03-14T03:11:59.197Z",
  "updated_at": "2026-03-14T03:11:59.202Z",
  "health_percentage": 100,
  "metadata": {}
}
```

### DELETE /api/v1/m/webhooks/{id}

Webhook: destroy

Header parameters:

- `Authorization` (string, required), e.g. `Bearer sk_live_YOUR_SECRET_KEY`

Path parameters:

- `id` (string, required), e.g. `e885ab37-4295-41e4-bebc-3287142a555f`

Responses:

- `204` returns no content status
- `401` requires authentication
- `404` prevents deleting other merchant's webhooks

## Public API

### POST /api/v1/pub/access/verify

Access: verify

Header parameters:

- `Authorization` (string, required), e.g. `Bearer pk_live_YOUR_PUBLISHABLE_KEY`

Request body (`application/json`):

- `access_token` (string)

```json
{
  "access_token": "eyJfcmFpbHMiOnsiZGF0YSI6eyJwYXltZW50X2lkIjoicGF5LXRlc3QtdXVpZCIsInJlc291cmNlX2lkIjoicHJvZF9hYmMiLCJyZXNvdXJjZV90eXBlIjoic3RyZWFtIiwiZXhwIjoxNzczNDU4NTE5fSwiZXhwIjoiMjAyNi0wMy0xNFQwMzoyMTo1OS4zMDdaIiwicHVyIjoiYWNjZXNzX3Rva2VuIn19--1c6d12e1f86a6bba3be28f425669b2a0adf2449b"
}
```

Responses:

- `200` accepts token via X-Access-Token header
- `400` returns 400 when access_token param is missing
- `401` returns 401 without a publishable key
- `402` returns 402 for a tampered token
- `403` returns 403 when merchant has privapaid disabled

Example `200` response:

```json
{
  "valid": true,
  "remaining_seconds": 600,
  "resource_id": "prod_abc",
  "resource_type": "stream"
}
```

### GET /api/v1/pub/categories

Category: index

Responses:

- `200` sets public cache headers

Example `200` response (lists cut to their first item):

```json
{
  "object": "list",
  "data": [
    "Film & Animation"
  ]
}
```

### GET /api/v1/pub/channels

Channel: index

Query parameters:

- `category` (string), e.g. `Gaming`
- `nsfw` (string), e.g. `true`
- `page` (integer), e.g. `1`
- `per_page` (integer), e.g. `2`
- `q` (string), e.g. `Gaming`

Responses:

- `200` defaults to 24 per page

Example `200` response:

```json
{
  "object": "list",
  "data": [
    {
      "slug": "chan-1",
      "name": "Channel 15",
      "bio": "A test channel for streaming.",
      "videos_count": 0,
      "nsfw": false,
      "theme": "rose",
      "category": "Entertainment",
      "profile_image_url": null,
      "social_links": {},
      "merchant_name": "Merchant 250",
      "country": "IQ",
      "country_name": "Country 224",
      "language": "en",
      "currency": "usd"
    }
  ],
  "meta": {
    "current_page": 1,
    "total_pages": 1,
    "total_count": 1,
    "per_page": 24
  }
}
```

### POST /api/v1/pub/checkout_sessions

Checkout session: create

Header parameters:

- `Authorization` (string, required), e.g. `Bearer pk_live_YOUR_PUBLISHABLE_KEY`

Request body (`application/json`):

- `checkout_session` (object, required)
- `checkout_session.amount_cents` (integer)
- `checkout_session.currency` (string)
- `checkout_session.product_id` (string)
- `checkout_session.customer_email` (string)
- `checkout_session.customer_name` (string)
- `checkout_session.metadata` (object)
- `checkout_session.metadata.order_ref` (string, required)
- `checkout_session.metadata.source` (string, required)

```json
{
  "checkout_session": {
    "amount_cents": 5000,
    "currency": "usd",
    "product_id": "prod_32bf92c24dbad85569bd9acd",
    "customer_email": "buyer@example.com",
    "customer_name": "Alice Smith",
    "metadata": {
      "order_ref": "MY-123",
      "source": "website"
    }
  }
}
```

Responses:

- `201` accepts metadata hash
- `401` requires authentication
- `422` returns validation error for invalid email

Example `201` response:

```json
{
  "object": "checkout_session",
  "livemode": true,
  "id": "1ba2644d-ca41-479d-9689-db2e0e0b4657",
  "token": "cs_Ku60a_FLU38Nls2UOZyfvdjIs39RzcItqAiV0NK9IsQ",
  "checkout_url": "http://www.example.com/checkout/cs_Ku60a_FLU38Nls2UOZyfvdjIs39RzcItqAiV0NK9IsQ",
  "expires_at": "2026-03-14T03:26:55Z",
  "amount_cents": 5000,
  "tax_amount_cents": 0,
  "tax_details": [],
  "currency": "usd",
  "status": "pending",
  "customer_email": null,
  "customer_name": null,
  "customer_phone": null,
  "customer_address": null,
  "checkout_mode": "iframe",
  "checkout_theme": "light",
  "checkout_accent_color": "#e8b53c",
  "privapaid_enabled": false,
  "metadata": {
    "order_ref": "MY-123",
    "source": "website"
  }
}
```

### GET /api/v1/pub/creators

Creator: index

Responses:

- `200` returns the same data as the channels endpoint

Example `200` response:

```json
{
  "object": "list",
  "data": [
    {
      "slug": "compat-test",
      "name": "Channel 16",
      "bio": "A test channel for streaming.",
      "videos_count": 0,
      "nsfw": false,
      "theme": "rose",
      "category": "Entertainment",
      "profile_image_url": null,
      "social_links": {},
      "merchant_name": "Merchant 251",
      "country": "IR",
      "country_name": "Country 225",
      "language": "en",
      "currency": "usd"
    }
  ],
  "meta": {
    "current_page": 1,
    "total_pages": 1,
    "total_count": 1,
    "per_page": 24
  }
}
```

### GET /api/v1/pub/exchanges

Exchange: index

Responses:

- `200` invalidates cache when an exchange_country is destroyed

Example `200` response (lists cut to their first item):

```json
{
  "exchanges": [
    {
      "id": "02f4199f-a5d2-4f93-ae93-581f4bafe650",
      "name": "Buda.com",
      "url": "https://buda.com",
      "promoted": true,
      "min_transaction_sats": null,
      "notes": null,
      "logo_url": null,
      "countries": [
        {
          "id": "5f355627-8c07-4c5e-a8a7-2f05fe7be640",
          "name": "Chile",
          "iso_code": "CL"
        }
      ]
    }
  ]
}
```

### GET /api/v1/pub/subscription_plans

Subscription plan: index

Responses:

- `200` includes features as a hash

Example `200` response (lists cut to their first item):

```json
{
  "plans": [
    {
      "slug": "free",
      "name": "Free",
      "description": "Get started with Bitcoin Lightning payments — no credit card required",
      "price_cents": 0,
      "price_display": "$0.00/month",
      "interval": "month",
      "max_monthly_volume_cents": 50000,
      "features": {
        "basic_reports": true,
        "accounting:reports": false,
        "email_support": true,
        "webhook_endpoints": 0,
        "team_members": 3,
        "max_monthly_transactions": 100,
        "pos_access": true,
        "privapaid_access": true,
        "max_channels": 1,
        "report_history_days": 30,
        "support_tier": "email"
      },
      "feature_labels": [
        "Basic reports"
      ],
      "rate_limits": {
        "per_api_key_per_minute": 20,
        "per_api_key_per_hour": 200,
        "delivery_per_order_per_hour": 10,
        "unauthenticated_per_minute": 20
      }
    }
  ]
}
```
