Charges
4 operations under /v1/charges (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Charges
Base URL: https://api.codespar.dev
Every operation below requires a Bearer token. See Authentication.
GET /v1/charges
https://api.codespar.dev/v1/chargesList charges
The charges this project issued, newest first. Page with limit (1 to 200, default 50) and cursor, which is the created_at of the last row you saw; next_cursor comes back null when the page is the last one.
The settlement here is the LAST KNOWN, and the one on the by-id read is the one from now. The by-id read calls the issuer and compares; a list cannot do that per row, because 200 rows would be 200 provider calls and paging would become a flood. So every item carries settlement_as_of: when we last knew the issuer's state for that charge, or null if we never did.
The same charge can therefore answer a different settlement here and there. That is the age of the data, not a defect, which is why the freshness field is required rather than optional. A charge we never read carries settlement_as_of: null and can never be unconfirmable on this list, because there is nothing to conflict with.
settlement=none selects the charge with no settlement to report: nobody paid it, so there is no pending settlement, only an open receivable. It is not the same as omitting the filter.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
cursor | string | no | — |
limit | string | no | — |
settlement | "confirmed" | "pending" | "unconfirmable" | "none" | no | Filters on the same three facts the field is derived from, in SQL. |
status | string | no | Our own row status: pending, settled, expired. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The query is malformed, or the credential names no project. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
data | array of object | yes | — |
next_cursor | string,null | yes | — |
curl -X GET https://api.codespar.dev/v1/charges \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/charges HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/charges",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/charges", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/charges");{
"data": [
{
"id": "obj_0000000000000000",
"status": "string",
"local_status": "string",
"status_conflict": true,
"currency": "BRL",
"amount_minor": 1000,
"due_date": "string",
"created_at": "string",
"settlement": "confirmed",
"settlement_as_of": "string"
}
],
"next_cursor": "string"
}POST /v1/charges
https://api.codespar.dev/v1/chargesIssues an inbound charge: the buyer pays the merchant.
Issue a charge
Issues an inbound charge: the buyer pays the merchant. The RAIL IS A FIELD, not a path. method: "pix" mints an immediate Pix that hands back its copy-and-paste string right away and expires in about an hour. method: "boleto" WITH a due_date issues a cobrança com vencimento: ONE receivable the payer settles either as a boleto (barcode and linha digitável) or by Pix. One debt, two payable legs, never two documents.
The cobrança com vencimento answers PROCESSING with payable: false and no document. The instrument registers with the clearing house first. Subscribe to commerce.charge.created rather than polling for a barcode that is not late, only not made yet. commerce.charge.paid is the event that means the funds arrived, by either leg.
idempotency_key is REQUIRED for that combination, and it is checked before anything reaches the issuer. A repeat with the same key returns the charge already issued, or its still-open reservation, instead of a second receivable the same debtor could pay twice. Send it as the Idempotency-Key header, as the body's idempotency_key, or as both with the SAME value; two different values are refused rather than one silently winning.
An agreement in N instalments is N cobranças, one per parcela, each with its own due_date and its own key. There is no single instalment charge.
Each refusal names a reason, and the reason is what to branch on. The two error code values (invalid_args, provider_error) are too coarse: they cover six different situations, and reading them as one would turn not found into bad request.
| reason | status | what it means |
|---|---|---|
charge_id_missing | 400 | the call named no charge |
project_scope_missing | 400 | the credential is not project-scoped, and a charge belongs to one project |
charge_not_found | 404 | no charge under that id FOR THIS TENANT |
issuance_unconfirmed | 409 | the key holds a reservation whose create never got an answer |
already_payment_notified | 409 | the clearing house already notified a payment |
not_cancellable_in_this_state | 409 | the issuer accepts a cancellation only in some states |
provider_status_unreadable | 502 | the issuer did not answer the read |
provider_cancel_failed | 502 | the issuer refused the withdrawal |
charge_not_found is deliberately the SAME answer for an id that belongs to another project and for an id that exists nowhere. Telling those apart would be an oracle about another tenant over a string anyone can guess.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | In minor units. |
buyer | object | no | — |
country | string | no | — |
currency | string | yes | — |
description | string | no | — |
due_date | string | no | YYYY-MM-DD. With method: "boleto" this is what makes it a cobrança com vencimento; an immediate Pix has no due date and refuses one. |
idempotency_key | string | no | Required for a cobrança com vencimento. Stable per debt: it is what makes a retry the SAME charge instead of a second one. |
metadata | object | no | — |
method | string | yes | pix, boleto, card or wallet. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The call is malformed, or the two idempotency keys disagree. |
409 | object | The idempotency slot is held by a row this project cannot read. |
502 | object | The slot could not be reserved, or the issuer refused. Nothing was sent. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | In units. |
amount_minor | integer | yes | In minor units. |
boleto_bank_line | string,null | yes | — |
boleto_bar_code | string,null | yes | — |
credit_correlation_armed | boolean | yes | Whether an inbound Pix on this charge's Pix leg can resolve a wallet on its own. False until registration hands us the leg's identifier. |
currency | string | yes | — |
due_date | string,null | yes | — |
id | string,null | yes | The issuer's charge id. Null while the issuance is unconfirmed. |
issuance_unconfirmed | boolean | yes | True while the row is a reservation with no charge id: the issuer's answer to the create was lost, or a create is in flight elsewhere under the same key. Nothing is payable and nothing was duplicated. |
local_status | string | yes | What CodeSpar recorded, which is a different question from status. |
method | string | yes | — |
payable | boolean | yes | True only when all four hold: the issuance is confirmed, our row is still open, the provider says PENDING, and at least one document exists. This is the field to branch on, not status. |
payment_in_flight | boolean | yes | True when a payment was notified and the receivable is still open. Read this before you write anything off: the issuer can answer EXPIRED for a boleto it already told us was paid, because the baixa happened before the due date and the credit lands on the next business day. |
pix_copy_paste | string,null | yes | The Pix leg of the same debt. One receivable, two payable legs. |
settlement | "confirmed" | "pending" | "unconfirmable" | yes | The settlement tri-state (decision 10b of the canonical matrix). confirmed: the money is in the account. pending: the clearing house notified a payment and the credit has not landed, which is the D+1 window. unconfirmable: our terminal state disagrees with the issuer's, so settlement cannot be established. NULL MEANS NOT APPLICABLE, never pending. A charge nobody paid has no pending settlement: it has an open receivable. Reading null as pending would tell you money is on its way. |
status | string | yes | The provider's state as far as we know it, normalized. Forced to PROCESSING until the issuance is confirmed. |
status_conflict | boolean | yes | True when the two disagree terminally, instead of one of them silently winning. |
curl -X POST https://api.codespar.dev/v1/charges \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 1000,
"currency": "BRL",
"method": "string",
"description": "string",
"buyer": {},
"due_date": "string",
"idempotency_key": "string",
"country": "string",
"metadata": {}
}'POST /v1/charges HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"amount": 1000,
"currency": "BRL",
"method": "string",
"description": "string",
"buyer": {},
"due_date": "string",
"idempotency_key": "string",
"country": "string",
"metadata": {}
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/charges",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"amount": 1000,
"currency": "BRL",
"method": "string",
"description": "string",
"buyer": {},
"due_date": "string",
"idempotency_key": "string",
"country": "string",
"metadata": {}
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/charges", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"amount": 1000,
"currency": "BRL",
"method": "string",
"description": "string",
"buyer": {},
"due_date": "string",
"idempotency_key": "string",
"country": "string",
"metadata": {}
}),
});
const data = await res.json();const result = await cs.api.post("/v1/charges", {
body: {
amount: 1000,
currency: "BRL",
method: "string",
description: "string",
buyer: {},
due_date: "string",
idempotency_key: "string",
country: "string",
metadata: {}
}
});{
"id": "obj_0000000000000000",
"status": "string",
"local_status": "string",
"status_conflict": true,
"method": "string",
"currency": "BRL",
"amount": 1000,
"amount_minor": 1000,
"due_date": "string",
"payable": true,
"boleto_bar_code": "string",
"boleto_bank_line": "string",
"pix_copy_paste": "string",
"credit_correlation_armed": true,
"payment_in_flight": true,
"settlement": "confirmed",
"issuance_unconfirmed": true
}GET /v1/charges/{chargeId}
https://api.codespar.dev/v1/charges/{chargeId}Read a charge
Reads a charge this project issued. chargeId accepts EITHER the id the create returned OR the caller's own idempotency_key — the row is resolved under this credential's org and project, which is what makes another tenant's id simply not found.
Branch on payable, not on status. payable is true only when the issuance is confirmed, our row is still open, the provider says PENDING, and at least one payable document exists. A cobrança com vencimento answers PROCESSING with payable: false and no document at first, because the instrument registers with the clearing house before it can be paid.
This read also ARMS the Pix leg's correlation when the provider's answer carries it, which is why credit_correlation_armed can flip from a read. Nothing else about it writes: no ledger row, no money.
Each refusal names a reason, and the reason is what to branch on. The two error code values (invalid_args, provider_error) are too coarse: they cover six different situations, and reading them as one would turn not found into bad request.
| reason | status | what it means |
|---|---|---|
charge_id_missing | 400 | the call named no charge |
project_scope_missing | 400 | the credential is not project-scoped, and a charge belongs to one project |
charge_not_found | 404 | no charge under that id FOR THIS TENANT |
issuance_unconfirmed | 409 | the key holds a reservation whose create never got an answer |
already_payment_notified | 409 | the clearing house already notified a payment |
not_cancellable_in_this_state | 409 | the issuer accepts a cancellation only in some states |
provider_status_unreadable | 502 | the issuer did not answer the read |
provider_cancel_failed | 502 | the issuer refused the withdrawal |
charge_not_found is deliberately the SAME answer for an id that belongs to another project and for an id that exists nowhere. Telling those apart would be an oracle about another tenant over a string anyone can guess.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
chargeId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The call is malformed. reason says which half. |
404 | object | No charge under that id for this tenant. |
409 | object | The charge exists and is not readable in this state. |
502 | object | The issuer did not answer. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | In units. |
amount_minor | integer | yes | In minor units. |
boleto_bank_line | string,null | yes | — |
boleto_bar_code | string,null | yes | — |
credit_correlation_armed | boolean | yes | Whether an inbound Pix on this charge's Pix leg can resolve a wallet on its own. False until registration hands us the leg's identifier. |
currency | string | yes | — |
due_date | string,null | yes | — |
id | string,null | yes | The issuer's charge id. Null while the issuance is unconfirmed. |
issuance_unconfirmed | boolean | yes | True while the row is a reservation with no charge id: the issuer's answer to the create was lost, or a create is in flight elsewhere under the same key. Nothing is payable and nothing was duplicated. |
local_status | string | yes | What CodeSpar recorded, which is a different question from status. |
method | string | yes | — |
payable | boolean | yes | True only when all four hold: the issuance is confirmed, our row is still open, the provider says PENDING, and at least one document exists. This is the field to branch on, not status. |
payment_in_flight | boolean | yes | True when a payment was notified and the receivable is still open. Read this before you write anything off: the issuer can answer EXPIRED for a boleto it already told us was paid, because the baixa happened before the due date and the credit lands on the next business day. |
pix_copy_paste | string,null | yes | The Pix leg of the same debt. One receivable, two payable legs. |
settlement | "confirmed" | "pending" | "unconfirmable" | yes | The settlement tri-state (decision 10b of the canonical matrix). confirmed: the money is in the account. pending: the clearing house notified a payment and the credit has not landed, which is the D+1 window. unconfirmable: our terminal state disagrees with the issuer's, so settlement cannot be established. NULL MEANS NOT APPLICABLE, never pending. A charge nobody paid has no pending settlement: it has an open receivable. Reading null as pending would tell you money is on its way. |
status | string | yes | The provider's state as far as we know it, normalized. Forced to PROCESSING until the issuance is confirmed. |
status_conflict | boolean | yes | True when the two disagree terminally, instead of one of them silently winning. |
curl -X GET https://api.codespar.dev/v1/charges/{chargeId} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/charges/{chargeId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/charges/{chargeId}",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/charges/{chargeId}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/charges/{chargeId}", {
path: {
chargeId: "charge_0000000000000000"
}
});{
"id": "obj_0000000000000000",
"status": "string",
"local_status": "string",
"status_conflict": true,
"method": "string",
"currency": "BRL",
"amount": 1000,
"amount_minor": 1000,
"due_date": "string",
"payable": true,
"boleto_bar_code": "string",
"boleto_bank_line": "string",
"pix_copy_paste": "string",
"credit_correlation_armed": true,
"payment_in_flight": true,
"settlement": "confirmed",
"issuance_unconfirmed": true
}POST /v1/charges/{chargeId}/cancel
https://api.codespar.dev/v1/charges/{chargeId}/cancelWithdraw a charge
Withdraws a charge that has not been paid. Takes no body: the id is the whole request, and it accepts the same two forms the read does.
This never moves money. It is not a refund: an unpaid receivable is withdrawn, and a charge whose payment the clearing house already notified is REFUSED with already_payment_notified rather than withdrawn. That refusal is the D+1 guard: a boleto paid after the convênio's cut-off is notified on one day and credited on the next business day, so the due date can fall between the two, and withdrawing there would write off a receivable somebody paid.
A charge the issuer already reports as cancelled is a no-op that closes our row, not a second withdrawal.
Each refusal names a reason, and the reason is what to branch on. The two error code values (invalid_args, provider_error) are too coarse: they cover six different situations, and reading them as one would turn not found into bad request.
| reason | status | what it means |
|---|---|---|
charge_id_missing | 400 | the call named no charge |
project_scope_missing | 400 | the credential is not project-scoped, and a charge belongs to one project |
charge_not_found | 404 | no charge under that id FOR THIS TENANT |
issuance_unconfirmed | 409 | the key holds a reservation whose create never got an answer |
already_payment_notified | 409 | the clearing house already notified a payment |
not_cancellable_in_this_state | 409 | the issuer accepts a cancellation only in some states |
provider_status_unreadable | 502 | the issuer did not answer the read |
provider_cancel_failed | 502 | the issuer refused the withdrawal |
charge_not_found is deliberately the SAME answer for an id that belongs to another project and for an id that exists nowhere. Telling those apart would be an oracle about another tenant over a string anyone can guess.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
chargeId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The call is malformed. |
404 | object | No charge under that id for this tenant. |
409 | object | The charge exists and this state does not accept a withdrawal. already_payment_notified is the D+1 guard and is never a retry case. |
502 | object | The issuer refused the withdrawal, or did not answer. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | In units. |
amount_minor | integer | yes | In minor units. |
boleto_bank_line | string,null | yes | — |
boleto_bar_code | string,null | yes | — |
credit_correlation_armed | boolean | yes | Whether an inbound Pix on this charge's Pix leg can resolve a wallet on its own. False until registration hands us the leg's identifier. |
currency | string | yes | — |
due_date | string,null | yes | — |
id | string,null | yes | The issuer's charge id. Null while the issuance is unconfirmed. |
issuance_unconfirmed | boolean | yes | True while the row is a reservation with no charge id: the issuer's answer to the create was lost, or a create is in flight elsewhere under the same key. Nothing is payable and nothing was duplicated. |
local_status | string | yes | What CodeSpar recorded, which is a different question from status. |
method | string | yes | — |
payable | boolean | yes | True only when all four hold: the issuance is confirmed, our row is still open, the provider says PENDING, and at least one document exists. This is the field to branch on, not status. |
payment_in_flight | boolean | yes | True when a payment was notified and the receivable is still open. Read this before you write anything off: the issuer can answer EXPIRED for a boleto it already told us was paid, because the baixa happened before the due date and the credit lands on the next business day. |
pix_copy_paste | string,null | yes | The Pix leg of the same debt. One receivable, two payable legs. |
settlement | "confirmed" | "pending" | "unconfirmable" | yes | The settlement tri-state (decision 10b of the canonical matrix). confirmed: the money is in the account. pending: the clearing house notified a payment and the credit has not landed, which is the D+1 window. unconfirmable: our terminal state disagrees with the issuer's, so settlement cannot be established. NULL MEANS NOT APPLICABLE, never pending. A charge nobody paid has no pending settlement: it has an open receivable. Reading null as pending would tell you money is on its way. |
status | string | yes | The provider's state as far as we know it, normalized. Forced to PROCESSING until the issuance is confirmed. |
status_conflict | boolean | yes | True when the two disagree terminally, instead of one of them silently winning. |
curl -X POST https://api.codespar.dev/v1/charges/{chargeId}/cancel \
-H "Authorization: Bearer $CODESPAR_API_KEY"POST /v1/charges/{chargeId}/cancel HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.post(
"https://api.codespar.dev/v1/charges/{chargeId}/cancel",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/charges/{chargeId}/cancel", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.post("/v1/charges/{chargeId}/cancel", {
path: {
chargeId: "charge_0000000000000000"
}
});{
"id": "obj_0000000000000000",
"status": "string",
"local_status": "string",
"status_conflict": true,
"method": "string",
"currency": "BRL",
"amount": 1000,
"amount_minor": 1000,
"due_date": "string",
"payable": true,
"boleto_bar_code": "string",
"boleto_bank_line": "string",
"pix_copy_paste": "string",
"credit_correlation_armed": true,
"payment_in_flight": true,
"settlement": "confirmed",
"issuance_unconfirmed": true
}Health
1 operation under /v1/health (GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Paywalls
6 operations under /v1/paywalls (GET POST PATCH DELETE): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.