Payment Links
Generated HTTP reference for the 5 operations the published OpenAPI document describes under payment-links.
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.
Payment link object
| Field | Type | Description |
|---|---|---|
id | string | Payment link ID, pl_<...> |
slug | string | URL slug; global namespace. Auto-generated when omitted |
title | string | Display title |
description | string | null | Optional description |
pay_url | string | https://gw.codespar.dev/pay/<slug> |
accepts | Accept[] | One or more settlement rails (see below) |
environment | "test" | "live" | Environment the link settles in |
one_time | boolean | Closes after the first successful payment |
status | string | Lifecycle status |
use_count | number | Successful payments so far |
max_uses | number | null | Cap on total payments; null for unlimited |
expires_at | string | null | ISO 8601, or null |
redirect_url | string | null | Where a person is sent after paying |
active | boolean | Paused links cannot be paid |
created_at | string | ISO 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" }
}amountis decimal USDC (up to 6 decimals).pay_to.kind: "provisioned"settles to the CodeSpar wallet ofconsumer_id.pay_to.kind: "byo"settles topay_to.address(a0xEVM address).
Pix / BRL
{
"rail": "pix",
"amount": "5.00",
"currency": "BRL",
"pix": { "key": "your-pix-key" }
}amountis decimal BRL (up to 2 decimals).pix.key(orpix.celcoin_account) is where the Pix payment lands.
Create a payment link
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
}slugis optional; a unique one is generated when omitted.acceptsmust have at least one rail.one_time,max_uses,expires_at, andredirect_urlare all optional.
Response — 201 Created with the payment link object.
List payment links
GET /v1/payment-links
Response
{ "payment_links": [/* payment link */] }Get a payment link
GET /v1/payment-links/:id — the payment link object, or 404 payment_link_not_found.
Update a payment link
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 a payment link
DELETE /v1/payment-links/:id (admin) — 204 No Content. Collect links are
config, not audit data; settled receipts are retained independently.
Errors
| HTTP | code | when |
|---|---|---|
400 | invalid_body | body failed validation (including a malformed x402/Pix amount) |
403 | forbidden | non-admin caller on an admin endpoint |
404 | payment_link_not_found | unknown id in this project |
409 | slug_conflict | the 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.
GET /v1/payment-links
List the payment links in this project
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
payment_links | array of object | yes | — |
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
| Field | Type | Required | Description |
|---|---|---|---|
accepts | array of object | object | yes | — |
description | string | no | — |
expires_at | string (date-time) | no | — |
max_uses | integer | no | — |
metadata | object | no | — |
one_time | boolean | no | — |
redirect_url | string (uri) | no | — |
slug | string | no | — |
title | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | OK |
400 | object | Bad 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. |
403 | object | Forbidden. 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. |
409 | object | Conflict. That slug is already taken. The namespace is global across tenants, so the holder may not be yours. |
502 | object | Bad 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
| Field | Type | Required | Description |
|---|---|---|---|
accepts | array of object | object | yes | — |
active | boolean | yes | — |
created_at | string (date-time) | yes | — |
description | string,null | yes | — |
environment | "live" | "test" | yes | Taken from the calling key, never from the request body. |
expires_at | string,null (date-time) | yes | — |
id | string | yes | pl_ + nanoid. |
max_uses | integer,null | yes | — |
one_time | boolean | yes | — |
pay_url | string | yes | https://gw.codespar.dev/pay/\<slug\>. Built from the slug, not stored. |
project_id | string | yes | — |
redirect_url | string,null | yes | — |
slug | string | yes | — |
status | "active" | "paid" | "expired" | "void" | yes | Lifecycle, 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. |
title | string | yes | — |
use_count | integer | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found. No payment link with that id in the caller's organization. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
accepts | array of object | object | yes | — |
active | boolean | yes | — |
created_at | string (date-time) | yes | — |
description | string,null | yes | — |
environment | "live" | "test" | yes | Taken from the calling key, never from the request body. |
expires_at | string,null (date-time) | yes | — |
id | string | yes | pl_ + nanoid. |
max_uses | integer,null | yes | — |
one_time | boolean | yes | — |
pay_url | string | yes | https://gw.codespar.dev/pay/\<slug\>. Built from the slug, not stored. |
project_id | string | yes | — |
redirect_url | string,null | yes | — |
slug | string | yes | — |
status | "active" | "paid" | "expired" | "void" | yes | Lifecycle, 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. |
title | string | yes | — |
use_count | integer | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
active | boolean | no | — |
description | string,null | no | — |
expires_at | string,null (date-time) | no | — |
redirect_url | string,null (uri) | no | — |
title | string | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Bad 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. |
403 | object | Forbidden. 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. |
404 | object | Not 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
| Field | Type | Required | Description |
|---|---|---|---|
accepts | array of object | object | yes | — |
active | boolean | yes | — |
created_at | string (date-time) | yes | — |
description | string,null | yes | — |
environment | "live" | "test" | yes | Taken from the calling key, never from the request body. |
expires_at | string,null (date-time) | yes | — |
id | string | yes | pl_ + nanoid. |
max_uses | integer,null | yes | — |
one_time | boolean | yes | — |
pay_url | string | yes | https://gw.codespar.dev/pay/\<slug\>. Built from the slug, not stored. |
project_id | string | yes | — |
redirect_url | string,null | yes | — |
slug | string | yes | — |
status | "active" | "paid" | "expired" | "void" | yes | Lifecycle, 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. |
title | string | yes | — |
use_count | integer | yes | — |
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
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
204 | — | No Content |
403 | object | Forbidden. 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. |
404 | object | Not 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"