Skip to main content
API reference

Payment Links

Generated HTTP reference for the 5 operations the published OpenAPI document describes under payment-links.

11 min read
View MarkdownEdit on GitHub

Payment Links

This page is generated from the published OpenAPI document. It is complete with respect to that document and says nothing about surfaces the document does not describe yet. See what is generated here for what that means.

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

Every operation below requires a Bearer token. See Authentication.

Collect is CodeSpar's direct-charge product, and shareable payment links are its first surface. Collect is in early access.

REST API for Collect payment links. A payment link is a shareable URL where the product IS the payment: an agent reads it and pays over x402/USDC, or a person pays via a Pix QR. One link can advertise more than one settlement method (rail) at once. The gateway verifies the payment before fulfillment and seals a hash-chained receipt. Served at pay_url = https://gw.codespar.dev/pay/<slug>.

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

All endpoints require authentication. See Authentication. Endpoints marked admin require a bearer api key OR service auth with an x-codespar-user header for an account admin/owner. Collect links are project-scoped.

FieldTypeDescription
idstringPayment link ID, pl_<...>
slugstringURL slug; global namespace. Auto-generated when omitted
titlestringDisplay title
descriptionstring | nullOptional description
pay_urlstringhttps://gw.codespar.dev/pay/<slug>
acceptsAccept[]One or more settlement rails (see below)
environment"test" | "live"Environment the link settles in
one_timebooleanCloses after the first successful payment
statusstringLifecycle status
use_countnumberSuccessful payments so far
max_usesnumber | nullCap on total payments; null for unlimited
expires_atstring | nullISO 8601, or null
redirect_urlstring | nullWhere a person is sent after paying
activebooleanPaused links cannot be paid
created_atstringISO 8601

Accept (settlement rail)

accepts[] carries one entry per rail. A link can offer x402/USDC, Pix/BRL, or both; the payer uses whichever they are provisioned for.

x402 / USDC

{
  "rail": "x402",
  "amount": "0.01",
  "currency": "USDC",
  "pay_to": { "kind": "provisioned", "consumer_id": "your-consumer" }
}
  • amount is decimal USDC (up to 6 decimals).
  • pay_to.kind: "provisioned" settles to the CodeSpar wallet of consumer_id.
  • pay_to.kind: "byo" settles to pay_to.address (a 0x EVM address).

Pix / BRL

{
  "rail": "pix",
  "amount": "5.00",
  "currency": "BRL",
  "pix": { "key": "your-pix-key" }
}
  • amount is decimal BRL (up to 2 decimals).
  • pix.key (or pix.celcoin_account) is where the Pix payment lands.

POST /v1/payment-links (admin)

Body

{
  "title": "Coffee",
  "description": "One coffee",
  "accepts": [
    { "rail": "x402", "amount": "0.01", "pay_to": { "kind": "provisioned", "consumer_id": "your-consumer" } },
    { "rail": "pix", "amount": "5.00", "pix": { "key": "your-pix-key" } }
  ],
  "one_time": false,
  "max_uses": 10
}
  • slug is optional; a unique one is generated when omitted.
  • accepts must have at least one rail.
  • one_time, max_uses, expires_at, and redirect_url are all optional.

Response201 Created with the payment link object.

GET /v1/payment-links

Response

{ "payment_links": [/* payment link */] }

GET /v1/payment-links/:id — the payment link object, or 404 payment_link_not_found.

PATCH /v1/payment-links/:id (admin)

Body (at least one field)

{ "title": "...", "description": "...", "active": false, "expires_at": "2026-12-31T00:00:00Z", "redirect_url": "https://..." }

Set active: false to pause the link (it can no longer be paid); true to resume. The accepts rails are fixed at creation and are not editable.

DELETE /v1/payment-links/:id (admin)204 No Content. Collect links are config, not audit data; settled receipts are retained independently.

Errors

HTTPcodewhen
400invalid_bodybody failed validation (including a malformed x402/Pix amount)
403forbiddennon-admin caller on an admin endpoint
404payment_link_not_foundunknown id in this project
409slug_conflictthe slug is already taken

Every operation, from the spec

Generated from the published OpenAPI document, so it never drifts from what the API actually serves. The section above is written by hand and carries what a schema cannot: the object model, field rules, and the order to call things in.

List the payment links in this project

Responses

StatusBodyDescription
200objectOK

Response 200

FieldTypeRequiredDescription
payment_linksarray of objectyes

Example response

