# Crypto processing — Swaps API

Crypto invoices, pay-ins and subscriptions settled to the merchant's own wallet.

**Jump to an operation:**

- [GET /payment_links/{id}/payments](#list-a-links-payment-attempts-available)
- [GET /payments](#list-pay-ins-across-products-available)
- [GET /payments/{id}](#get-a-pay-in-available)
- [GET /payments/{id}/events](#list-a-payments-events-available)
- [GET /subscriptions](#list-subscriptions-dark-flag)
- [POST /subscriptions](#create-a-subscription-dark-flag)
- [GET /subscriptions/{id}](#get-a-subscription-dark-flag)
- [POST /subscriptions/{id}/pause](#pause-a-subscription-dark-flag)
- [POST /subscriptions/{id}/resume](#resume-a-paused-subscription-dark-flag)
- [POST /subscriptions/{id}/cancel](#cancel-a-subscription-dark-flag)
- [GET /subscriptions/{id}/invoices](#list-a-subscriptions-invoices-dark-flag)
- [GET /subscriptions/{id}/invoices/{invoice_id}](#get-a-subscription-invoice-dark-flag)

> Generated from the bundled OpenAPI contract. The HTML page and this Markdown twin share the same processed operation descriptions and status annotations.

<a id="list-a-links-payment-attempts-available"></a>
## `GET /payment_links/{id}/payments` — List a link's payment attempts — available

**Operation ID:** `payment_links.payments.list`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable

A view over the top-level /v1/payments resource, scoped to one link (RESOURCE-MODEL §2.1 v2 amendment). The detail screen must hold this before rendering the cancel confirmation, so the client can pick the right copy without an extra round trip (R11 PL-G11). Business key, agent (owner only), dashboard session; test mode is unavailable (`503 temporarily_unavailable`).

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of payment attempts on this link. | `application/json` [PaymentList](/reference/~schemas.md#paymentlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="list-pay-ins-across-products-available"></a>
## `GET /payments` — List pay-ins across products — available

**Operation ID:** `payments.list`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable

List this account's pay-ins, newest first — payment-link attempts and crypto-processing attempts in one cross-product resource (RESOURCE-MODEL §2.1 v2 amendment). underpaid, overpaid, unmatched and processing are planned states (R19 X2/X4) — their absence from a result is not proof a payment never reached that condition. Business key, agent (owner only), dashboard session; test mode is unavailable (`503 temporarily_unavailable`).

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |
| `payment_link_id` | query | no | `string` | — |
| `subscription_id` | query | no | `string` | — |
| `settlement` | query | no | `wallet` | Filters by settlement destination (CP-T1). `wallet` is the merchant's own Swaps Wallet (RESOURCE-MODEL §2.3): the pay-ins of links whose `settlement_kind` is `crypto_only`, which the Crypto processing Payments list reads. Applies to the page and to `?expand=summary`. Any other value is `400 invalid_request` with `param: settlement`. |
| `status` | query | no | [PaymentStatus](/reference/~schemas.md#paymentstatus) | — |
| `expand` | query | no | `string` | Comma-separated. `summary` (LIST-SUMMARY-1) adds `summary`: counts computed server-side over EVERY row matching this request's filters, not over the returned page, so a client never adds up a paginated page and calls it a total. `limit`/`cursor` never change it. An unrecognised value is ignored rather than rejected (the `GET /account` `expand` rule). |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of payments. | `application/json` [PaymentList](/reference/~schemas.md#paymentlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="get-a-pay-in-available"></a>
## `GET /payments/{id}` — Get a pay-in — available

**Operation ID:** `payments.get`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable

Read one payment the caller owns — amount, fee, net amount, on-chain evidence and screening where it applies. No create, no refund: only a payer session creates one, and Swaps never initiates a refund (RESOURCE-MODEL §0.10). Business key, agent (owner only), dashboard session; test mode is unavailable (`503 temporarily_unavailable`).

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The payment. | `application/json` [Payment](/reference/~schemas.md#payment) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="list-a-payments-events-available"></a>
## `GET /payments/{id}/events` — List a payment's events — available

**Operation ID:** `payments.events.list`

> **Status:** Available · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** sandbox

This payment's own event timeline, newest first (RESOURCE-MODEL §2.1, mirrors `/payment_links/{id}/events` exactly — BL-16 closed the same class for payment_links). A view over the K8 events outbox (`api_events`) scoped to this payment's own `resource_id`; unlike the link-level route there is no legacy audit table to merge, and `payment.*` had no producer before the outbox — so this page is complete since the events outbox was enabled for this account, not "the complete history" for a payment that predates the flip. Only the 5 publishable `payment.*` types this account's flag has actually produced are ever returned: `payment.created`, `.awaiting`, `.paid`, `.settled`, `.expired`. Callers: business key, agent, dashboard session. Test mode: sandbox (K11-6, §4.2 drift, was declared `full`) — a test payment's events are written by DEV handlers into DEV's own `api_events` outbox; a prod-local read would return an empty page forever. BL-33 (2026-09-15): closes the crypto-processing payment timeline's fallback to attempt.updated_at once the outbox has rows for the account; the timeline must keep that fallback until then. Ownership is checked against the payment link's `merchant_user_id`, but the event query is scoped to the caller's resolved `account_id`; a merchant who owns more than one account can see an empty page for a payment that legitimately belongs to their OTHER account, rather than a 404 (pre-existing on every `/v1/events`-family read, not introduced here — `_shared/services/api_events.ts`'s owner-walk always resolves to the OLDEST account).

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of events on this payment. | `application/json` [PaymentEventList](/reference/~schemas.md#paymenteventlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="list-subscriptions-dark-flag"></a>
## `GET /subscriptions` — List subscriptions — dark-flag

**Operation ID:** `subscriptions.list`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

List this account's version-1 scheduled-invoice subscriptions. Each row carries `overdue` (NB-2) — derived from its own open, past-due invoices, never from the subscription's own status. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session. With `api_v1.subscriptions` closed, the answer is `503 temporarily_unavailable` (an availability lever, never an authorization one), not `capability_unavailable`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |
| `status` | query | no | `active` \| `paused` \| `cancelled` | — |
| `expand` | query | no | `string` | Comma-separated. `summary` (LIST-SUMMARY-1) adds `summary`: counts computed server-side over EVERY row matching this request's filters, not over the returned page, so a client never adds up a paginated page and calls it a total. `limit`/`cursor` never change it. An unrecognised value is ignored rather than rejected (the `GET /account` `expand` rule). |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of subscriptions. | `application/json` [SubscriptionList](/reference/~schemas.md#subscriptionlist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="create-a-subscription-dark-flag"></a>
## `POST /subscriptions` — Create a subscription — dark-flag

**Operation ID:** `subscriptions.create`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.write` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

Create a version-1 scheduled-invoice subscription — a crypto invoice reissued on a schedule, never an authorised pull (RESOURCE-MODEL §2.6). Every subscription settles `crypto_only` (the no-FX `crypto_tempo` rail), so `amount.currency` must be `USD` — anything else is refused `422 crypto_only_currency_not_usd` (C4-D27), the same code and reason `payment_links.activate` refuses a non-USD crypto-only link with. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Request body

Required: **yes**

| Media type | Schema |
| --- | --- |
| `application/json` | [SubscriptionCreateRequest](/reference/~schemas.md#subscriptioncreaterequest) |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `201` | The created subscription. | `application/json` [Subscription](/reference/~schemas.md#subscription) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | — | `Conflict` |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="get-a-subscription-dark-flag"></a>
## `GET /subscriptions/{id}` — Get a subscription — dark-flag

**Operation ID:** `subscriptions.get`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

Read one subscription the caller owns. `overdue` (NB-2) is derived from its own open, past-due invoices at read time and never stored — the subscription's own status never flips to reflect a missed payment. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session. With `api_v1.subscriptions` closed, the answer is `503 temporarily_unavailable` (an availability lever, never an authorization one), not `capability_unavailable`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The subscription. | `application/json` [Subscription](/reference/~schemas.md#subscription) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="pause-a-subscription-dark-flag"></a>
## `POST /subscriptions/{id}/pause` — Pause a subscription — dark-flag

**Operation ID:** `subscriptions.pause`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.write` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

Stops future invoices from issuing until resumed. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session. With `api_v1.subscriptions` closed, the answer is `503 temporarily_unavailable` (an availability lever, never an authorization one), not `capability_unavailable`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The paused subscription. | `application/json` [Subscription](/reference/~schemas.md#subscription) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | — | `Conflict` |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="resume-a-paused-subscription-dark-flag"></a>
## `POST /subscriptions/{id}/resume` — Resume a paused subscription — dark-flag

**Operation ID:** `subscriptions.resume`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.write` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

**Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The resumed subscription. | `application/json` [Subscription](/reference/~schemas.md#subscription) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | — | `Conflict` |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="cancel-a-subscription-dark-flag"></a>
## `POST /subscriptions/{id}/cancel` — Cancel a subscription — dark-flag

**Operation ID:** `subscriptions.cancel`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.write` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

Terminal — a cancelled subscription behaves like a cancelled payment link and issues no further invoices. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session. With `api_v1.subscriptions` closed, the answer is `503 temporarily_unavailable` (an availability lever, never an authorization one), not `capability_unavailable`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `IdempotencyKey` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The cancelled subscription. | `application/json` [Subscription](/reference/~schemas.md#subscription) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `409` | — | `Conflict` |
| `413` | — | `PayloadTooLarge` |
| `422` | — | `UnprocessableEntity` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="list-a-subscriptions-invoices-dark-flag"></a>
## `GET /subscriptions/{id}/invoices` — List a subscription's invoices — dark-flag

**Operation ID:** `subscriptions.invoices.list`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

The child-invoice ladder — one row per period, issued on its due date, never earlier. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session. Resuming a paused subscription deletes any never-issued invoice whose period fell inside the paused span, so a `sequence` gap can appear across a resume with no corresponding event. With `api_v1.subscriptions` closed, the answer is `503 temporarily_unavailable` (an availability lever, never an authorization one), not `capability_unavailable`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |
| `Limit` | component | — | — | Reusable parameter component |
| `Cursor` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | A page of invoices. | `application/json` [SubscriptionInvoiceList](/reference/~schemas.md#subscriptioninvoicelist) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |

---

<a id="get-a-subscription-invoice-dark-flag"></a>
## `GET /subscriptions/{id}/invoices/{invoice_id}` — Get a subscription invoice — dark-flag

**Operation ID:** `subscriptions.invoices.get`

> **Status:** Dark-flag — live behind a cohort or flag · **Callers:** business key, agent (business key), dashboard session · **Scope:** `payment_links.read` · **Test mode:** unavailable
>
> This operation is **dark-flag — live behind a cohort or flag**. While its resource switch (`api_v1.<resource>`) is off, calling it answers `503 temporarily_unavailable`; with the switch on, a closed product or launch flag, or a cohort the account is outside, answers `409 capability_unavailable`. Check its product page before relying on it.

No create: the emitter is a cron, not a caller. **Dark-flag** — gated behind `api_v1.subscriptions` (K13c), REST availability depends on runtime flags; this catalogue status does not establish their production values. Business key, agent, dashboard session. With `api_v1.subscriptions` closed, the answer is `503 temporarily_unavailable` (an availability lever, never an authorization one), not `capability_unavailable`.

### Parameters

| Name | In | Required | Schema | Description |
| --- | --- | --- | --- | --- |
| `id` | path | yes | `string` | — |
| `invoice_id` | path | yes | `string` | — |
| `SwapsVersion` | component | — | — | Reusable parameter component |
| `SwapsAccount` | component | — | — | Reusable parameter component |

### Responses

| Status | Description | Body |
| --- | --- | --- |
| `200` | The invoice. | `application/json` [SubscriptionInvoice](/reference/~schemas.md#subscriptioninvoice) |
| `400` | — | `BadRequest` |
| `401` | — | `Unauthorized` |
| `403` | — | `Forbidden` |
| `404` | — | `NotFound` |
| `429` | — | `TooManyRequests` |
| `500` | — | `InternalError` |
| `503` | — | `ServiceUnavailable` |
