Skip to main content

Charges

4 operations under /v1/charges (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.

14 min read
View MarkdownEdit on GitHub

Charges

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

Every operation below requires a Bearer token. See Authentication.

GET /v1/charges

GEThttps://api.codespar.dev/v1/charges

List 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

NameTypeRequiredDescription
cursorstringno
limitstringno
settlement"confirmed" | "pending" | "unconfirmable" | "none"noFilters on the same three facts the field is derived from, in SQL.
statusstringnoOur own row status: pending, settled, expired.

Responses

StatusBodyDescription
200objectOK
400objectThe query is malformed, or the credential names no project.

Response 200

FieldTypeRequiredDescription
dataarray of objectyes
next_cursorstring,nullyes
Example request
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_KEY
import 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");
Example response 200
application/json
{
  "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

POSThttps://api.codespar.dev/v1/charges
Can move money

Issues 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.

reasonstatuswhat it means
charge_id_missing400the call named no charge
project_scope_missing400the credential is not project-scoped, and a charge belongs to one project
charge_not_found404no charge under that id FOR THIS TENANT
issuance_unconfirmed409the key holds a reservation whose create never got an answer
already_payment_notified409the clearing house already notified a payment
not_cancellable_in_this_state409the issuer accepts a cancellation only in some states
provider_status_unreadable502the issuer did not answer the read
provider_cancel_failed502the 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

FieldTypeRequiredDescription
amountnumberyesIn minor units.
buyerobjectno
countrystringno
currencystringyes
descriptionstringno
due_datestringnoYYYY-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_keystringnoRequired for a cobrança com vencimento. Stable per debt: it is what makes a retry the SAME charge instead of a second one.
metadataobjectno
methodstringyespix, boleto, card or wallet.

Responses

StatusBodyDescription
200objectOK
400objectThe call is malformed, or the two idempotency keys disagree.
409objectThe idempotency slot is held by a row this project cannot read.
502objectThe slot could not be reserved, or the issuer refused. Nothing was sent.

Response 200

FieldTypeRequiredDescription
amountnumberyesIn units.
amount_minorintegeryesIn minor units.
boleto_bank_linestring,nullyes
boleto_bar_codestring,nullyes
credit_correlation_armedbooleanyesWhether 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.
currencystringyes
due_datestring,nullyes
idstring,nullyesThe issuer's charge id. Null while the issuance is unconfirmed.
issuance_unconfirmedbooleanyesTrue 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_statusstringyesWhat CodeSpar recorded, which is a different question from status.
methodstringyes
payablebooleanyesTrue 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_flightbooleanyesTrue 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_pastestring,nullyesThe Pix leg of the same debt. One receivable, two payable legs.
settlement"confirmed" | "pending" | "unconfirmable"yesThe 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.
statusstringyesThe provider's state as far as we know it, normalized. Forced to PROCESSING until the issuance is confirmed.
status_conflictbooleanyesTrue when the two disagree terminally, instead of one of them silently winning.
Example request
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: {}
  }
});
Example response 200
application/json
{
  "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}

GEThttps://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.

reasonstatuswhat it means
charge_id_missing400the call named no charge
project_scope_missing400the credential is not project-scoped, and a charge belongs to one project
charge_not_found404no charge under that id FOR THIS TENANT
issuance_unconfirmed409the key holds a reservation whose create never got an answer
already_payment_notified409the clearing house already notified a payment
not_cancellable_in_this_state409the issuer accepts a cancellation only in some states
provider_status_unreadable502the issuer did not answer the read
provider_cancel_failed502the 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

NameTypeRequiredDescription
chargeIdstringyes

Responses

StatusBodyDescription
200objectOK
400objectThe call is malformed. reason says which half.
404objectNo charge under that id for this tenant.
409objectThe charge exists and is not readable in this state.
502objectThe issuer did not answer.

Response 200

FieldTypeRequiredDescription
amountnumberyesIn units.
amount_minorintegeryesIn minor units.
boleto_bank_linestring,nullyes
boleto_bar_codestring,nullyes
credit_correlation_armedbooleanyesWhether 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.
currencystringyes
due_datestring,nullyes
idstring,nullyesThe issuer's charge id. Null while the issuance is unconfirmed.
issuance_unconfirmedbooleanyesTrue 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_statusstringyesWhat CodeSpar recorded, which is a different question from status.
methodstringyes
payablebooleanyesTrue 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_flightbooleanyesTrue 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_pastestring,nullyesThe Pix leg of the same debt. One receivable, two payable legs.
settlement"confirmed" | "pending" | "unconfirmable"yesThe 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.
statusstringyesThe provider's state as far as we know it, normalized. Forced to PROCESSING until the issuance is confirmed.
status_conflictbooleanyesTrue when the two disagree terminally, instead of one of them silently winning.
Example request
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_KEY
import 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"
  }
});
Example response 200
application/json
{
  "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

POSThttps://api.codespar.dev/v1/charges/{chargeId}/cancel

Withdraw 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.

reasonstatuswhat it means
charge_id_missing400the call named no charge
project_scope_missing400the credential is not project-scoped, and a charge belongs to one project
charge_not_found404no charge under that id FOR THIS TENANT
issuance_unconfirmed409the key holds a reservation whose create never got an answer
already_payment_notified409the clearing house already notified a payment
not_cancellable_in_this_state409the issuer accepts a cancellation only in some states
provider_status_unreadable502the issuer did not answer the read
provider_cancel_failed502the 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

NameTypeRequiredDescription
chargeIdstringyes

Responses

StatusBodyDescription
200objectOK
400objectThe call is malformed.
404objectNo charge under that id for this tenant.
409objectThe charge exists and this state does not accept a withdrawal. already_payment_notified is the D+1 guard and is never a retry case.
502objectThe issuer refused the withdrawal, or did not answer.

Response 200

FieldTypeRequiredDescription
amountnumberyesIn units.
amount_minorintegeryesIn minor units.
boleto_bank_linestring,nullyes
boleto_bar_codestring,nullyes
credit_correlation_armedbooleanyesWhether 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.
currencystringyes
due_datestring,nullyes
idstring,nullyesThe issuer's charge id. Null while the issuance is unconfirmed.
issuance_unconfirmedbooleanyesTrue 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_statusstringyesWhat CodeSpar recorded, which is a different question from status.
methodstringyes
payablebooleanyesTrue 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_flightbooleanyesTrue 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_pastestring,nullyesThe Pix leg of the same debt. One receivable, two payable legs.
settlement"confirmed" | "pending" | "unconfirmable"yesThe 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.
statusstringyesThe provider's state as far as we know it, normalized. Forced to PROCESSING until the issuance is confirmed.
status_conflictbooleanyesTrue when the two disagree terminally, instead of one of them silently winning.
Example request
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_KEY
import 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"
  }
});
Example response 200
application/json
{
  "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
}
Charges | CodeSpar