{
  "payment_links": [
    {
      "id": "obj_0000000000000000",
      "project_id": "prj_0000000000000000",
      "slug": "example",
      "title": "Example",
      "pay_url": "https://example.com/hook",
      "accepts": [
        {
          "rail": "x402",
          "currency": "USDC",
          "amount_atomic": "1000",
          "pay_to": "string",
          "pay_to_kind": "byo"
        }
      ],
      "environment": "live",
      "one_time": true,
      "status": "active",
      "use_count": 1,
      "active": true,
      "created_at": "2026-01-15T12:00:00.000Z"
    }
  ]
}

Example request

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

POST /v1/payment-links

Create a multi-rail payment link

Request body

FieldTypeRequiredDescription
acceptsarray of object | objectyes
descriptionstringno
expires_atstring (date-time)no
max_usesintegerno
metadataobjectno
one_timebooleanno
redirect_urlstring (uri)no
slugstringno
titlestringyes

Responses

StatusBodyDescription
201objectOK
400objectBad Request. invalid_body when the body did not match the schema (the Zod issues are in details.issues) or when a rail's amount is not a valid positive amount for that rail. invalid_rail when a rail appears twice. invalid_payto when the payee is incomplete for the rail: a byo x402 without a 0x address, a provisioned x402 without consumer_id, or a pix with neither key nor account.
403objectForbidden. This write is gated on an organization role, and the guard refused. It is reachable with an OAuth access token or a service credential: either no acting user was forwarded in the x-codespar-user header, or the user it named does not hold at least admin in the organization. Re-sending the same header will not help in the second case — promote that user, or forward one who already qualifies. AN API KEY IS NOT REFUSED HERE TODAY. The role requirement is not enforced for that credential at this version, so a key alone completes the write and a 200 is not evidence that anyone holding admin authorized it. Plan for the header: it is the arm that will be closed.
409objectConflict. That slug is already taken. The namespace is global across tenants, so the holder may not be yours.
502objectBad Gateway. A provisioned x402 payee could not be given a receiving wallet. Nothing was created; the whole link is refused rather than stored with one rail missing.

Response 201

FieldTypeRequiredDescription
acceptsarray of object | objectyes
activebooleanyes
created_atstring (date-time)yes
descriptionstring,nullyes
environment"live" | "test"yesTaken from the calling key, never from the request body.
expires_atstring,null (date-time)yes
idstringyespl_ + nanoid.
max_usesinteger,nullyes
one_timebooleanyes
pay_urlstringyeshttps://gw.codespar.dev/pay/\<slug\>. Built from the slug, not stored.
project_idstringyes
redirect_urlstring,nullyes
slugstringyes
status"active" | "paid" | "expired" | "void"yesLifecycle, separate from active. active is the operator switch a PATCH flips; status is what the link's own use has made of it. A link is payable only when active is true, status is neither void nor paid, the expiry has not passed and the use cap has not been reached.
titlestringyes
use_countintegeryes

Example response

{
  "id": "obj_0000000000000000",
  "project_id": "prj_0000000000000000",
  "slug": "example",
  "title": "Example",
  "pay_url": "https://example.com/hook",
  "accepts": [
    {
      "rail": "x402",
      "currency": "USDC",
      "amount_atomic": "1000",
      "pay_to": "string",
      "pay_to_kind": "byo"
    }
  ],
  "environment": "live",
  "one_time": true,
  "status": "active",
  "use_count": 1,
  "active": true,
  "created_at": "2026-01-15T12:00:00.000Z"
}

Example request

curl -X POST https://api.codespar.dev/v1/payment-links \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "slug": "example",
       "title": "Example",
       "description": "string",
       "accepts": [
         {
           "rail": "x402",
           "amount": "1000",
           "currency": "USDC",
           "pay_to": {
             "kind": "byo",
             "address": "string",
             "consumer_id": "csm_0000000000000000"
           }
         }
       ],
       "one_time": true,
       "max_uses": 1,
       "expires_at": "2026-01-15T12:00:00.000Z",
       "redirect_url": "https://example.com/hook",
       "metadata": {}
     }'

GET /v1/payment-links/{id}

Read one payment link

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectOK
404objectNot Found. No payment link with that id in the caller's organization.

Response 200

FieldTypeRequiredDescription
acceptsarray of object | objectyes
activebooleanyes
created_atstring (date-time)yes
descriptionstring,nullyes
environment"live" | "test"yesTaken from the calling key, never from the request body.
expires_atstring,null (date-time)yes
idstringyespl_ + nanoid.
max_usesinteger,nullyes
one_timebooleanyes
pay_urlstringyeshttps://gw.codespar.dev/pay/\<slug\>. Built from the slug, not stored.
project_idstringyes
redirect_urlstring,nullyes
slugstringyes
status"active" | "paid" | "expired" | "void"yesLifecycle, separate from active. active is the operator switch a PATCH flips; status is what the link's own use has made of it. A link is payable only when active is true, status is neither void nor paid, the expiry has not passed and the use cap has not been reached.
titlestringyes
use_countintegeryes

