---
title: "Collect"
description: "29 operations under /v1/collect (GET POST PATCH PUT): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript."
---

Base URL: `https://api.codespar.dev`

Operations below require a Bearer token unless their bar says `No credential`. See [Authentication](/docs/concepts/authentication).

### GET `/v1/collect/attempts`

<Endpoint method="GET" path="/v1/collect/attempts" base="https://api.codespar.dev" />

List the project's Collect charges with their payments

Every attempt of the project whose link is in the credential's environment, newest first, each with its link, receiver, payer label and payment. Read-only. Cursor `before` = the previous page's `next_before`.

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `before` | `string` | no | — |
| `limit` | `integer` | no | — |
| `link_id` | `string` | no | — |
| `state` | `"issuing"` \| `"open"` \| `"superseded"` \| `"expired"` \| `"cancelled"` \| `"paid"` \| `"failed"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |
| `next_before` | `string,null` | yes | — |
| `object` | `"list"` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/attempts \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/attempts HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/attempts",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/attempts", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/attempts");
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "list",
  "data": [
    {
      "id": "obj_0000000000000000",
      "object": "collect_attempt_activity",
      "link": {
        "id": "obj_0000000000000000",
        "title": "Example",
        "state": "draft",
        "version": 0
      },
      "environment": "live",
      "surface": "page",
      "state": "issuing",
      "amount_minor": 1000,
      "currency": "BRL",
      "due_date": "2026-01-15",
      "expires_at": "string",
      "failure_code": "string",
      "created_at": "string",
      "closed_at": "string",
      "payer": {
        "label": "Example"
      },
      "receiver": {
        "name": "Example"
      },
      "charge_registered": true,
      "issuer": "celcoin_sandbox",
      "test_payment": {
        "stage": "claimed",
        "at": "string"
      },
      "payment": {
        "id": "obj_0000000000000000",
        "kind": "paid",
        "exception": "late",
        "amount_minor": 1000,
        "currency": "BRL",
        "paid_at": "string",
        "rail": "pix",
        "receipt_id": "receipt_0000000000000000",
        "simulated": true,
        "executor": "celcoin_sandbox",
        "paid_by": "test_payer"
      },
      "session_id": "ses_0000000000000000"
    }
  ],
  "next_before": "string"
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/attempts" />

### GET `/v1/collect/attempts/{attemptId}/artifact`

<Endpoint method="GET" path="/v1/collect/attempts/{attemptId}/artifact" base="https://api.codespar.dev" />

The WhatsApp material of an attempt

What the partner's channel sends: the copia-e-cola, a QR code PNG of it (base64), the amount, the due date and the hosted page URL as the fallback. CodeSpar sends nothing (`sent` is always false).

NOT BEFORE IT EXISTS. While the attempt is `issuing` this answers 409 `artifact_not_ready` with `Retry-After` and `details.retry_after`; subscribe to `collect.attempt.ready` instead of polling if you prefer; a failed attempt emits `collect.attempt.failed` with its `failure_code`. An attempt that is no longer payable (paid, superseded, expired, cancelled, failed) answers 410 `artifact_unavailable` with its state.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `attemptId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No attempt with that id in this project. |
| `409` | object | Not ready, or the link does not enable WhatsApp. |
| `410` | object | The attempt has no payable material any more. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `attempt_id` | `string` | yes | — |
| `copy_paste` | `string,null` | yes | The Pix copia-e-cola. Null until the attempt is `open`: it does not exist before the issuer registers it. |
| `currency` | `"BRL"` | yes | — |
| `due_date` | `string` | yes | — |
| `environment` | `"live"` \| `"test"` | yes | The link's environment. `test`: the Pix moves no real money, and the message can say so. |
| `expires_at` | `string` | yes | — |
| `failure_code` | `string,null` | no | — |
| `fallback_url` | `string` | yes | — |
| `id` | `string` | yes | — |
| `link_id` | `string` | yes | — |
| `object` | `"collect_artifact"` | yes | — |
| `qr_mime_type` | `"image/png"` | yes | — |
| `qr_png_base64` | `string` | yes | — |
| `ready_at` | `string,null` | yes | — |
| `retry_after` | `integer` | no | While `issuing`: seconds until the next readiness check. |
| `sent` | `false` | yes | — |
| `state` | `"issuing"` \| `"open"` \| `"superseded"` \| `"expired"` \| `"cancelled"` \| `"paid"` \| `"failed"` | yes | — |
| `surface` | `"page"` \| `"whatsapp"` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/attempts/{attemptId}/artifact \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/attempts/{attemptId}/artifact HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/attempts/{attemptId}/artifact",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/attempts/{attemptId}/artifact", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/attempts/{attemptId}/artifact", {
  path: {
    attemptId: "attempt_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "state": "issuing",
  "surface": "page",
  "amount_minor": 1000,
  "currency": "BRL",
  "due_date": "2026-01-15",
  "expires_at": "string",
  "copy_paste": "string",
  "ready_at": "string",
  "retry_after": 0,
  "failure_code": "string",
  "object": "collect_artifact",
  "attempt_id": "attempt_0000000000000000",
  "link_id": "link_0000000000000000",
  "environment": "live",
  "qr_png_base64": "string",
  "qr_mime_type": "image/png",
  "fallback_url": "https://example.com/hook",
  "sent": false
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/attempts/{attemptId}/artifact" />

### POST `/v1/collect/attempts/{attemptId}/test-pay`

<Endpoint method="POST" path="/v1/collect/attempts/{attemptId}/test-pay" base="https://api.codespar.dev" money="pays the attempt's charge FOR REAL at the Celcoin sandbox" />

Pay a Test attempt's charge from the shared Test payer (the payer button)

"Simular pagamento do cliente": pays the attempt's charge FOR REAL at the Celcoin sandbox, from the operator's Test payer account, the way a customer would pay its copia-e-cola. Test projects only.

No body: the amount and the destination come from the stored attempt and charge. One payment per charge; a second call answers 409 `test_pay_already_used` whatever happened to the first. Capped per payment and per project per 24 hours. The attempt turns `paid` the way any payment does (the charge-in, the reconciler, or the hosted page's read), never from this answer.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `attemptId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | A body was sent, or the credential names no project. |
| `403` | object | Not a Test credential or project, or a project outside the proof's list (the shared Test payer is its own capability, D10). |
| `404` | object | No attempt with that id in this project. |
| `409` | object | The attempt is not open, the charge already had its Test payment, or it is above the cap. |
| `429` | object | The project used its Test payments for the last 24 hours. |
| `502` | object | A step at Celcoin failed; the charge keeps its claim. |
| `503` | object | No Test payer or Celcoin connection on this deployment. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `attempt_id` | `string` | yes | — |
| `charge_id` | `string` | yes | — |
| `end_to_end_id` | `string,null` | yes | — |
| `object` | `"collect_test_payment"` | yes | — |
| `status` | `string` | yes | Celcoin's answer to the Pix, usually PROCESSING; not a settlement. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/attempts/{attemptId}/test-pay \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/attempts/{attemptId}/test-pay HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/attempts/{attemptId}/test-pay",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/attempts/{attemptId}/test-pay", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/attempts/{attemptId}/test-pay", {
  path: {
    attemptId: "attempt_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "collect_test_payment",
  "attempt_id": "attempt_0000000000000000",
  "charge_id": "charge_0000000000000000",
  "amount_minor": 1000,
  "status": "string",
  "end_to_end_id": "endtoend_0000000000000000"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/attempts/{attemptId}/test-pay" />

### GET `/v1/collect/links`

<Endpoint method="GET" path="/v1/collect/links" base="https://api.codespar.dev" />

List this project's Collect links

Newest first. `next_cursor` is the `created_at` of the last row, to pass as `before`. `reference` lists every version of one obligation (D-C), archived ones included.

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `before` | `string (date-time)` | no | — |
| `limit` | `integer` | no | — |
| `reference` | `string` | no | — |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |
| `next_cursor` | `string,null` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links");
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "data": [
    {
      "id": "obj_0000000000000000",
      "object": "collect_link",
      "state": "draft",
      "environment": "live",
      "consumer_id": "csm_0000000000000000",
      "receiver": {
        "name": "Example"
      },
      "paid": true,
      "paid_at": "string",
      "current_version": 0,
      "valid_until": "string",
      "url": "https://example.com/hook",
      "payer_prefilled": [
        "name"
      ],
      "payer_document_masked": "string",
      "version": {
        "version": 0,
        "status": "draft",
        "title": "Example",
        "description": "string",
        "success_message": "string",
        "items": [
          {
            "name": "Example",
            "quantity": 0,
            "unit_amount_minor": 1000
          }
        ],
        "total_minor": 1,
        "currency": "BRL",
        "brand": {},
        "surfaces": [
          "page"
        ],
        "due_in_days": 0,
        "due_on": "string",
        "payer_fields": {
          "name": "required",
          "contact": "required",
          "external_reference": "required"
        },
        "usdc_price": "string",
        "usdc_price_atomic": "string",
        "usdc_price_confirmed_at": "string",
        "created_at": "string",
        "published_at": "string"
      },
      "draft": {
        "version": 0,
        "status": "draft",
        "title": "Example",
        "description": "string",
        "success_message": "string",
        "items": [
          {
            "name": "Example",
            "quantity": 0,
            "unit_amount_minor": 1000
          }
        ],
        "total_minor": 1,
        "currency": "BRL",
        "brand": {},
        "surfaces": [
          "page"
        ],
        "due_in_days": 0,
        "due_on": "string",
        "payer_fields": {
          "name": "required",
          "contact": "required",
          "external_reference": "required"
        },
        "usdc_price": "string",
        "usdc_price_atomic": "string",
        "usdc_price_confirmed_at": "string",
        "created_at": "string",
        "published_at": "string"
      },
      "created_at": "string",
      "updated_at": "string",
      "published_at": "string",
      "paused_at": "string",
      "archived_at": "string",
      "reference": "string",
      "reference_version": "string",
      "receiver_resolution": "explicit",
      "agent": {
        "url": "https://example.com/hook",
        "status": "ready",
        "reason": "string",
        "challenge": {},
        "refusal": {
          "code": "string",
          "message": "string",
          "http_status": 0
        },
        "usdc": {
          "price": "string",
          "amount": "1000",
          "currency": "USDC",
          "network": "string",
          "asset": "string",
          "payable": false,
          "reason": "usdc_pay_to_unavailable",
          "message": "string",
          "statement": "string"
        }
      }
    }
  ],
  "next_cursor": "string"
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links" />

### POST `/v1/collect/links`

<Endpoint method="POST" path="/v1/collect/links" base="https://api.codespar.dev" />

Create a Collect link with its first draft

Creates a single-use link (one link, one charge, one valid payment) in `draft`, with version 1 as its draft. Nothing is shown to payers until it is published.

`consumer_id` is the RECEIVING consumer: the Pix the link issues settles into that consumer's wallet. No Pix key or account is accepted on the link. Omitted, Collect chooses it (D-B): the project's default receiver (`PUT /v1/collect/receivers/default`), else the project's only consumer with its own Celcoin receiving identity, else 409 `collect_receiver_unresolved` with `details.next_action`. A receiver Collect chose never falls back to a shared account: publishing or issuing into one is refused (`collect_receiver_not_own`).

`reference` + `reference_version` (both or neither) name the caller's obligation and the version asked for (D-C), inside this org and project. The same pair answers the SAME link, forever, archived or not (200, `replay: true`); the same pair with different content is 409 `collect_reference_conflict`. A new version is created only when no earlier version can still be paid: 409 `collect_reference_paid` (the obligation was paid, or a late payment of it was kept; it is not reissued), `collect_reference_active` (an earlier version is not archived), `collect_reference_late_undecided` (a late payment of an earlier version, recorded before any newer version existed, waits for the collector's decision: keep it or refund it; pending Fabiano's decision, implemented closed), `collect_reference_not_cancelled` (no POSITIVE proof that nothing earlier can still be paid, from the archive's evaluator; `details.reasons`: `link_not_archived`, `attempt_live`, `attempt_state_unrecognized`, `issuance_unresolved`, `charge_unknown`, `charge_payable`, `charge_state_unrecognized`, `payment_notified`, `payment_contradiction`, `payment_unrecorded`, `cancel_unresolved`, `obligation_paid`, `late_undecided`, `paid_before_cancel`, `payment_vs_cancel_unproven`).

`payer` optionally prefills the payer. `document` (CPF/CNPJ, check digits verified) and `address` are the regulated fields a Pix with due date requires; when they are not prefilled, the hosted page collects them. All payer data is stored encrypted and is never returned: the response names which fields are prefilled and shows the document masked.

Collect is off unless the deployment sets `COLLECT_ENABLED=true`. Off, every `/v1/collect` path (owner and payer) answers 404 `{"error": "collect_disabled", "message": "Collect não está disponível nesta versão. ...", "details": {"reason": "switch_off"}}`, never the router's generic 404. On, a project that may not start a Collect operation gets the same code with `details.reason`: `live` (Collect runs only in test projects in this phase), `project_not_listed` (a test project the deployment has not enabled) or `capability_inactive` (a test project whose `collect` capability was requested but is not active: suspended, revoked, expired or still requested). An unlisted test project whose `collect` capability is active reaches Collect; whether a given receiver is ready to issue is decided at publish.

RECURRING OFFERS (0390). `billing: "recurring"` with a version carrying `recurrence` (`first_payment: "now"`, `frequency: "monthly"`, `max_charges`, `retries`, `qr_valid_days`) creates an offer each payer adheres to with Pix Automático: they pay the first charge now and authorize the next ones in their bank app, at the version's total every month. The receiver must be a company (CNPJ) account with a registered name, its own (never a shared one): otherwise 422 `collect_recurring_receiver_not_eligible` with `details.reason`. A recurring offer takes no `reference` and no `payer` prefill, and is paid on the hosted page only.

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `billing` | `"one_time"` \| `"recurring"` | no | — |
| `consumer_id` | `string` | no | — |
| `payer` | object | no | — |
| `reference` | `string` | no | — |
| `reference_version` | `string` | no | — |
| `valid_until` | `string (date-time)` | no | — |
| `version` | object \| object | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `201` | object | OK |
| `400` | object | Bad Request. The body did not match the schema (`invalid_body`, with `details.issues`), or the version breaks a rule the schema cannot state: the items total below the R$5.00 a Pix with due date needs (`collect_total_below_minimum`), above R$1,000,000.00 (`collect_total_above_maximum`), or brand colors whose contrast is below 4.5:1 (`collect_brand_contrast_insufficient`), or a `usdc_price` on a version without the agent surface (`collect_usdc_price_requires_agent`). Unknown keys are refused, not dropped: the regulated payer fields (document, address) are never free-text questions. |
| `409` | object | Conflict. The obligation or the receiver refuses a new link; `details` says why and what to do. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `replay` | `true` | yes | — |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/links \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "consumer_id": "csm_0000000000000000",
       "valid_until": "2026-01-15T12:00:00.000Z",
       "billing": "one_time",
       "version": {
         "title": "Example",
         "description": "string",
         "success_message": "string",
         "items": [
           {
             "name": "Example",
             "quantity": 0,
             "unit_amount_minor": 1000
           }
         ],
         "brand": {
           "logo_url": "https://example.com/hook",
           "color": "string",
           "text_color": "string",
           "texts": {
             "header": "string",
             "footer": "string"
           }
         },
         "surfaces": [
           "page"
         ],
         "due_in_days": 0,
         "payer_fields": {
           "contact": "optional",
           "external_reference": "off"
         },
         "usdc_price": "string"
       },
       "payer": {
         "name": "Example",
         "contact": "string",
         "external_reference": "string",
         "document": "string",
         "address": {
           "publicArea": "string",
           "number": "string",
           "neighborhood": "string",
           "city": "string",
           "state": "string",
           "postalCode": "string"
         }
       },
       "reference": "string",
       "reference_version": "string"
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/links HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "consumer_id": "csm_0000000000000000",
  "valid_until": "2026-01-15T12:00:00.000Z",
  "billing": "one_time",
  "version": {
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "brand": {
      "logo_url": "https://example.com/hook",
      "color": "string",
      "text_color": "string",
      "texts": {
        "header": "string",
        "footer": "string"
      }
    },
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "payer_fields": {
      "contact": "optional",
      "external_reference": "off"
    },
    "usdc_price": "string"
  },
  "payer": {
    "name": "Example",
    "contact": "string",
    "external_reference": "string",
    "document": "string",
    "address": {
      "publicArea": "string",
      "number": "string",
      "neighborhood": "string",
      "city": "string",
      "state": "string",
      "postalCode": "string"
    }
  },
  "reference": "string",
  "reference_version": "string"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/links",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "consumer_id": "csm_0000000000000000",
      "valid_until": "2026-01-15T12:00:00.000Z",
      "billing": "one_time",
      "version": {
        "title": "Example",
        "description": "string",
        "success_message": "string",
        "items": [
          {
            "name": "Example",
            "quantity": 0,
            "unit_amount_minor": 1000
          }
        ],
        "brand": {
          "logo_url": "https://example.com/hook",
          "color": "string",
          "text_color": "string",
          "texts": {
            "header": "string",
            "footer": "string"
          }
        },
        "surfaces": [
          "page"
        ],
        "due_in_days": 0,
        "payer_fields": {
          "contact": "optional",
          "external_reference": "off"
        },
        "usdc_price": "string"
      },
      "payer": {
        "name": "Example",
        "contact": "string",
        "external_reference": "string",
        "document": "string",
        "address": {
          "publicArea": "string",
          "number": "string",
          "neighborhood": "string",
          "city": "string",
          "state": "string",
          "postalCode": "string"
        }
      },
      "reference": "string",
      "reference_version": "string"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "consumer_id": "csm_0000000000000000",
    "valid_until": "2026-01-15T12:00:00.000Z",
    "billing": "one_time",
    "version": {
      "title": "Example",
      "description": "string",
      "success_message": "string",
      "items": [
        {
          "name": "Example",
          "quantity": 0,
          "unit_amount_minor": 1000
        }
      ],
      "brand": {
        "logo_url": "https://example.com/hook",
        "color": "string",
        "text_color": "string",
        "texts": {
          "header": "string",
          "footer": "string"
        }
      },
      "surfaces": [
        "page"
      ],
      "due_in_days": 0,
      "payer_fields": {
        "contact": "optional",
        "external_reference": "off"
      },
      "usdc_price": "string"
    },
    "payer": {
      "name": "Example",
      "contact": "string",
      "external_reference": "string",
      "document": "string",
      "address": {
        "publicArea": "string",
        "number": "string",
        "neighborhood": "string",
        "city": "string",
        "state": "string",
        "postalCode": "string"
      }
    },
    "reference": "string",
    "reference_version": "string"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.post("/v1/collect/links", {
  body: {
    consumer_id: "csm_0000000000000000",
    valid_until: "2026-01-15T12:00:00.000Z",
    billing: "one_time",
    version: {
      title: "Example",
      description: "string",
      success_message: "string",
      items: [
        {
          name: "Example",
          quantity: 0,
          unit_amount_minor: 1000
        }
      ],
      brand: {
        logo_url: "https://example.com/hook",
        color: "string",
        text_color: "string",
        texts: {
          header: "string",
          footer: "string"
        }
      },
      surfaces: [
        "page"
      ],
      due_in_days: 0,
      payer_fields: {
        contact: "optional",
        external_reference: "off"
      },
      usdc_price: "string"
    },
    payer: {
      name: "Example",
      contact: "string",
      external_reference: "string",
      document: "string",
      address: {
        publicArea: "string",
        number: "string",
        neighborhood: "string",
        city: "string",
        state: "string",
        postalCode: "string"
      }
    },
    reference: "string",
    reference_version: "string"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  },
  "replay": true
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/links" body={"{\"consumer_id\":\"csm_0000000000000000\",\"valid_until\":\"2026-01-15T12:00:00.000Z\",\"billing\":\"one_time\",\"version\":{\"title\":\"Example\",\"description\":\"string\",\"success_message\":\"string\",\"items\":[{\"name\":\"Example\",\"quantity\":0,\"unit_amount_minor\":1000}],\"brand\":{\"logo_url\":\"https://example.com/hook\",\"color\":\"string\",\"text_color\":\"string\",\"texts\":{\"header\":\"string\",\"footer\":\"string\"}},\"surfaces\":[\"page\"],\"due_in_days\":0,\"payer_fields\":{\"contact\":\"optional\",\"external_reference\":\"off\"},\"usdc_price\":\"string\"},\"payer\":{\"name\":\"Example\",\"contact\":\"string\",\"external_reference\":\"string\",\"document\":\"string\",\"address\":{\"publicArea\":\"string\",\"number\":\"string\",\"neighborhood\":\"string\",\"city\":\"string\",\"state\":\"string\",\"postalCode\":\"string\"}},\"reference\":\"string\",\"reference_version\":\"string\"}"} />

### GET `/v1/collect/links/stats`

<Endpoint method="GET" path="/v1/collect/links/stats" base="https://api.codespar.dev" />

The project's Collect numbers over the last 30 days

One read for the Collect screen's figures, over `[to - 30 days, to)` by `paid_at`. Only `paid` payments count as received: a late or duplicate payment is credited too, but it is a refund obligation, not revenue. `by_surface` is the surface of the attempt each payment paid.

`previous_received_minor` is the same sum over the 30 days before, and null when the project has no paid payment before `from` (no history to compare against; 0 would read as a real drop). `links` lists every link with a paid payment or an attempt in the window, most received first; a link with neither is absent.

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `window` | `"30d"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `by_surface` | object | yes | — |
| `currency` | `"BRL"` | yes | — |
| `from` | `string` | yes | — |
| `links` | array of object | yes | — |
| `payments` | `integer` | yes | Paid payments in the window. |
| `previous_received_minor` | `integer,null` | yes | — |
| `received_minor` | `integer` | yes | — |
| `to` | `string` | yes | — |
| `window` | `"30d"` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links/stats \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links/stats HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links/stats",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/stats", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links/stats");
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "window": "30d",
  "from": "string",
  "to": "string",
  "currency": "BRL",
  "received_minor": 1,
  "previous_received_minor": 1,
  "payments": 0,
  "by_surface": {
    "page": 0,
    "whatsapp": 0
  },
  "links": [
    {
      "link_id": "link_0000000000000000",
      "received_minor": 1,
      "payments": 0,
      "attempts": 0,
      "by_surface": {
        "page": 0,
        "whatsapp": 0
      },
      "paid": true
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links/stats" />

### GET `/v1/collect/links/{linkId}`

<Endpoint method="GET" path="/v1/collect/links/{linkId}" base="https://api.codespar.dev" />

Read a Collect link

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links/{linkId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links/{linkId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links/{linkId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links/{linkId}", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  }
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links/{linkId}" />

### PATCH `/v1/collect/links/{linkId}`

<Endpoint method="PATCH" path="/v1/collect/links/{linkId}" base="https://api.codespar.dev" />

Change a draft link's validity or receiving consumer

Only while the link has never been published (`state: draft`). Once published, the validity and the receiving consumer are fixed with what payers were shown, and this answers 409 `collect_link_published_immutable` (an archived link: `collect_link_archived`); a different charge is a new link.

A new `consumer_id` is asked the publish gate's question first, in the link's environment, and refused with the same 422 `receiving_identity_missing`; the link is left as it was. `valid_until: null` clears the validity. At least one field.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `consumer_id` | `string` | no | — |
| `valid_until` | `string,null (date-time)` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. The body did not match the schema (`invalid_body`, with `details.issues`), or the version breaks a rule the schema cannot state: the items total below the R$5.00 a Pix with due date needs (`collect_total_below_minimum`), above R$1,000,000.00 (`collect_total_above_maximum`), or brand colors whose contrast is below 4.5:1 (`collect_brand_contrast_insufficient`), or a `usdc_price` on a version without the agent surface (`collect_usdc_price_requires_agent`). Unknown keys are refused, not dropped: the regulated payer fields (document, address) are never free-text questions. |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | The link was published (or archived): its settings are fixed. |
| `422` | object | The new consumer cannot receive a Pix with due date in the link's environment. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X PATCH https://api.codespar.dev/v1/collect/links/{linkId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "valid_until": "2026-01-15T12:00:00.000Z",
       "consumer_id": "csm_0000000000000000"
     }'
```

</Tab>
<Tab value="HTTP">

```http
PATCH /v1/collect/links/{linkId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "valid_until": "2026-01-15T12:00:00.000Z",
  "consumer_id": "csm_0000000000000000"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.patch(
    "https://api.codespar.dev/v1/collect/links/{linkId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "valid_until": "2026-01-15T12:00:00.000Z",
      "consumer_id": "csm_0000000000000000"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "valid_until": "2026-01-15T12:00:00.000Z",
    "consumer_id": "csm_0000000000000000"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("patch", "/v1/collect/links/{linkId}", {
  path: {
    linkId: "link_0000000000000000"
  },
  body: {
    valid_until: "2026-01-15T12:00:00.000Z",
    consumer_id: "csm_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  }
}
```

</SplitPane>
</Split>

<TryIt method="PATCH" path="/v1/collect/links/{linkId}" body={"{\"valid_until\":\"2026-01-15T12:00:00.000Z\",\"consumer_id\":\"csm_0000000000000000\"}"} />

### POST `/v1/collect/links/{linkId}/archive`

<Endpoint method="POST" path="/v1/collect/links/{linkId}/archive" base="https://api.codespar.dev" />

Archive a Collect link

Terminal. The link takes no new version and issues nothing.

A RECURRING offer (0390) with live adhesions is never archived by hiding its page while debits continue: without a body it answers 409 `collect_link_has_active_subscriptions`. With `{"subscriptions":"cancel"}` the offer is paused first, every live adhesion is cancelled commercially, and only then is the link archived; a cancel that is refused or uncertain leaves it paused (409 `collect_link_archive_incomplete`, `pending`). To stop selling and keep billing, pause the link instead.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `subscriptions` | `"cancel"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | A recurring offer still has adhesions that charge. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `changed` | `boolean` | yes | False when the link was already in the requested state (the call was a no-op). |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/links/{linkId}/archive \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "subscriptions": "cancel"
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/links/{linkId}/archive HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "subscriptions": "cancel"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/links/{linkId}/archive",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "subscriptions": "cancel"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/archive", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "subscriptions": "cancel"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.post("/v1/collect/links/{linkId}/archive", {
  path: {
    linkId: "link_0000000000000000"
  },
  body: {
    subscriptions: "cancel"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  },
  "changed": true
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/links/{linkId}/archive" body={"{\"subscriptions\":\"cancel\"}"} />

### GET `/v1/collect/links/{linkId}/attempts`

<Endpoint method="GET" path="/v1/collect/links/{linkId}/attempts" base="https://api.codespar.dev" />

List a Collect link's attempts

Newest first. No payer data and no copia-e-cola: an attempt's state, charge, amount and due date only.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links/{linkId}/attempts \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links/{linkId}/attempts HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links/{linkId}/attempts",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/attempts", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links/{linkId}/attempts", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "data": [
    {
      "id": "obj_0000000000000000",
      "object": "collect_attempt",
      "link_id": "link_0000000000000000",
      "version": 0,
      "surface": "page",
      "state": "issuing",
      "charge_id": "charge_0000000000000000",
      "amount_minor": 1000,
      "currency": "BRL",
      "due_date": "2026-01-15",
      "expires_at": "string",
      "ready_at": "string",
      "superseded_by": "string",
      "failure_code": "string",
      "created_at": "string",
      "closed_at": "string"
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links/{linkId}/attempts" />

### POST `/v1/collect/links/{linkId}/attempts`

<Endpoint method="POST" path="/v1/collect/links/{linkId}/attempts" base="https://api.codespar.dev" />

Issue a Pix attempt for the partner's channel

Issues a payable Pix with due date on a published link, for the partner's own channel (surface `whatsapp` by default). Same rules as the hosted page's POST: the amount is the published version's, the payer fields not prefilled are sent here, one live attempt per link (`collect_attempt_exists`), and `renew: true` replaces the open attempt only after its charge was cancelled at the issuer. `idempotency_key` (or the `Idempotency-Key` header) makes a retry answer the same attempt.

The attempt starts `issuing`: the copia-e-cola appears 30 s to 60 min later. Read it with the artifact route, or subscribe to `collect.attempt.ready`.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `idempotency_key` | `string` | no | — |
| `payer` | object | no | — |
| `renew` | `boolean` | no | — |
| `surface` | `"whatsapp"` \| `"page"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `201` | object | OK |
| `400` | object | Bad Request. |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | Conflict. |
| `410` | object | The link is past its validity. |
| `422` | object | The issuer refused the charge before it existed; `details.failure_code` says why. |
| `429` | object | A test project that reaches Collect by capability: its sandbox calls reached their hourly or daily limit. Nothing was created or sent; `Retry-After` and `details.retry_after` give the wait. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `charge_id` | `string,null` | yes | — |
| `closed_at` | `string,null` | yes | — |
| `created_at` | `string` | yes | — |
| `currency` | `"BRL"` | yes | — |
| `due_date` | `string` | yes | — |
| `expires_at` | `string` | yes | — |
| `failure_code` | `string,null` | yes | — |
| `id` | `string` | yes | `cla_` + 128 random bits. The charge is created under `idempotency_key = collect:<id>`. |
| `link_id` | `string` | yes | — |
| `object` | `"collect_attempt"` | yes | — |
| `ready_at` | `string,null` | yes | — |
| `replay` | `true` | yes | — |
| `state` | `"issuing"` \| `"open"` \| `"superseded"` \| `"expired"` \| `"cancelled"` \| `"paid"` \| `"failed"` | yes | `issuing` until the copia-e-cola exists (30 s to 60 min after the charge is created); `open` once it does; `failed` when it never did (`failure_code`). |
| `superseded` | `string,null` | yes | — |
| `superseded_by` | `string,null` | yes | — |
| `surface` | `"page"` \| `"whatsapp"` | yes | — |
| `version` | `integer` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/links/{linkId}/attempts \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "surface": "whatsapp",
       "payer": {},
       "renew": false,
       "idempotency_key": "string"
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/links/{linkId}/attempts HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "surface": "whatsapp",
  "payer": {},
  "renew": false,
  "idempotency_key": "string"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/links/{linkId}/attempts",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "surface": "whatsapp",
      "payer": {},
      "renew": False,
      "idempotency_key": "string"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/attempts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "surface": "whatsapp",
    "payer": {},
    "renew": false,
    "idempotency_key": "string"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/links/{linkId}/attempts", {
  path: {
    linkId: "link_0000000000000000"
  },
  body: {
    surface: "whatsapp",
    payer: {},
    renew: false,
    idempotency_key: "string"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_attempt",
  "link_id": "link_0000000000000000",
  "version": 0,
  "surface": "page",
  "state": "issuing",
  "charge_id": "charge_0000000000000000",
  "amount_minor": 1000,
  "currency": "BRL",
  "due_date": "2026-01-15",
  "expires_at": "string",
  "ready_at": "string",
  "superseded_by": "string",
  "failure_code": "string",
  "created_at": "string",
  "closed_at": "string",
  "replay": true,
  "superseded": "string"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/links/{linkId}/attempts" body={"{\"surface\":\"whatsapp\",\"payer\":{},\"renew\":false,\"idempotency_key\":\"string\"}"} />

### PUT `/v1/collect/links/{linkId}/draft`

<Endpoint method="PUT" path="/v1/collect/links/{linkId}/draft" base="https://api.codespar.dev" />

Replace the draft of a Collect link

Replaces the open draft, or opens a new draft after the last publish. A published version never changes. Refused on an archived link, and on a link that already issued a Pix to a payer (`collect_link_version_locked`): a single-use link keeps the version it was issued under.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Request body**

Type: object \| object

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. The body did not match the schema (`invalid_body`, with `details.issues`), or the version breaks a rule the schema cannot state: the items total below the R$5.00 a Pix with due date needs (`collect_total_below_minimum`), above R$1,000,000.00 (`collect_total_above_maximum`), or brand colors whose contrast is below 4.5:1 (`collect_brand_contrast_insufficient`), or a `usdc_price` on a version without the agent surface (`collect_usdc_price_requires_agent`). Unknown keys are refused, not dropped: the regulated payer fields (document, address) are never free-text questions. |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | Conflict. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X PUT https://api.codespar.dev/v1/collect/links/{linkId}/draft \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "title": "Example",
       "description": "string",
       "success_message": "string",
       "items": [
         {
           "name": "Example",
           "quantity": 0,
           "unit_amount_minor": 1000
         }
       ],
       "brand": {
         "logo_url": "https://example.com/hook",
         "color": "string",
         "text_color": "string",
         "texts": {
           "header": "string",
           "footer": "string"
         }
       },
       "surfaces": [
         "page"
       ],
       "due_in_days": 0,
       "payer_fields": {
         "contact": "optional",
         "external_reference": "off"
       },
       "usdc_price": "string"
     }'
```

</Tab>
<Tab value="HTTP">

```http
PUT /v1/collect/links/{linkId}/draft HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "title": "Example",
  "description": "string",
  "success_message": "string",
  "items": [
    {
      "name": "Example",
      "quantity": 0,
      "unit_amount_minor": 1000
    }
  ],
  "brand": {
    "logo_url": "https://example.com/hook",
    "color": "string",
    "text_color": "string",
    "texts": {
      "header": "string",
      "footer": "string"
    }
  },
  "surfaces": [
    "page"
  ],
  "due_in_days": 0,
  "payer_fields": {
    "contact": "optional",
    "external_reference": "off"
  },
  "usdc_price": "string"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.put(
    "https://api.codespar.dev/v1/collect/links/{linkId}/draft",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "title": "Example",
      "description": "string",
      "success_message": "string",
      "items": [
        {
          "name": "Example",
          "quantity": 0,
          "unit_amount_minor": 1000
        }
      ],
      "brand": {
        "logo_url": "https://example.com/hook",
        "color": "string",
        "text_color": "string",
        "texts": {
          "header": "string",
          "footer": "string"
        }
      },
      "surfaces": [
        "page"
      ],
      "due_in_days": 0,
      "payer_fields": {
        "contact": "optional",
        "external_reference": "off"
      },
      "usdc_price": "string"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/draft", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "brand": {
      "logo_url": "https://example.com/hook",
      "color": "string",
      "text_color": "string",
      "texts": {
        "header": "string",
        "footer": "string"
      }
    },
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "payer_fields": {
      "contact": "optional",
      "external_reference": "off"
    },
    "usdc_price": "string"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.put("/v1/collect/links/{linkId}/draft", {
  path: {
    linkId: "link_0000000000000000"
  },
  body: {
    title: "Example",
    description: "string",
    success_message: "string",
    items: [
      {
        name: "Example",
        quantity: 0,
        unit_amount_minor: 1000
      }
    ],
    brand: {
      logo_url: "https://example.com/hook",
      color: "string",
      text_color: "string",
      texts: {
        header: "string",
        footer: "string"
      }
    },
    surfaces: [
      "page"
    ],
    due_in_days: 0,
    payer_fields: {
      contact: "optional",
      external_reference: "off"
    },
    usdc_price: "string"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  }
}
```

</SplitPane>
</Split>

<TryIt method="PUT" path="/v1/collect/links/{linkId}/draft" body={"{\"title\":\"Example\",\"description\":\"string\",\"success_message\":\"string\",\"items\":[{\"name\":\"Example\",\"quantity\":0,\"unit_amount_minor\":1000}],\"brand\":{\"logo_url\":\"https://example.com/hook\",\"color\":\"string\",\"text_color\":\"string\",\"texts\":{\"header\":\"string\",\"footer\":\"string\"}},\"surfaces\":[\"page\"],\"due_in_days\":0,\"payer_fields\":{\"contact\":\"optional\",\"external_reference\":\"off\"},\"usdc_price\":\"string\"}"} />

### POST `/v1/collect/links/{linkId}/pause`

<Endpoint method="POST" path="/v1/collect/links/{linkId}/pause" base="https://api.codespar.dev" />

Pause a Collect link

No new attempt is issued while paused, and payers see the link as paused. An attempt already issued can still be paid; that payment is credited. Emits `collect.link.paused`.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | Conflict: the link is not published. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `changed` | `boolean` | yes | False when the link was already in the requested state (the call was a no-op). |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/links/{linkId}/pause \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/links/{linkId}/pause HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/links/{linkId}/pause",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/pause", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.post("/v1/collect/links/{linkId}/pause", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  },
  "changed": true
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/links/{linkId}/pause" />

### GET `/v1/collect/links/{linkId}/payments`

<Endpoint method="GET" path="/v1/collect/links/{linkId}/payments" base="https://api.codespar.dev" />

List a Collect link's payments

Every payment of the link's charges: the one `paid` (a single-use link has at most one) and any exceptions. Each is recorded in the same transaction as the wallet credit, keyed on the charge id, and carries its signed receipt once minted, its `status` and the evidence behind it (`confirmation`, D-A).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links/{linkId}/payments \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links/{linkId}/payments HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links/{linkId}/payments",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/payments", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links/{linkId}/payments", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "data": [
    {
      "id": "obj_0000000000000000",
      "object": "collect_payment",
      "kind": "paid",
      "exception": "late",
      "link_id": "link_0000000000000000",
      "attempt_id": "attempt_0000000000000000",
      "surface": "page",
      "version": 0,
      "charge_id": "charge_0000000000000000",
      "amount_minor": 1000,
      "currency": "BRL",
      "paid_at": "string",
      "rail": "pix",
      "end_to_end_id": "endtoend_0000000000000000",
      "ledger_entry_id": "ledgerentry_0000000000000000",
      "simulated": true,
      "receipt": {
        "id": "obj_0000000000000000",
        "object": "collect_receipt",
        "document": {
          "v": 1,
          "receipt_id": "receipt_0000000000000000",
          "issuer": "string",
          "charge_id": "charge_0000000000000000",
          "link_id": "link_0000000000000000",
          "link_version": 0,
          "amount_minor": 1000,
          "currency": "BRL",
          "paid_at": "string",
          "rail": "pix",
          "end_to_end_id": "endtoend_0000000000000000",
          "ledger_entry_id": "ledgerentry_0000000000000000"
        },
        "digest": "string",
        "sig": "string",
        "kid": "string",
        "recipe": {}
      },
      "status": "paid_confirmed",
      "label": "Pago · confirmado pela Celcoin",
      "confirmation": {
        "source": "provider_webhook",
        "confirmed_at": "string",
        "checks": {
          "charge": true,
          "amount": true,
          "currency": true,
          "receiver": true
        },
        "confirmed": true
      },
      "decide_by": "string"
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links/{linkId}/payments" />

### POST `/v1/collect/links/{linkId}/publish`

<Endpoint method="POST" path="/v1/collect/links/{linkId}/publish" base="https://api.codespar.dev" />

Publish a Collect link

Freezes the draft as the version in force and puts the link in front of payers; emits `collect.link.published`.

GATED ON THE RECEIVER. The owner's consumer must be able to receive a Pix with due date in the link's environment: its own receiving identity, or, in a test project, the shared-sandbox receiver seeded on this deployment. Otherwise the publish is refused with 422 `receiving_identity_missing` before anything is written, and the link stays a draft. A link already published with no new draft answers 200 with `changed: false`.

CONFIRMED USDC PRICE (N12). A draft with a `usdc_price` publishes only when the body confirms that exact price (`confirm_usdc_price`); the confirmation is stored with the version (`usdc_price_confirmed_at`). A missing or different price, or a price confirmed for a draft that has none, answers 409 `collect_usdc_price_unconfirmed` with the draft's price and the statement the tenant confirms, and nothing is published. A different price is a new version and asks again.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `confirm_usdc_price` | `string,null` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. The body did not match the schema (`invalid_body`, with `details.issues`), or the version breaks a rule the schema cannot state: the items total below the R$5.00 a Pix with due date needs (`collect_total_below_minimum`), above R$1,000,000.00 (`collect_total_above_maximum`), or brand colors whose contrast is below 4.5:1 (`collect_brand_contrast_insufficient`), or a `usdc_price` on a version without the agent surface (`collect_usdc_price_requires_agent`). Unknown keys are refused, not dropped: the regulated payer fields (document, address) are never free-text questions. |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | Conflict. |
| `422` | object | The owner's consumer has no receiving identity in this environment. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `changed` | `boolean` | yes | False when the link was already in the requested state (the call was a no-op). |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/links/{linkId}/publish \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "confirm_usdc_price": "string"
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/links/{linkId}/publish HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "confirm_usdc_price": "string"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/links/{linkId}/publish",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "confirm_usdc_price": "string"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/publish", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "confirm_usdc_price": "string"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/links/{linkId}/publish", {
  path: {
    linkId: "link_0000000000000000"
  },
  body: {
    confirm_usdc_price: "string"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  },
  "changed": true
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/links/{linkId}/publish" body={"{\"confirm_usdc_price\":\"string\"}"} />

### GET `/v1/collect/links/{linkId}/refunds`

<Endpoint method="GET" path="/v1/collect/links/{linkId}/refunds" base="https://api.codespar.dev" />

The refund obligations of a link's exception payments

A payment that is not the link's one valid payment (`late` or `duplicate`) is never kept as credit by default. Its refund obligation is recorded in the same transaction as the credit. A duplicate is due for refund at once. A late payment waits for the collector until `decide_by`, 72 hours from when Collect recorded it (D-D; a row recorded before keeps the deadline it was given), and is due for refund if not honored. A late payment of an obligation version that already has a newer version opens no window: it is due at once (`decided_by: rule:obligation_has_substitute`). Accepting and the lapse exclude each other. Events: `collect.refund.awaiting_decision`, `collect.refund.required` (with `refund_method` and `manual`), `collect.refund.honored`. No money moves yet: a Pix refund waits in `awaiting_pix_rail`, and a boleto one is `manual_refund_required`.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links/{linkId}/refunds \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links/{linkId}/refunds HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links/{linkId}/refunds",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/refunds", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links/{linkId}/refunds", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "data": [
    {
      "id": "obj_0000000000000000",
      "object": "collect_refund",
      "payment_id": "payment_0000000000000000",
      "link_id": "link_0000000000000000",
      "charge_id": "charge_0000000000000000",
      "reason": "late",
      "refund_method": "pix",
      "state": "awaiting_collector",
      "amount_minor": 1000,
      "currency": "BRL",
      "decide_by": "string",
      "decided_at": "string",
      "created_at": "string"
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links/{linkId}/refunds" />

### POST `/v1/collect/links/{linkId}/resume`

<Endpoint method="POST" path="/v1/collect/links/{linkId}/resume" base="https://api.codespar.dev" />

Resume a paused Collect link

Publishes the link again, re-checking the receiving identity first; emits `collect.link.published` with `resumed: true`.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |
| `409` | object | Conflict: the link is not paused. |
| `422` | object | The owner's consumer has no receiving identity in this environment. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `agent` | `object,null` | no | The agent surface (402), on the single-link read: null when the version in force does not enable it. |
| `archived_at` | `string,null` | yes | — |
| `changed` | `boolean` | yes | False when the link was already in the requested state (the call was a no-op). |
| `consumer_id` | `string` | yes | The receiving consumer: the charge settles into its wallet. |
| `created_at` | `string` | yes | — |
| `current_version` | `integer,null` | yes | — |
| `draft` | `object,null` | yes | The open draft, if any. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `id` | `string` | yes | `cl_` + 128 random bits. Minted by the server; never chosen by the caller. |
| `object` | `"collect_link"` | yes | — |
| `paid` | `boolean` | yes | Whether the link has its paid payment, whenever it settled (not bound to any stats window). A late or duplicate payment is not the link's payment and does not make it paid. |
| `paid_at` | `string,null` | yes | When the paid payment settled; null while unpaid. |
| `paused_at` | `string,null` | yes | — |
| `payer_document_masked` | `string,null` | yes | The prefilled CPF/CNPJ, last four characters only. |
| `payer_prefilled` | array of `"name"` \| `"contact"` \| `"external_reference"` \| `"document"` \| `"address"` | yes | Which payer fields were prefilled at create. Their values are never returned. |
| `published_at` | `string,null` | yes | — |
| `receiver` | object | yes | The name the payer sees on the hosted page: the consumer's display name, else the organization's name. The same text the public read answers. |
| `receiver_resolution` | `"explicit"` \| `"project_default"` \| `"only_eligible"` | yes | D-B: how `consumer_id` was chosen: named by the caller, the project's configured default receiver, or the project's only consumer with its own Celcoin receiving identity. Fixed at create: a default changed later moves no link. |
| `reference` | `string,null` | yes | D-C: the caller's obligation this link charges (e.g. `bill:<period>:<customer>`); null without one. |
| `reference_version` | `string,null` | yes | D-C: the version of the obligation this link was created for. |
| `state` | `"draft"` \| `"published"` \| `"paused"` \| `"archived"` | yes | — |
| `updated_at` | `string` | yes | — |
| `url` | `string` | yes | The hosted page for this link. |
| `valid_until` | `string,null` | yes | — |
| `version` | `object,null` | yes | The published version in force. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/links/{linkId}/resume \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/links/{linkId}/resume HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/links/{linkId}/resume",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/resume", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/links/{linkId}/resume", {
  path: {
    linkId: "link_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_link",
  "state": "draft",
  "environment": "live",
  "consumer_id": "csm_0000000000000000",
  "receiver": {
    "name": "Example"
  },
  "paid": true,
  "paid_at": "string",
  "current_version": 0,
  "valid_until": "string",
  "url": "https://example.com/hook",
  "payer_prefilled": [
    "name"
  ],
  "payer_document_masked": "string",
  "version": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "draft": {
    "version": 0,
    "status": "draft",
    "title": "Example",
    "description": "string",
    "success_message": "string",
    "items": [
      {
        "name": "Example",
        "quantity": 0,
        "unit_amount_minor": 1000
      }
    ],
    "total_minor": 1,
    "currency": "BRL",
    "brand": {},
    "surfaces": [
      "page"
    ],
    "due_in_days": 0,
    "due_on": "string",
    "payer_fields": {
      "name": "required",
      "contact": "required",
      "external_reference": "required"
    },
    "usdc_price": "string",
    "usdc_price_atomic": "string",
    "usdc_price_confirmed_at": "string",
    "created_at": "string",
    "published_at": "string"
  },
  "created_at": "string",
  "updated_at": "string",
  "published_at": "string",
  "paused_at": "string",
  "archived_at": "string",
  "reference": "string",
  "reference_version": "string",
  "receiver_resolution": "explicit",
  "agent": {
    "url": "https://example.com/hook",
    "status": "ready",
    "reason": "string",
    "challenge": {},
    "refusal": {
      "code": "string",
      "message": "string",
      "http_status": 0
    },
    "usdc": {
      "price": "string",
      "amount": "1000",
      "currency": "USDC",
      "network": "string",
      "asset": "string",
      "payable": false,
      "reason": "usdc_pay_to_unavailable",
      "message": "string",
      "statement": "string"
    }
  },
  "changed": true
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/links/{linkId}/resume" />

### GET `/v1/collect/links/{linkId}/subscriptions`

<Endpoint method="GET" path="/v1/collect/links/{linkId}/subscriptions" base="https://api.codespar.dev" />

The adhesions of a recurring offer

Newest first, each with the read model: where the payer's authorization stands, the next charge, the payments received and the failed charges, kept apart. `state` filters by the derived state.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `before` | `string (date-time)` | no | — |
| `limit` | `integer` | no | — |
| `state` | `"issuing"` \| `"failed"` \| `"awaiting_payment"` \| `"expired"` \| `"payment_received"` \| `"active"` \| `"authorization_denied"` \| `"authorization_expired"` \| `"authorization_cancelled"` \| `"cancelling"` \| `"cancelled"` \| `"ended"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad query. |
| `404` | object | Not Found. No link with that id in this project. A link of another project answers identically. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |
| `next_before` | `string,null` | yes | — |
| `object` | `"list"` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/links/{linkId}/subscriptions \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/links/{linkId}/subscriptions HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/links/{linkId}/subscriptions",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/links/{linkId}/subscriptions", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/links/{linkId}/subscriptions", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "list",
  "data": [
    {
      "id": "obj_0000000000000000",
      "object": "collect_subscription",
      "link_id": "link_0000000000000000",
      "version": 0,
      "environment": "live",
      "state": "issuing",
      "past_due": true,
      "payer": {
        "name": "Example",
        "document_masked": "string"
      },
      "terms": {},
      "authorization": {},
      "next_charge": {
        "due_date": "2026-01-15",
        "amount_minor": 1000,
        "status": "expected"
      },
      "payments": [
        {}
      ],
      "failed_charges": [
        {}
      ],
      "totals": {
        "received_minor": 1,
        "payments": 0
      },
      "commercial_cancellation": {},
      "recurrence_id": "recurrence_0000000000000000",
      "created_at": "string",
      "updated_at": "string"
    }
  ],
  "next_before": "string"
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/links/{linkId}/subscriptions" />

### POST `/v1/collect/payments/{paymentId}/honor`

<Endpoint method="POST" path="/v1/collect/payments/{paymentId}/honor" base="https://api.codespar.dev" />

Keep a late payment instead of refunding it

The collector confirms they honor a LATE payment, before its `decide_by`. Idempotent: honoring an honored refund answers it again. A duplicate cannot be honored, and a late payment past its window is already due for refund.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `paymentId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No payment with a refund decision under that id in this project. |
| `409` | object | Not honorable. `collect_refund_obligation_conflict`: another version of the same obligation is paid, in force, or not positively cancelled, so keeping this late payment could charge the payer twice (D-D). |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `charge_id` | `string` | yes | — |
| `created_at` | `string` | yes | — |
| `currency` | `"BRL"` | yes | — |
| `decide_by` | `string,null` | yes | — |
| `decided_at` | `string,null` | yes | — |
| `id` | `string` | yes | — |
| `link_id` | `string` | yes | — |
| `object` | `"collect_refund"` | yes | — |
| `payment_id` | `string` | yes | — |
| `reason` | `"late"` \| `"duplicate"` | yes | — |
| `refund_method` | `"pix"` \| `"manual"` | yes | `pix` when the payment was a Pix; `manual` when it was a boleto or the channel is unknown. |
| `state` | `"awaiting_collector"` \| `"honored"` \| `"awaiting_pix_rail"` \| `"manual_refund_required"` | yes | `awaiting_collector`: a late payment the collector may honor until `decide_by`. `honored`: kept by the collector. `awaiting_pix_rail`: due for refund by Pix, waiting for the Pix refund of Collect charges, which is not wired yet; no money has moved. `manual_refund_required`: due for refund and there is no Pix to return, so a person refunds it outside CodeSpar. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/payments/{paymentId}/honor \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/payments/{paymentId}/honor HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/payments/{paymentId}/honor",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/payments/{paymentId}/honor", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.post("/v1/collect/payments/{paymentId}/honor", {
  path: {
    paymentId: "payment_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_refund",
  "payment_id": "payment_0000000000000000",
  "link_id": "link_0000000000000000",
  "charge_id": "charge_0000000000000000",
  "reason": "late",
  "refund_method": "pix",
  "state": "awaiting_collector",
  "amount_minor": 1000,
  "currency": "BRL",
  "decide_by": "string",
  "decided_at": "string",
  "created_at": "string"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/payments/{paymentId}/honor" />

### POST `/v1/collect/payments/{paymentId}/refund`

<Endpoint method="POST" path="/v1/collect/payments/{paymentId}/refund" base="https://api.codespar.dev" />

Refund a late payment instead of keeping it

The collector decides, before `decide_by`, that a LATE payment is refunded rather than kept. The obligation moves to its due state at once (`awaiting_pix_rail`, or `manual_refund_required` without a Pix to return). Accepting, refunding and the lapse of the window exclude each other; asking again answers the decision recorded. For an obligation with a `reference`, this decision frees a new version of it (D-C, decision (1b)) without waiting for the refund to settle.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `paymentId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No payment with a refund decision under that id in this project. |
| `409` | object | The late payment was already kept. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `charge_id` | `string` | yes | — |
| `created_at` | `string` | yes | — |
| `currency` | `"BRL"` | yes | — |
| `decide_by` | `string,null` | yes | — |
| `decided_at` | `string,null` | yes | — |
| `id` | `string` | yes | — |
| `link_id` | `string` | yes | — |
| `object` | `"collect_refund"` | yes | — |
| `payment_id` | `string` | yes | — |
| `reason` | `"late"` \| `"duplicate"` | yes | — |
| `refund_method` | `"pix"` \| `"manual"` | yes | `pix` when the payment was a Pix; `manual` when it was a boleto or the channel is unknown. |
| `state` | `"awaiting_collector"` \| `"honored"` \| `"awaiting_pix_rail"` \| `"manual_refund_required"` | yes | `awaiting_collector`: a late payment the collector may honor until `decide_by`. `honored`: kept by the collector. `awaiting_pix_rail`: due for refund by Pix, waiting for the Pix refund of Collect charges, which is not wired yet; no money has moved. `manual_refund_required`: due for refund and there is no Pix to return, so a person refunds it outside CodeSpar. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/payments/{paymentId}/refund \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/payments/{paymentId}/refund HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/payments/{paymentId}/refund",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/payments/{paymentId}/refund", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.post("/v1/collect/payments/{paymentId}/refund", {
  path: {
    paymentId: "payment_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_refund",
  "payment_id": "payment_0000000000000000",
  "link_id": "link_0000000000000000",
  "charge_id": "charge_0000000000000000",
  "reason": "late",
  "refund_method": "pix",
  "state": "awaiting_collector",
  "amount_minor": 1000,
  "currency": "BRL",
  "decide_by": "string",
  "decided_at": "string",
  "created_at": "string"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/payments/{paymentId}/refund" />

### GET `/v1/collect/receivers`

<Endpoint method="GET" path="/v1/collect/receivers" base="https://api.codespar.dev" />

The consumers that could receive a Collect charge in this project

The accounts a link may name as `consumer_id`, each decided by the publish gate's own rule in this project's environment: the consumer's own active Celcoin receiving identity (`identity: own`), or, in a test project only, the deployment's shared-sandbox receiver (`identity: shared_sandbox`). An ineligible consumer carries `reason: receiving_identity_missing`, the code a publish would answer.

The candidates are the consumers this project already knows: an active directed-pay wallet in the project, a Pix (Celcoin) funding source attached to it, or one of its Collect links. At most 200, by consumer id.

A test project that reaches Collect by its `collect` capability instead of the deployment's list offers only its consumers with a sandbox account bound to the project (`identity: bound`), never the shared-sandbox receiver. One not ready yet names why in `not_ready` (`pix_key_pending`: the account exists and its Pix key is not registered yet). When exactly one is eligible it carries `default: true`; with several, the link's `consumer_id` is the explicit choice.

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object | yes | — |
| `environment` | `"live"` \| `"test"` | yes | The environment eligibility was decided for: this project's. |
| `object` | `"list"` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/receivers \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/receivers HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/receivers",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/receivers", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/receivers");
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "list",
  "environment": "live",
  "data": [
    {
      "consumer_id": "csm_0000000000000000",
      "label": "Example",
      "eligible": true,
      "identity": "own",
      "reason": "receiving_identity_missing",
      "not_ready": "receiver_not_bound",
      "default": true,
      "holder": {
        "name": "Example",
        "person_type": "PF",
        "document_masked": "string"
      },
      "destination": {
        "kind": "project_account",
        "space_id": "space_0000000000000000"
      },
      "recurring": {
        "eligible": true,
        "reason": "receiver_not_legal_person"
      }
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/receivers" />

### GET `/v1/collect/receivers/default`

<Endpoint method="GET" path="/v1/collect/receivers/default" base="https://api.codespar.dev" />

The project's default receiver for links that name none

D-B: the consumer Collect gives a link created without `consumer_id`. Null when the project has none; then the project's only consumer with its own Celcoin receiving identity is used, and with zero or several the create is refused with what to configure.

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `consumer_id` | `string,null` | yes | The project's default receiving consumer; null when none is configured. |
| `object` | `"collect_receiver_default"` | yes | — |
| `set_by` | `string,null` | yes | — |
| `updated_at` | `string,null` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/receivers/default \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/receivers/default HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/receivers/default",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/receivers/default", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/receivers/default");
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "collect_receiver_default",
  "consumer_id": "csm_0000000000000000",
  "set_by": "string",
  "updated_at": "string"
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/receivers/default" />

### PUT `/v1/collect/receivers/default`

<Endpoint method="PUT" path="/v1/collect/receivers/default" base="https://api.codespar.dev" />

Configure (or clear) the project's default receiver

D-B. `consumer_id` must have its own active Celcoin receiving identity in THIS project (a `pix-celcoin` funding source with account and document); the shared-sandbox receiver and other projects' consumers never qualify. `null` clears it. Requires `collect:write`, the permission that already names a link's receiver. Changing it moves no existing link.

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `consumer_id` | `string,null` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad Request. |
| `422` | object | The consumer cannot receive here. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `consumer_id` | `string,null` | yes | The project's default receiving consumer; null when none is configured. |
| `object` | `"collect_receiver_default"` | yes | — |
| `set_by` | `string,null` | yes | — |
| `updated_at` | `string,null` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X PUT https://api.codespar.dev/v1/collect/receivers/default \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "consumer_id": "csm_0000000000000000"
     }'
```

</Tab>
<Tab value="HTTP">

```http
PUT /v1/collect/receivers/default HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "consumer_id": "csm_0000000000000000"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.put(
    "https://api.codespar.dev/v1/collect/receivers/default",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "consumer_id": "csm_0000000000000000"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/receivers/default", {
  method: "PUT",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "consumer_id": "csm_0000000000000000"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("put", "/v1/collect/receivers/default", {
  body: {
    consumer_id: "csm_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "collect_receiver_default",
  "consumer_id": "csm_0000000000000000",
  "set_by": "string",
  "updated_at": "string"
}
```

</SplitPane>
</Split>

<TryIt method="PUT" path="/v1/collect/receivers/default" body={"{\"consumer_id\":\"csm_0000000000000000\"}"} />

### GET `/v1/collect/subscriptions/{subscriptionId}`

<Endpoint method="GET" path="/v1/collect/subscriptions/{subscriptionId}" base="https://api.codespar.dev" />

One adhesion of a recurring offer

The same view as the list, with every cycle (`cycles`).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `404` | object | No adhesion with that id in this project. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `authorization` | object | yes | — |
| `commercial_cancellation` | `object,null` | yes | — |
| `created_at` | `string` | yes | — |
| `environment` | `"live"` \| `"test"` | yes | — |
| `failed_charges` | array of object | yes | — |
| `id` | `string` | yes | cls_ + 22 characters |
| `link_id` | `string` | yes | — |
| `next_charge` | `object,null` | yes | — |
| `object` | `"collect_subscription"` | yes | — |
| `past_due` | `boolean` | yes | Active, and the most recent charge due by today failed. The adhesion is still authorized. |
| `payer` | object | yes | — |
| `payments` | array of object | yes | The first payment (`initial`) and every paid cycle (`cycle`). |
| `recurrence_id` | `string,null` | yes | — |
| `state` | `"issuing"` \| `"failed"` \| `"awaiting_payment"` \| `"expired"` \| `"payment_received"` \| `"active"` \| `"authorization_denied"` \| `"authorization_expired"` \| `"authorization_cancelled"` \| `"cancelling"` \| `"cancelled"` \| `"ended"` | yes | Derived from the recurrence: `active` only once the payer's bank reported the authorization ACCEPTED; `authorization_cancelled` is the payer's or the bank's cancellation; `cancelling` / `cancelled` the seller's; `authorization_expired` a paid adhesion whose authorization never arrived by its deadline (the payment stays credited). |
| `terms` | object | yes | — |
| `totals` | object | yes | — |
| `updated_at` | `string` | yes | — |
| `version` | `integer` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/subscriptions/{subscriptionId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/subscriptions/{subscriptionId}", {
  path: {
    subscriptionId: "subscription_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_subscription",
  "link_id": "link_0000000000000000",
  "version": 0,
  "environment": "live",
  "state": "issuing",
  "past_due": true,
  "payer": {
    "name": "Example",
    "document_masked": "string"
  },
  "terms": {},
  "authorization": {},
  "next_charge": {
    "due_date": "2026-01-15",
    "amount_minor": 1000,
    "status": "expected"
  },
  "payments": [
    {}
  ],
  "failed_charges": [
    {}
  ],
  "totals": {
    "received_minor": 1,
    "payments": 0
  },
  "commercial_cancellation": {},
  "recurrence_id": "recurrence_0000000000000000",
  "created_at": "string",
  "updated_at": "string"
}
```

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/subscriptions/{subscriptionId}" />

### POST `/v1/collect/subscriptions/{subscriptionId}/cancel`

<Endpoint method="POST" path="/v1/collect/subscriptions/{subscriptionId}/cancel" base="https://api.codespar.dev" />

Cancel an adhesion commercially

The seller's cancellation. Recorded before anything is sent; then every charge already scheduled at the payer's bank whose cancel window is open (until 23:59 of the day before) is cancelled, then the authorization itself. 202 with `state: cancelling` once the bank accepted the request; the confirmation arrives from the bank and moves it to `cancelled`. Asking again answers the current state.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `reason` | `"customer_request"` \| `"non_payment"` \| `"offer_discontinued"` \| `"fraud_suspected"` \| `"other"` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `202` | object | OK |
| `400` | object | Bad Request. The body did not match the schema (`invalid_body`, with `details.issues`), or the version breaks a rule the schema cannot state: the items total below the R$5.00 a Pix with due date needs (`collect_total_below_minimum`), above R$1,000,000.00 (`collect_total_above_maximum`), or brand colors whose contrast is below 4.5:1 (`collect_brand_contrast_insufficient`), or a `usdc_price` on a version without the agent surface (`collect_usdc_price_requires_agent`). Unknown keys are refused, not dropped: the regulated payer fields (document, address) are never free-text questions. |
| `404` | object | No adhesion with that id in this project. |
| `409` | object | Not cancellable now. |
| `422` | object | The bank refused the cancellation. |
| `429` | object | This project's sandbox calls reached their limit; nothing was sent. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `authorization` | object | yes | — |
| `commercial_cancellation` | `object,null` | yes | — |
| `created_at` | `string` | yes | — |
| `cycles_cancelled` | array of `string` | yes | Due dates of the scheduled charges cancelled with the adhesion. |
| `cycles_not_cancellable` | array of object | yes | Scheduled charges that were not cancelled, each with a closed reason. |
| `environment` | `"live"` \| `"test"` | yes | — |
| `failed_charges` | array of object | yes | — |
| `id` | `string` | yes | cls_ + 22 characters |
| `link_id` | `string` | yes | — |
| `next_charge` | `object,null` | yes | — |
| `object` | `"collect_subscription"` | yes | — |
| `past_due` | `boolean` | yes | Active, and the most recent charge due by today failed. The adhesion is still authorized. |
| `payer` | object | yes | — |
| `payments` | array of object | yes | The first payment (`initial`) and every paid cycle (`cycle`). |
| `recurrence_id` | `string,null` | yes | — |
| `state` | `"issuing"` \| `"failed"` \| `"awaiting_payment"` \| `"expired"` \| `"payment_received"` \| `"active"` \| `"authorization_denied"` \| `"authorization_expired"` \| `"authorization_cancelled"` \| `"cancelling"` \| `"cancelled"` \| `"ended"` | yes | Derived from the recurrence: `active` only once the payer's bank reported the authorization ACCEPTED; `authorization_cancelled` is the payer's or the bank's cancellation; `cancelling` / `cancelled` the seller's; `authorization_expired` a paid adhesion whose authorization never arrived by its deadline (the payment stays credited). |
| `terms` | object | yes | — |
| `totals` | object | yes | — |
| `updated_at` | `string` | yes | — |
| `version` | `integer` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}/cancel \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "reason": "customer_request"
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/subscriptions/{subscriptionId}/cancel HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "reason": "customer_request"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}/cancel",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "reason": "customer_request"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "reason": "customer_request"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/subscriptions/{subscriptionId}/cancel", {
  path: {
    subscriptionId: "subscription_0000000000000000"
  },
  body: {
    reason: "customer_request"
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "object": "collect_subscription",
  "link_id": "link_0000000000000000",
  "version": 0,
  "environment": "live",
  "state": "issuing",
  "past_due": true,
  "payer": {
    "name": "Example",
    "document_masked": "string"
  },
  "terms": {},
  "authorization": {},
  "next_charge": {
    "due_date": "2026-01-15",
    "amount_minor": 1000,
    "status": "expected"
  },
  "payments": [
    {}
  ],
  "failed_charges": [
    {}
  ],
  "totals": {
    "received_minor": 1,
    "payments": 0
  },
  "commercial_cancellation": {},
  "recurrence_id": "recurrence_0000000000000000",
  "created_at": "string",
  "updated_at": "string",
  "cycles_cancelled": [
    "string"
  ],
  "cycles_not_cancellable": [
    {
      "due_date": "2026-01-15",
      "reason": "cancel_window_closed"
    }
  ]
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/subscriptions/{subscriptionId}/cancel" body={"{\"reason\":\"customer_request\"}"} />

### POST `/v1/collect/subscriptions/{subscriptionId}/test-pay`

<Endpoint method="POST" path="/v1/collect/subscriptions/{subscriptionId}/test-pay" base="https://api.codespar.dev" money="pays the adhesion's journey 3 QR FOR REAL at the Celcoin sandbox" />

Pay a Test adhesion's first payment from the shared Test payer (Pagar (teste))

The payer button for a recurring offer: pays the adhesion's journey 3 QR FOR REAL at the Celcoin sandbox, from the operator's Test payer account, the way a customer would pay it. Test projects only.

No body: the amount and the destination come from the stored adhesion and its recurrence. One payment per adhesion; a second call answers 409 `test_pay_already_used`. Capped per payment and per project per 24 hours, together with the charges' Test payments. Paying is not the authorization: the adhesion turns active only when the bank's journey 3 ACCEPTED arrives, and the payment counts only when its credit arrives, never from this answer.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `subscriptionId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | A body was sent, or the credential names no project. |
| `403` | object | Not a Test credential or project, not an admin, or Collect is off. |
| `404` | object | No adhesion with that id in this project. |
| `409` | object | The adhesion is not awaiting its first payment (`details.reason`: not_test, not_issued, failed, cancelled, first_payment_received, recurrence_closed, qr_expired), already had its Test payment, or is above the cap. |
| `429` | object | The project used its Test payments for the last 24 hours. |
| `502` | object | A step at Celcoin failed; the adhesion keeps its claim. |
| `503` | object | No Test payer or Celcoin connection on this deployment. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `end_to_end_id` | `string,null` | yes | — |
| `object` | `"collect_subscription_test_payment"` | yes | — |
| `status` | `string` | yes | Celcoin's answer to the Pix, usually PROCESSING; not a settlement. |
| `subscription_id` | `string` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}/test-pay \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/subscriptions/{subscriptionId}/test-pay HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}/test-pay",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/subscriptions/{subscriptionId}/test-pay", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/subscriptions/{subscriptionId}/test-pay", {
  path: {
    subscriptionId: "subscription_0000000000000000"
  }
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "object": "collect_subscription_test_payment",
  "subscription_id": "subscription_0000000000000000",
  "amount_minor": 1000,
  "status": "string",
  "end_to_end_id": "endtoend_0000000000000000"
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/subscriptions/{subscriptionId}/test-pay" />

### GET `/v1/collect/{linkId}`

<Endpoint method="GET" path="/v1/collect/{linkId}" base="https://api.codespar.dev" open />

The hosted page's read of a Collect link (public)

No credential: the link id is the only thing the payer holds. Returns what a payer needs to pay and nothing else: the receiver's display name, items and total, brand, state, which payer fields the page must ask for (`payer_form`; a prefilled field reads `prefilled` and its value is never returned), the current attempt (its copia-e-cola only once `open`) and, once paid, the payment with its signed receipt. `environment` (`live` or `test`) is the link's: a test link moves no real money, and the page says so only when this field does. Rate limited per client IP and per link. A draft link answers 404; an archived, unpaid one 410.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | The id is not a collect link id (`cl_` + 22 characters). |
| `404` | object | No published link with that id. |
| `410` | object | The owner withdrew the link. |
| `429` | object | Too Many Requests. Per client IP and per link, shared by every API replica; `Retry-After` and `details.retry_after` give the wait, `details.scope` names which bucket refused. In a test project that reaches Collect by capability, `collect_call_budget_wait` says the project's sandbox calls reached their hourly or daily limit: nothing was created or sent, and `Retry-After` gives the wait. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X GET https://api.codespar.dev/v1/collect/{linkId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
```

</Tab>
<Tab value="HTTP">

```http
GET /v1/collect/{linkId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/collect/{linkId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/{linkId}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.get("/v1/collect/{linkId}", {
  path: {
    linkId: "link_0000000000000000"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
</Split>

<TryIt method="GET" path="/v1/collect/{linkId}" />

### POST `/v1/collect/{linkId}/attempts`

<Endpoint method="POST" path="/v1/collect/{linkId}/attempts" base="https://api.codespar.dev" open mayMove="every new attempt is a charge at the issuer" />

Ask a Collect link for a payable Pix (public)

The hosted page's POST. No credential. The body is strict: `page_session` (the page's idempotency key: the same session answers the same attempt, 200 with `replay: true`), the payer fields the page collected (never a field the charger prefilled), and `renew`. There is no amount field: the charge carries the published version's total. Payer data goes in this body only, never in a URL. Rate limited per client IP and per link, more tightly than the read, because every new attempt is a charge at the issuer.

On a RECURRING offer (0390) this creates the caller's adhesion instead: a Pix Automático charge that is paid now and authorizes the next ones, answered as `object: "collect_subscription"` with the QR; the page then follows it with POST /v1/collect/\{linkId\}/subscriptions/\{subscriptionId\}. `renew` is refused there (`collect_renew_not_applicable`): an expired QR is a new adhesion under a new page session.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `page_session` | `string` | yes | — |
| `payer` | object | no | — |
| `renew` | `boolean` | no | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `201` | object | OK |
| `400` | object | Bad Request. |
| `404` | object | No published link with that id. |
| `409` | object | Conflict. |
| `410` | object | The link is past its validity. |
| `422` | object | The issuer refused the charge before it existed. |
| `429` | object | Too Many Requests. Per client IP and per link, shared by every API replica; `Retry-After` and `details.retry_after` give the wait, `details.scope` names which bucket refused. In a test project that reaches Collect by capability, `collect_call_budget_wait` says the project's sandbox calls reached their hourly or daily limit: nothing was created or sent, and `Retry-After` gives the wait. |
| `503` | object | The rate-limit store could not answer, and this route fails closed because its limit bounds charges created at the issuer. `Retry-After` gives the wait. |

**Response `200`**

| Field | Type | Required | Description |
|---|---|---|---|
| `amount_minor` | `integer` | yes | — |
| `copy_paste` | `string,null` | yes | The Pix copia-e-cola. Null until the attempt is `open`: it does not exist before the issuer registers it. |
| `currency` | `"BRL"` | yes | — |
| `due_date` | `string` | yes | — |
| `expires_at` | `string` | yes | — |
| `failure_code` | `string,null` | no | — |
| `id` | `string` | yes | — |
| `ready_at` | `string,null` | yes | — |
| `replay` | `true` | yes | — |
| `retry_after` | `integer` | no | While `issuing`: seconds until the next readiness check. |
| `state` | `"issuing"` \| `"open"` \| `"superseded"` \| `"expired"` \| `"cancelled"` \| `"paid"` \| `"failed"` | yes | — |
| `surface` | `"page"` \| `"whatsapp"` | yes | — |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/{linkId}/attempts \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "page_session": "string",
       "payer": {},
       "renew": false
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/{linkId}/attempts HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "page_session": "string",
  "payer": {},
  "renew": false
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/{linkId}/attempts",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "page_session": "string",
      "payer": {},
      "renew": False
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/{linkId}/attempts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "page_session": "string",
    "payer": {},
    "renew": false
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const r = await cs.api.response("post", "/v1/collect/{linkId}/attempts", {
  path: {
    linkId: "link_0000000000000000"
  },
  body: {
    page_session: "string",
    payer: {},
    renew: false
  }
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
  console.log(r.data);
}
```

</Tab>
</Tabs>

</SplitPane>
<SplitPane label="Example response 200">

```json title="application/json"
{
  "id": "obj_0000000000000000",
  "state": "issuing",
  "surface": "page",
  "amount_minor": 1000,
  "currency": "BRL",
  "due_date": "2026-01-15",
  "expires_at": "string",
  "copy_paste": "string",
  "ready_at": "string",
  "retry_after": 0,
  "failure_code": "string",
  "replay": true
}
```

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/{linkId}/attempts" body={"{\"page_session\":\"string\",\"payer\":{},\"renew\":false}"} />

### POST `/v1/collect/{linkId}/subscriptions/{subscriptionId}`

<Endpoint method="POST" path="/v1/collect/{linkId}/subscriptions/{subscriptionId}" base="https://api.codespar.dev" open />

A payer follows their adhesion to a recurring offer (public)

No credential. Only the page session that created the adhesion reads it, sent in the body. Answers the QR while it is payable, then the payment and the authorization; `retry_after` while there is something to wait for. Any other session, an unknown id or another link's adhesion is the same 404.

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `linkId` | `string` | yes | — |
| `subscriptionId` | `string` | yes | — |

**Request body**

| Field | Type | Required | Description |
|---|---|---|---|
| `page_session` | `string` | yes | — |

**Responses**

| Status | Body | Description |
|---|---|---|
| `200` | object | OK |
| `400` | object | Bad request. |
| `404` | object | No adhesion with that id for this session. |
| `429` | object | Too Many Requests. Per client IP and per link, shared by every API replica; `Retry-After` and `details.retry_after` give the wait, `details.scope` names which bucket refused. In a test project that reaches Collect by capability, `collect_call_budget_wait` says the project's sandbox calls reached their hourly or daily limit: nothing was created or sent, and `Retry-After` gives the wait. |

<Split min={380}>
<SplitPane label="Example request">

<Tabs items={["curl","HTTP","Python","TypeScript","SDK"]}>
<Tab value="curl">

```bash
curl -X POST https://api.codespar.dev/v1/collect/{linkId}/subscriptions/{subscriptionId} \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "page_session": "string"
     }'
```

</Tab>
<Tab value="HTTP">

```http
POST /v1/collect/{linkId}/subscriptions/{subscriptionId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "page_session": "string"
}
```

</Tab>
<Tab value="Python">

```python
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/collect/{linkId}/subscriptions/{subscriptionId}",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "page_session": "string"
    },
)
res.raise_for_status()
data = res.json()
```

</Tab>
<Tab value="TypeScript">

```ts
const res = await fetch("https://api.codespar.dev/v1/collect/{linkId}/subscriptions/{subscriptionId}", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "page_session": "string"
  }),
});

const data = await res.json();
```

</Tab>
<Tab value="SDK">

```ts
const result = await cs.api.post("/v1/collect/{linkId}/subscriptions/{subscriptionId}", {
  path: {
    linkId: "link_0000000000000000",
    subscriptionId: "subscription_0000000000000000"
  },
  body: {
    page_session: "string"
  }
});
```

</Tab>
</Tabs>

</SplitPane>
</Split>

<TryIt method="POST" path="/v1/collect/{linkId}/subscriptions/{subscriptionId}" body={"{\"page_session\":\"string\"}"} />

