Charges
5 operations under /v1/charges (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
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.
consumer_id is REQUIRED for a boleto too: it names the merchant-side consumer the receivable settles into, and its wallet is what the payment credits. Without it there is nothing to settle into and the call is refused with consumer_id_required before anything is sent. In a test project any stable id of your own works: the project's shared-sandbox receiver stands in for the consumer's account at the issuer (the consumer is never onboarded there), and POST /v1/test/charges/{chargeId}/pay settles the charge into that consumer's wallet.
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 |
consumer_id_required | 400 | a boleto create named no consumer_id, so there is no wallet for the receivable to settle into |
charge_not_found | 404 | no charge under that id FOR THIS TENANT |
charge_reference_ambiguous | 409 | the reference is one charge's id AND another's idempotency key; query by the charge id |
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 MAJOR units of currency, NOT minor: 12.5 is R$ 12,50 and comes back as amount_minor: 1250. Sending 1250 here issues a R$ 1.250,00 charge. This is the one field of the charge family that takes major units; every *_minor field, including the response's amount_minor, is in centavos. |
buyer | object | no | — |
consumer_id | string | no | The merchant-side consumer the charge settles into: its wallet is credited when the debtor pays. REQUIRED for boleto (refused with consumer_id_required otherwise); optional for the other methods, where an absent value falls back to the session's user when there is one. |
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, names no consumer for a boleto, 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 MAJOR units of currency: 12.5 is R$ 12,50. Always amount_minor / 100. |
amount_minor | integer | yes | In minor units (centavos): 1250 is R$ 12,50. |
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 (PROCESSING, PENDING, CONFIRMED, CANCELLED, EXPIRED, ERROR). Forced to PROCESSING until the issuance is confirmed. ERROR is the issuer rejecting the charge: no boleto and no Pix were ever issued, so nobody can pay it. A charge settled by the sandbox payer answers CONFIRMED here, the same value a real payment leaves a live read answering — so a client that polls status sees the test payment exactly where it would see a live one. The issuer's homolog account never sees that money and keeps answering PENDING (EXPIRED after the due date); that answer stays in raw and is not reported as status or as a status_conflict, because for a test charge the fixture is the issuer's truth. settlement: "confirmed" and local_status: "settled" say the same thing from our side. Any other issuer answer (ERROR, CANCELLED, PROCESSING) is reported as the issuer said it, with status_conflict: true. |
status_conflict | boolean | yes | True whenever the issuer's known state and ours disagree, instead of one of them silently winning. They agree only as follows: our pending beside the issuer's PENDING or PROCESSING; our settled beside CONFIRMED or PENDING (the issuer's read can lag a payment); our expired beside CANCELLED or PENDING (lag after a cancel). Every other pair is a conflict, including an issuer ERROR, an issuer terminal state on a charge we still hold open, and a status spelling we do not recognise. False when no issuer state is known at all. settlement: "unconfirmable" is the narrower case of both sides terminal and different. |
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",
"consumer_id": "csm_0000000000000000",
"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",
"consumer_id": "csm_0000000000000000",
"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",
"consumer_id": "csm_0000000000000000",
"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",
"consumer_id": "csm_0000000000000000",
"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",
consumer_id: "csm_0000000000000000",
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 (the issuer's charge transaction id) 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. A value that is one charge's id AND another charge's key is refused with charge_reference_ambiguous rather than answered for whichever came first; query by the charge id in that case. The Pix leg's own transaction id is not a reference here.
A charge the sandbox payer settled answers status: "CONFIRMED" with settlement: "confirmed" and local_status: "settled" — see status on the response for why the issuer's own PENDING is not reported.
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 |
consumer_id_required | 400 | a boleto create named no consumer_id, so there is no wallet for the receivable to settle into |
charge_not_found | 404 | no charge under that id FOR THIS TENANT |
charge_reference_ambiguous | 409 | the reference is one charge's id AND another's idempotency key; query by the charge id |
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, or the reference names two charges. |
502 | object | The issuer did not answer. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | yes | In MAJOR units of currency: 12.5 is R$ 12,50. Always amount_minor / 100. |
amount_minor | integer | yes | In minor units (centavos): 1250 is R$ 12,50. |
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 (PROCESSING, PENDING, CONFIRMED, CANCELLED, EXPIRED, ERROR). Forced to PROCESSING until the issuance is confirmed. ERROR is the issuer rejecting the charge: no boleto and no Pix were ever issued, so nobody can pay it. A charge settled by the sandbox payer answers CONFIRMED here, the same value a real payment leaves a live read answering — so a client that polls status sees the test payment exactly where it would see a live one. The issuer's homolog account never sees that money and keeps answering PENDING (EXPIRED after the due date); that answer stays in raw and is not reported as status or as a status_conflict, because for a test charge the fixture is the issuer's truth. settlement: "confirmed" and local_status: "settled" say the same thing from our side. Any other issuer answer (ERROR, CANCELLED, PROCESSING) is reported as the issuer said it, with status_conflict: true. |
status_conflict | boolean | yes | True whenever the issuer's known state and ours disagree, instead of one of them silently winning. They agree only as follows: our pending beside the issuer's PENDING or PROCESSING; our settled beside CONFIRMED or PENDING (the issuer's read can lag a payment); our expired beside CANCELLED or PENDING (lag after a cancel). Every other pair is a conflict, including an issuer ERROR, an issuer terminal state on a charge we still hold open, and a status spelling we do not recognise. False when no issuer state is known at all. settlement: "unconfirmable" is the narrower case of both sides terminal and different. |
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 |
consumer_id_required | 400 | a boleto create named no consumer_id, so there is no wallet for the receivable to settle into |
charge_not_found | 404 | no charge under that id FOR THIS TENANT |
charge_reference_ambiguous | 409 | the reference is one charge's id AND another's idempotency key; query by the charge id |
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 MAJOR units of currency: 12.5 is R$ 12,50. Always amount_minor / 100. |
amount_minor | integer | yes | In minor units (centavos): 1250 is R$ 12,50. |
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 (PROCESSING, PENDING, CONFIRMED, CANCELLED, EXPIRED, ERROR). Forced to PROCESSING until the issuance is confirmed. ERROR is the issuer rejecting the charge: no boleto and no Pix were ever issued, so nobody can pay it. A charge settled by the sandbox payer answers CONFIRMED here, the same value a real payment leaves a live read answering — so a client that polls status sees the test payment exactly where it would see a live one. The issuer's homolog account never sees that money and keeps answering PENDING (EXPIRED after the due date); that answer stays in raw and is not reported as status or as a status_conflict, because for a test charge the fixture is the issuer's truth. settlement: "confirmed" and local_status: "settled" say the same thing from our side. Any other issuer answer (ERROR, CANCELLED, PROCESSING) is reported as the issuer said it, with status_conflict: true. |
status_conflict | boolean | yes | True whenever the issuer's known state and ours disagree, instead of one of them silently winning. They agree only as follows: our pending beside the issuer's PENDING or PROCESSING; our settled beside CONFIRMED or PENDING (the issuer's read can lag a payment); our expired beside CANCELLED or PENDING (lag after a cancel). Every other pair is a conflict, including an issuer ERROR, an issuer terminal state on a charge we still hold open, and a status spelling we do not recognise. False when no issuer state is known at all. settlement: "unconfirmable" is the narrower case of both sides terminal and different. |
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
}POST /v1/charges/{chargeId}/sandbox/pay
https://api.codespar.dev/v1/charges/{chargeId}/sandbox/payledger `fund()` with the `pending -> settled` claim
Pay a charge in the sandbox (alias path)
Alias of POST /v1/test/charges/{chargeId}/pay (ent#979 contract: one handler, two paths, one scope). Same body, same answers.
Plays the DEBTOR of a test charge: settles it through the SAME path a Celcoin charge-in webhook takes (translate, publish commerce.charge.paid, reconcile, ledger fund() with the pending -> settled claim). It is not a status write, so the event fans out to your triggers exactly as a real payment would, and the ledger and receipt side effects are the ones production produces.
Test-mode only, fail-closed. A live-environment key or project answers 403 sandbox_pay_not_permitted before the body is read or any query runs.
Nothing claims money moved. The event payload, the ledger entry and the charge row all carry simulated: true and settled_against: "sandbox_fixture"; a real charge-in never carries either.
Only a payable charge settles. The debtor can only pay a document the issuer handed over: the charge must be open, a boleto or Pix recorded, and the issuer's last known status PENDING (or CONFIRMED, the issuer already calling it paid) — the conditions of payable: true on the read. Anything else is refused with 409 charge_not_payable before anything is written, exactly as a live debtor would find nothing to pay.
Idempotent. The provider event id is the one the real delivery would carry, so a second pay dedupes at the event (no second trigger fire), the ledger dedupes on the charge, and a charge already settled answers its recorded state with idempotent_replay: true whatever amount the second call names.
chargeId is the id create returned, or your own idempotency key, resolved under the caller's org and project: another tenant's charge is charge_not_found, never forbidden.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
chargeId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount_minor | integer | no | Overrides the quoted amount, in minor units, to simulate a partial or divergent payment. The funding bridge credits what arrived and closes the charge, exactly as it does for a real charge-in that disagrees with the quote. Omitted: the quoted amount is paid in full. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The body is outside the schema, or the credential names no project. |
403 | object | Not a test-environment key/project. Terminal for that credential. |
404 | object | No charge under that id for this tenant. |
409 | object | The charge exists and cannot be paid, and nothing was settled, credited or published: issuance_unconfirmed is a reservation whose create never got an answer; charge_not_payable is a charge with no document a debtor could pay, and details.reason says which: issuer_rejected (the issuer left it in ERROR, no boleto or Pix ever existed — terminal, create a new charge), charge_closed (expired or cancelled, ours or the issuer's — terminal, create a new charge), instrument_not_registered (the issuer has not handed over a boleto or Pix yet — read the charge and pay once it answers payable: true); charge_reference_ambiguous is a reference that is one charge's id and another's idempotency key — pay by the charge id; collect_simulated_settle_refused is a Collect link's charge, issued on a real rail: in Test it is paid with POST /v1/collect/attempts/{attemptId}/test-pay (details.next_action), never by this simulation. |
502 | object | The payment event was published but the ledger credit did not land; the charge is still open. Retrying is safe — the event dedupes and the credit is keyed on the charge. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
charge_id | string | yes | — |
currency | string | yes | — |
event | object,null | yes | The one commerce.charge.paid this charge has. A second pay never mints another. |
idempotent_replay | boolean | yes | True when the charge was already settled and this call answered from the record. |
ledger_entry_id | string,null | yes | The fund entry keyed bolepix:<charge_id>. Null only for a charge the other credit path settled. |
local_status | string | yes | What CodeSpar recorded: settled. |
money_moved | false | yes | Nothing moved anywhere, including at the provider's sandbox. |
paid_minor | integer | yes | What the ledger credited. |
paid_via | string | yes | The leg the simulated debtor used. Always Pix today. |
payment | "full" | "partial" | "over" | yes | — |
quoted_minor | integer | yes | — |
settled_against | "sandbox_fixture" | yes | — |
simulated | boolean | yes | True when a sandbox fixture settled it. Read off the records, never off the request. |
status | "paid" | yes | — |
wallet_id | string | yes | — |
curl -X POST https://api.codespar.dev/v1/charges/{chargeId}/sandbox/pay \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": 1000
}'POST /v1/charges/{chargeId}/sandbox/pay HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"amount_minor": 1000
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/charges/{chargeId}/sandbox/pay",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"amount_minor": 1000
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/charges/{chargeId}/sandbox/pay", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"amount_minor": 1000
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/charges/{chargeId}/sandbox/pay", {
path: {
chargeId: "charge_0000000000000000"
},
body: {
amount_minor: 1000
}
});
// 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);
}{
"charge_id": "charge_0000000000000000",
"status": "paid",
"local_status": "string",
"currency": "BRL",
"quoted_minor": 1,
"paid_minor": 1,
"payment": "full",
"paid_via": "string",
"wallet_id": "wlt_0000000000000000",
"ledger_entry_id": "ledgerentry_0000000000000000",
"event": {
"id": "obj_0000000000000000",
"type": "commerce.charge.paid"
},
"simulated": true,
"settled_against": "sandbox_fixture",
"money_moved": false,
"idempotent_replay": true
}