Example response

{
  "id": "obj_0000000000000000",
  "project_id": "prj_0000000000000000",
  "slug": "example",
  "title": "Example",
  "pay_url": "https://example.com/hook",
  "accepts": [
    {
      "rail": "x402",
      "currency": "USDC",
      "amount_atomic": "1000",
      "pay_to": "string",
      "pay_to_kind": "byo"
    }
  ],
  "environment": "live",
  "one_time": true,
  "status": "active",
  "use_count": 1,
  "active": true,
  "created_at": "2026-01-15T12:00:00.000Z"
}

Example request

curl -X GET https://api.codespar.dev/v1/payment-links/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

PATCH /v1/payment-links/{id}

Update a payment link's title, copy, expiry, redirect or active flag

Path parameters

NameTypeRequiredDescription
idstringyes

Request body

FieldTypeRequiredDescription
activebooleanno
descriptionstring,nullno
expires_atstring,null (date-time)no
redirect_urlstring,null (uri)no
titlestringno

Responses

StatusBodyDescription
200objectOK
400objectBad Request. The patch did not match the schema, which includes the empty patch and any field outside the five above. The Zod issues are in details.issues.
403objectForbidden. This write is gated on an organization role, and the guard refused. It is reachable with an OAuth access token or a service credential: either no acting user was forwarded in the x-codespar-user header, or the user it named does not hold at least admin in the organization. Re-sending the same header will not help in the second case — promote that user, or forward one who already qualifies. AN API KEY IS NOT REFUSED HERE TODAY. The role requirement is not enforced for that credential at this version, so a key alone completes the write and a 200 is not evidence that anyone holding admin authorized it. Plan for the header: it is the arm that will be closed.
404objectNot Found. No payment link with that id reachable from this key: either no such id in the caller's organization, or one that lives in another of the organization's projects. The two are not distinguished.

Response 200

FieldTypeRequiredDescription
acceptsarray of object | objectyes
activebooleanyes
created_atstring (date-time)yes
descriptionstring,nullyes
environment"live" | "test"yesTaken from the calling key, never from the request body.
expires_atstring,null (date-time)yes
idstringyespl_ + nanoid.
max_usesinteger,nullyes
one_timebooleanyes
pay_urlstringyeshttps://gw.codespar.dev/pay/\<slug\>. Built from the slug, not stored.
project_idstringyes
redirect_urlstring,nullyes
slugstringyes
status"active" | "paid" | "expired" | "void"yesLifecycle, separate from active. active is the operator switch a PATCH flips; status is what the link's own use has made of it. A link is payable only when active is true, status is neither void nor paid, the expiry has not passed and the use cap has not been reached.
titlestringyes
use_countintegeryes

Example response

{
  "id": "obj_0000000000000000",
  "project_id": "prj_0000000000000000",
  "slug": "example",
  "title": "Example",
  "pay_url": "https://example.com/hook",
  "accepts": [
    {
      "rail": "x402",
      "currency": "USDC",
      "amount_atomic": "1000",
      "pay_to": "string",
      "pay_to_kind": "byo"
    }
  ],
  "environment": "live",
  "one_time": true,
  "status": "active",
  "use_count": 1,
  "active": true,
  "created_at": "2026-01-15T12:00:00.000Z"
}

Example request

curl -X PATCH https://api.codespar.dev/v1/payment-links/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "title": "Example",
       "active": true
     }'

DELETE /v1/payment-links/{id}

Delete a payment link

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
204No Content
403objectForbidden. This write is gated on an organization role, and the guard refused. It is reachable with an OAuth access token or a service credential: either no acting user was forwarded in the x-codespar-user header, or the user it named does not hold at least admin in the organization. Re-sending the same header will not help in the second case — promote that user, or forward one who already qualifies. AN API KEY IS NOT REFUSED HERE TODAY. The role requirement is not enforced for that credential at this version, so a key alone completes the write and a 200 is not evidence that anyone holding admin authorized it. Plan for the header: it is the arm that will be closed.
404objectNot Found. No payment link with that id reachable from this key: either no such id in the caller's organization, or one that lives in another of the organization's projects. The two are not distinguished.

Example request

curl -X DELETE https://api.codespar.dev/v1/payment-links/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
Payment Links | CodeSpar