# SatsRail Developers > REST API reference and integration guides for SatsRail — non-custodial Bitcoin payment infrastructure, on-chain or Lightning. - The API is served from `https://app.satsrail.com`. Merchant endpoints are under `/api/v1/m/` and take a secret key (`sk_live_…`, or `sk_test_…` in test mode); public endpoints are under `/api/v1/pub/` and take a publishable key. - The hosted MCP server for AI agents is `https://app.satsrail.com/api/v1/mcp`. - The OpenAPI spec is at https://www.satsrail.com/developers/data/satsrail-openapi.json. - Every page is also Markdown: at the page's URL plus `index.md`, or at the page's own URL when the request says `Accept: text/markdown`. - Every page in one file: https://www.satsrail.com/developers/llms-full.txt - For the product itself, see https://www.satsrail.com/llms.txt. # 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 `. - `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 } } ] } ``` --- # AI Agents > Let AI agents accept Bitcoin payments programmatically. MCP server, zero-browser payment flow, and agent-friendly API for Claude, Cursor, and any LLM tool. Source: https://www.satsrail.com/developers/guides/ai-agents/ The only payment processor AI agents can use natively. No browser. No forms. Just an API call and a Lightning invoice string. ## Why Lightning Is Perfect for AI Agents ##### Credit Cards - Require browser forms & 3D Secure - PCI compliance burden - Card numbers agents can't handle safely - Chargebacks and fraud checks - Banks can freeze accounts anytime ##### Lightning (SatsRail) - One API call → invoice string - No browser, no forms, no redirects - Instant settlement - No chargebacks — payment is final - Non-custodial — no account freezes ## MCP Server The SatsRail MCP server gives any AI agent with [Model Context Protocol](https://modelcontextprotocol.io/) support the ability to create orders, generate invoices, and check payment status. ##### Connect to the hosted server SatsRail hosts the server at `https://app.satsrail.com/api/v1/mcp`. It speaks Streamable HTTP (one JSON-RPC message per POST), so there is nothing to install: point your client at the URL and authenticate with a secret key. ``` { "mcpServers": { "satsrail": { "type": "http", "url": "https://app.satsrail.com/api/v1/mcp", "headers": { "Authorization": "Bearer sk_test_your_key_here" } } } } ``` Works in Claude Code, Claude Desktop, Cursor, Windsurf and any Streamable-HTTP MCP client. Stdio-only clients can bridge to the URL with `mcp-remote`. No key to paste? A client that supports OAuth connects with the URL alone: you sign in, pick the business and approve. Those connections manage your catalog and payment links only, and you can revoke them any time under Settings → AI Assistants. ## Available Tools The hosted server exposes 43 tools: orders, invoices, payments, checkout, payment links, products and the catalog, webhooks, wallets and account data. These are the core payment tools; `tools/list` returns them all: | Tool | Description | | --- | --- | | `create_order` | Create a payment order with optional auto-generated Lightning invoice | | `get_order` | Get order details by ID (expandable: invoice, payment, merchant) | | `list_orders` | List orders with optional status filter | | `cancel_order` | Cancel a pending order | | `get_invoice` | Get invoice details including bolt11 Lightning string | | `check_invoice_status` | Real-time payment status check against the Lightning node | | `generate_invoice` | Generate a new invoice for an existing order | | `list_payments` | List confirmed payments with optional date range filter | | `get_payment` | Get payment details | | `create_checkout_session` | Create a hosted checkout session with redirect URL | | `create_payment_link` | Create a reusable payment link (`/pay/…`) from a name and a price, to send a customer by chat, email or text | | `get_merchant` | Get the current merchant's profile and settings | | `list_wallets` | List connected wallets | ## Agent Payment Flow A complete payment takes 3 steps — no browser involved at any point. ##### 1. Create Order Agent calls `create_order` with amount and description. SatsRail returns an order with a bolt11 Lightning invoice string. ##### 2. Customer Pays Agent presents the bolt11 string or a QR code to the customer. Customer pays with any Lightning wallet. ##### 3. Confirm Agent calls `check_invoice_status` to verify payment, or listens for a webhook. Done. ##### Example Conversation User "Charge me $25 for the monthly subscription" Agent *→ calls create_order(amount_cents: 2500, generate_invoice: true)* Agent "Here's your Lightning invoice. Scan this QR code or copy the payment string:" lnbc250u1pj...kqq5yxmetu User "Paid!" Agent *→ calls check_invoice_status(invoice_id: "...")* Agent "Payment confirmed! Your subscription is active. ⚡" ## Payment Links for Agents A payment link (`https://www.satsrail.com/pay/prod_…`) opens the hosted checkout for one product at one price. It never expires and can be paid any number of times. That makes it the thing an agent hands to a person: - **Selling.** The merchant's agent calls `create_payment_link` with a name and a price and gets back `payment_link_url` to send the customer. Every product the API returns carries its `payment_link_url` too (null while the product is inactive). - **Buying for someone.** An agent that needs to pay a SatsRail merchant for the person it works for doesn't need a wallet, a key or a card: it sends that person the merchant's payment link, and they pay from their own wallet. The money goes straight to the merchant; the agent never holds funds. Before forwarding a link, read it: the same URL with `.json` (or `Accept: application/json`) describes it and opens no checkout. No key is needed. ``` GET https://www.satsrail.com/pay/prod_4f1c9e2a7b3d8e6f5a1c0b9d.json { "object": "payment_link", "url": "https://www.satsrail.com/pay/prod_4f1c9e2a7b3d8e6f5a1c0b9d", "name": "Sunset kayak tour", "amount_cents": 4500, "currency": "usd", "formatted_amount": "$45.00", "merchant": { "name": "Ember Tours" }, "payable": true, "payment_methods": ["lightning"], "how_to_pay": "Open the url in a browser and pay from a bitcoin wallet. ..." } ``` `payable` is false while the merchant has no wallet that can take the price; say so rather than send a link that shows an unavailable page. An inactive or unknown link answers `404`. ##### Example Conversation User "Book me the sunset kayak tour on Saturday." Agent *→ finds the tour's payment link on the operator's site, reads it with .json* Agent "Ember Tours charges $45.00 for the Sunset kayak tour. Pay here and they'll confirm your spot: https://www.satsrail.com/pay/prod_4f1c…" ## Direct REST API Don't need MCP? Any agent that can make HTTP requests can use SatsRail directly. The entire flow is JSON in, JSON out — no redirects, no HTML. ##### Create order + invoice in one call ``` curl -X POST https://app.satsrail.com/api/v1/m/orders \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -d '{ "order": { "total_amount_cents": 2500, "currency": "usd" }, "generate_invoice": true, "payment_method": "lightning" }' ``` ##### Check payment status ``` curl https://app.satsrail.com/api/v1/m/invoices/{invoice_id}/status \ -H "Authorization: Bearer sk_test_..." ``` [Full API Reference →](https://www.satsrail.com/developers/) ## Use Cases ##### SaaS & API Billing Agents that sell access to services and collect payment in the conversation. Per-call, per-session, or per-task billing with no checkout page. ##### Agent-Generated Invoicing Agents that create fresh invoices when payment is due — milestone billing, on-demand charges, or periodic collections. ##### Multi-Merchant Platforms Build agent-powered marketplaces where AI handles the checkout flow across multiple merchants. ##### Invoicing Bots Agents that send invoices, track payments, and follow up — from Slack, Discord, Telegram, or any chat platform. ## Configuration | Setting | Required | Description | | --- | --- | --- | | URL | Yes | `https://app.satsrail.com/api/v1/mcp`, over Streamable HTTP. | | `Authorization` header | Yes, unless you use OAuth | `Bearer sk_test_*` for testing, `Bearer sk_live_*` for production. | | OAuth | No | Discovery starts at `https://app.satsrail.com/.well-known/oauth-protected-resource/api/v1/mcp`. An OAuth connection gets the catalog tools only. | ## Build the future of AI payments Get your API key and let your agents start accepting Bitcoin in minutes. [Get Your API Key →](https://satsrail.com/users/sign_up) [MCP Server](https://www.satsrail.com/mcp/) --- # Checkout Experience > Visual guide to the SatsRail checkout flow. See what your customers experience in both embedded and new tab modes — from customer info collection through payment confirmation. Source: https://www.satsrail.com/developers/guides/checkout-experience/ What your customers see at each step of the payment flow. ## Two Display Modes SatsRail checkout pages adapt their layout based on your merchant's **checkout mode** setting. Each mode is designed for its context: ###### Embedded Mode Opens in an overlay on the merchant's page Compact, space-efficient layout. No popup blockers. Works on desktop and mobile. ###### New Tab Mode Opens in a full browser tab Two-column layout with product info on the left and payment details on the right. > Set the checkout mode in your **Merchant Settings** or override it per embed button with the `data-mode` attribute. ## Your Brand Merchants choose three things in **Checkout Settings → Appearance**, with a live preview: a **light or dark theme**, one **accent color**, and the **logo** uploaded in General settings. The accent colors the main button and small highlights, and text on it switches between white and black so it stays readable. A color that would disappear against the chosen theme is refused when saving. Everything else is the same for every merchant: the layout, the QR code (always black on white, so every wallet can read it), the amount and fee lines, the paid and expired colors, and "Powered by SatsRail". Creating a checkout session returns `checkout_theme` (`light` or `dark`) and `checkout_accent_color` (a `#rrggbb` hex, SatsRail gold unless the merchant chose one), so your own page can match the checkout it opens. ## Embedded Mode Compact checkout embedded in an overlay iframe on the merchant's page. ###### 1. Customer Info ![Embedded - Collect customer info](https://www.satsrail.com/images/docs/checkout/popup-collect-info.png) Collects email, name, phone, or address based on merchant settings. ###### 2. Payment Method ![Embedded - Select payment method](https://www.satsrail.com/images/docs/checkout/popup-select-method.png) Shown when the merchant offers both Lightning and on-chain (early access) and the amount reaches the on-chain minimum. Otherwise this step is skipped. ###### 3. QR Code ![Embedded - QR code payment](https://www.satsrail.com/images/docs/checkout/popup-qr-code.png) On a computer, scan the QR code with any Lightning or Bitcoin wallet. On a phone, **Open in wallet** comes first and hands the invoice to the wallet app on that phone. Real-time status via WebSocket. ###### Confirmed ![Embedded - Payment confirmed](https://www.satsrail.com/images/docs/checkout/popup-confirmation.png) Payment received. Customer is auto-redirected to your success URL. ## New Tab Mode Spacious two-column layout for the full browser experience. ###### 1. Customer Info ![New Tab - Collect customer info](https://www.satsrail.com/images/docs/checkout/newtab-collect-info.png) Amount displayed on the left, form fields on the right. ###### 2. Payment Method ![New Tab - Select payment method](https://www.satsrail.com/images/docs/checkout/newtab-select-method.png) Price on the left, payment method options on the right. Shown when both Lightning and on-chain (early access) are offered; otherwise this step is skipped. ###### 3. QR Code ![New Tab - QR code payment](https://www.satsrail.com/images/docs/checkout/newtab-qr-code.png) Amount and invoice details on the left, large QR code on the right. ###### Confirmed ![New Tab - Payment confirmed](https://www.satsrail.com/images/docs/checkout/newtab-confirmation.png) Wider confirmation card with order details and email receipt notice. ## Steps Are Adaptive Not every checkout has all four steps. The flow adapts automatically: | Step | When It Appears | When It's Skipped | | --- | --- | --- | | **1. Customer Info** | Merchant has customer fields configured (email, name, phone, address) | No customer fields configured, or info pre-filled via API | | **2. Payment Method** | The merchant offers both Lightning and on-chain (early access) and the amount reaches the on-chain minimum | Only one method can take the payment (an on-chain-only checkout goes straight to its screen), or the method is pre-selected via API | | **3. QR Code** | Always | Never skipped | | **4. Confirmation** | Always | Never skipped | ## Try it yourself See the checkout in action on our demo site, or integrate it into your own. [Live Demos →](https://www.satsrail.com/demos/) [Embed Button →](https://www.satsrail.com/developers/guides/embed/) --- # Checkout Flow > Understand the complete data flow from checkout session creation through payment confirmation. Table relationships, status transitions, and every database write explained. Source: https://www.satsrail.com/developers/guides/checkout-flow/ How data moves from session creation to payment confirmation. ## Table Relationships ``` Merchant │ has_many ▼ CheckoutSession ──belongs_to──▶ Order ──has_one──▶ Invoice ──has_one──▶ Payment │ │ │ │ │ token │ order_number │ payment_request │ preimage │ amount_cents │ total_amount_sats │ payment_hash │ amount_sats │ customer_* │ items (jsonb) │ │ btc_fiat_price │ payment_method │ metadata (jsonb) │ │ fiat_variance │ success_url / cancel_url │ │ │ confirmed_at │ expires_at (15 min) │ │ expires_at │ │ │ │ │ └─ UI session (disposable) └─ Business record └─ Network request └─ Settlement proof (permanent) (per attempt) (immutable) ``` ## Why Four Tables? Each table captures a distinct stage with a different lifecycle: | Table | Purpose | Lifecycle | | --- | --- | --- | | **CheckoutSession** | Temporary UI session. Captures customer intent and info before any money is involved. Expires in 15 minutes. | Disposable | | **Order** | Permanent business record. The merchant's receipt — tied to items, amounts, and a wallet. Feeds reports, dashboards, and refunds. | Permanent | | **Invoice** | Payment-network request. Holds the BOLT-11 string (Lightning) plus confirmation tracking. One Order may need a new Invoice if the first expires. | Per attempt | | **Payment** | Proof of settlement. Records the exact BTC/fiat price at confirmation, the preimage, and calculates price variance. | Immutable | > **Key insight:** Separating Invoice from Order allows a single Order to survive invoice expiration. If a Lightning invoice expires after 1 hour, a new Invoice can be generated for the same Order with an updated BTC price — without losing the order context. ## Entry Points Three ways to start a checkout, one unified flow: ###### API `POST /api/v1/checkout_sessions` Server-side integration. Returns checkout_url to redirect customer. ###### Payment Link `GET /pay/:slug` Zero-code. Share a URL via email, social media, or QR code. ###### Embed Button `data-key="pk_live_..."` JavaScript widget. Opens checkout overlay from any webpage. All three converge at the same CheckoutController, entering the 3-step pipeline below. ## Step-by-Step Data Flow ##### 1. Session Created API, payment link, or embed widget creates a CheckoutSession. Customer is redirected to the checkout page. WRITE: `checkout_sessions` token, amount_cents, currency, merchant_id, product_id, success_url, cancel_url, metadata, expires_at ##### 2. Collect Customer Info optional If the merchant requires customer info collection and it was not pre-filled via API, the customer fills out a form. WRITE: `checkout_sessions` customer_email, customer_name, customer_phone, customer_address ##### 4. Order + Invoice Created On the first visit to the QR code page, the system creates an Order, links it to the session, converts the fiat amount to sats at the current BTC price, and generates an Invoice with a BOLT-11 payment request. WRITE: `orders` merchant_id, wallet_id, total_amount_cents, total_amount_sats, items, metadata, status = pending WRITE: `checkout_sessions` order_id = new order WRITE: `invoices` payment_request, payment_hash, amount_sats, expires_at ##### 5. QR Code + Monitoring The checkout page displays a QR code. Background jobs begin polling for payment: PaymentMonitorJob polls every 5 seconds (up to 15 minutes). The checkout page listens on WebSocket (ActionCable) for instant updates, with HTTP polling every 5 seconds as fallback. ##### Payment Confirmed When a background job detects settlement: WRITE: `invoices` status = paid, paid_at WRITE: `orders` status = paid WRITE: `payments` amount_sats, preimage, btc_fiat_price, fiat_value, fiat_variance, confirmed_at WRITE: `checkout_sessions` status = completed WebSocket broadcasts to checkout page, which redirects the customer to success_url. Webhooks fire asynchronously. ##### Session Expired (no payment) If 15 minutes pass with no payment detected: WRITE: `invoices` status = expired WRITE: `checkout_sessions` status = expired Customer is redirected to cancel_url. ## Bitcoin Conversion Fee A merchant can add a fee of up to 3% to bitcoin payments to cover selling the sats for local currency. The price itself (`amount_cents`, `total_amount_cents`) never changes: the fee is charged on top of it and the invoice is priced at `amount_due_cents`. The hosted checkout shows it as its own line. When the fee is not zero, these fields are added so your own UI can show it too: ``` "total_amount_cents": 10000, "conversion_fee_percent": 1.5, "conversion_fee_cents": 150, "amount_due_cents": 10150, "total_amount_sats": 101500 // priced on amount_due_cents ``` Returned on orders, invoices, payment requests, checkout sessions, the checkout status poll and the order in webhook payloads. The fields are absent when the fee is zero, and on cash orders, which never carry it. A new checkout session previews the merchant's current rate; once the order exists, its own recorded rate is what the customer is charged. `GET /api/v1/m/merchant` returns the configured `conversion_fee_percent` for clients that build their own orders. ## Status Transitions ###### CheckoutSession ``` pending ──▶ completed (paid) pending ──▶ expired (15 min) pending ──▶ cancelled ``` ###### Order ``` pending ──▶ invoice_generated ──▶ paid ──▶ cancelled paid ──▶ refunded ``` ###### Invoice ``` pending ──▶ paid (settled) pending ──▶ expired (TTL) pending ──▶ cancelled ``` ###### Payment ``` No status enum. Created once on confirmation. Immutable after creation. Proof: preimage (Lightning) ``` ## Every Database Write, In Order | # | Trigger | Table | Operation | | --- | --- | --- | --- | | 1 | API call / payment link / embed | `checkout_sessions` | INSERT token, amount, currency, merchant, product, urls, metadata, expires_at | | 2 | Customer submits info form | `checkout_sessions` | UPDATE customer_email, customer_name, customer_phone, customer_address | | 3 | First visit to QR page | `orders` | INSERT merchant, wallet, amounts, items, metadata | | 5 | First visit to QR page | `checkout_sessions` | UPDATE order_id = new order | | 6 | Invoice generation | `orders` | UPDATE total_amount_sats (fresh BTC price) | | 7 | Invoice generation | `invoices` | INSERT payment_request, payment_hash, amount_sats, expires_at | | 8 | Background job detects payment | `invoices` | UPDATE status = paid, paid_at | | 9 | Background job detects payment | `orders` | UPDATE status = paid | | 10 | Background job detects payment | `payments` | INSERT amount_sats, preimage, btc_fiat_price, fiat_value, fiat_variance, confirmed_at | | 11 | Background job detects payment | `checkout_sessions` | UPDATE status = completed | ## Ready to integrate? Create a checkout session with a single API call. [Checkout API →](https://www.satsrail.com/developers/) [Embed Button →](https://www.satsrail.com/developers/guides/embed/) --- # Embed Button > Add a Bitcoin pay button to any website with one script tag. No backend needed. Works on WordPress, Shopify, and static sites. Source: https://www.satsrail.com/developers/guides/embed/ One script tag. Any website. Bitcoin payments. ## How It Works Add a single ` ``` ###### Via API (server-side) ``` curl -X POST https://app.satsrail.com/api/v1/m/checkout_sessions \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "checkout_session": { "amount_cents": 2500, "currency": "usd", "customer_email": "buyer@example.com", "customer_name": "Alice Smith", "metadata": { "order_id": "ORD-42", "plan": "pro" }, "success_url": "https://mystore.com/thanks", "cancel_url": "https://mystore.com/cart" } }' ``` > When `customer_email` is provided, the checkout page shows the payment QR immediately — no extra form step. --- #### Flow 2 Product Button (Stripe-style) No checkout page on your site? Create a **Product** in your SatsRail dashboard and configure which customer fields to collect in your merchant Settings (email, name, phone, shipping address). SatsRail will show a form with the selected fields before the payment QR code. 1. Create a Product in your dashboard 2. Configure which customer fields to collect in Settings 3. Embed a product button or share the Payment Link 4. Customer fills in the requested info, selects payment method, and pays 5. Customer info is stored in the order metadata ###### Embed Button ``` ``` ###### Payment Link ``` https://satsrail.com/pay/prod_abc123def456 ``` > Customer fields are configured in your merchant Settings. Choose from email (always required), name, phone, and shipping address. The checkout page only shows the fields you selected. ## Product Button Instead of hardcoding amounts, create a **Product** in your SatsRail dashboard (with name and price), then reference it by slug. The button fetches the price automatically and displays it. ``` ``` The button will show **"Pay $25.00 with Bitcoin"** (or whatever the product price is). Override the label with `data-label` if needed. ## Payment Links Need something even simpler? Every product has a **shareable payment link** — a URL you can paste anywhere. No code, no API keys, no website required. ``` https://satsrail.com/pay/prod_abc123def456 ``` Share via email, WhatsApp, social media, or print as a QR code. When someone opens the link, a checkout session is created automatically and they see the payment page with QR code and countdown timer. ## Data Attributes | Attribute | Required | Description | | --- | --- | --- | | `data-key` | Yes | Publishable key (`pk_live_` or `pk_test_`) | | `data-product` | No* | Product slug (e.g. `prod_abc123`). If set, amount and currency are fetched automatically from the product. | | `data-amount` | No* | Amount in cents (e.g. 5000 = $50.00). Required if `data-product` is not set. | | `data-currency` | No | Currency code, default `"usd"` | | `data-success-url` | No | Redirect URL after payment | | `data-cancel-url` | No | Redirect URL on cancel | | `data-label` | No | Button text, default `Pay with Bitcoin ⚡` | | `data-mode` | No | `iframe` (embedded overlay), `new_tab`, or `redirect`. If omitted, uses your merchant Settings. | | `data-customer-email` | No | Pre-fill customer email. Skips the info collection form on the checkout page. | | `data-customer-name` | No | Pre-fill customer name. | | `data-customer-phone` | No | Pre-fill customer phone number. | | `data-customer-address` | No | Pre-fill customer shipping address. | ## Full Example ##### Amount Mode Hardcode the price directly in the script tag: ```

Buy Coffee Beans — $25.00

``` ##### Product Mode Reference a product — price is fetched automatically: ```

Buy Coffee Beans

``` ## Button Styling The script auto-creates a `