Boletos you owe (DDA)
4 operations under /v1/consumers/{consumerId}/dda (GET POST DELETE): 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/consumers/{consumerId}/dda/boletos
https://api.codespar.dev/v1/consumers/{consumerId}/dda/boletosThe boletos DDA has mirrored for this consumer
Every boleto issued nationwide against a document this consumer has registered, pushed to us and mirrored locally, filtered to a due-date window. This is the discovery step: it is what lets an agent find a bill instead of being told about it.
A pure read of OUR mirror. DDA has no pull endpoint, so this never calls the provider and can never be stale because the provider is down. What it does not contain is what was never pushed to us.
Covers ALL of the consumer's registered documents at once, since a consumer can hold both a CPF and a CNPJ registration. Both dates are required, inclusive, YYYY-MM-DD. Results are ordered by due date ascending, nulls last. No pagination and no limit: the window is the only bound.
THE WINDOW AND THE ANSWER DO NOT USE THE SAME DATE FORMAT. from_date and to_date go in as YYYY-MM-DD and are echoed back in that form, but each boleto's due_date comes back as a full ISO-8601 date-time at midnight UTC. See the field's own note for why, and do not round-trip a due_date straight back into from_date.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
from_date | string | yes | — |
to_date | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | from_date or to_date is missing or is not YYYY-MM-DD. details.issues carries the validation issues. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
boletos | array of object | yes | — |
count | integer | yes | The length of boletos. No pagination, so this is the total for the window. |
from_date | string | yes | Echoes the validated from_date query parameter, so exactly YYYY-MM-DD. |
to_date | string | yes | Echoes the validated to_date query parameter, so exactly YYYY-MM-DD. |
curl -X GET https://api.codespar.dev/v1/consumers/{consumerId}/dda/boletos \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/consumers/{consumerId}/dda/boletos HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/consumers/{consumerId}/dda/boletos",
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/consumers/{consumerId}/dda/boletos", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/consumers/{consumerId}/dda/boletos", {
path: {
consumerId: "csm_0000000000000000"
},
query: {
from_date: "string",
to_date: "string"
}
});{
"from_date": "string",
"to_date": "string",
"count": 1,
"boletos": [
{
"linha_digitavel": "string",
"due_date": "2026-01-15T12:00:00.000Z",
"original_amount_minor": 1000,
"beneficiary": "string",
"status": "string"
}
]
}POST /v1/consumers/{consumerId}/dda/subscriptions
https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptionsRegister the consumer's CPF/CNPJ with DDA
Registers the consumer's document with DDA, Brazil's national boleto-visibility register. The effect is broad and worth spelling out: from then on EVERY boleto issued against that document, by any issuer in the country, is pushed to us and mirrored. That is why the operation asks for two separate proofs.
PROOF 1, OWNERSHIP. The document must be the consumer's VERIFIED document. The consumer entity answers when it carries one, and the funding source answers when it does not; if the two disagree the call is refused rather than settled by picking one. All three refusals answer dda_document_not_owned, and they are NOT one answer: the two 403s are word-for-word identical and cannot be told apart from outside, while an account with no verified document at all answers 422 and says so, because that one is fixed by finishing onboarding rather than by sending a different document. What none of the three ever does is name the document it expected — no refusal here is an oracle for anyone's CPF, and none of them carries details.
PROOF 2, CONSENT. The document must be named in the SIGNED dda_allowlist of an active mandate of that consumer (intent.dda_allowlist at POST /v1/consents/init). There is no wildcard: a literal "*" in the list is normalized like any other entry and matches nothing. With no mandate authorizing it, 403, and nothing was registered.
idempotency_key is REQUIRED. Together with the organization, the consumer and the document it derives the provider-side anchor, so re-sending the SAME key is the same registration and never a second one against a person's document — while the same key with a different document is a different registration. The HTTP status separates the two outcomes: 201 when this call registered, 200 when it did not.
Punctuation in document is accepted and normalized; the comparison is on digits. A new registration is pending and turns active when the confirmation webhook arrives, so subscribe to dda.subscription.activated or re-read it rather than waiting for active here.
SCOPE: DDA is per organization, not per project. The table carries no project column, so this route answers for the caller's whole organization.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
client_name | string | no | — |
document | string | yes | — |
idempotency_key | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Nothing was registered by this call: an attempt under this same key already holds the registration, or one is in flight. replayed is true and status is the row's current state. |
201 | object | This call registered the document. replayed is false and status is pending. |
400 | object | invalid_body, from three places: the body did not match the schema (and only then is details.issues present); document carries no CPF/CNPJ digits; idempotency_key is blank. Nothing was registered. |
403 | object | dda_document_not_owned: the document is not the one verified for this consumer, or the consumer entity and the funding source disagree about who this consumer is. dda_document_unauthorized: no active mandate names this document in its signed allowlist. Nothing was registered, and neither carries details. |
422 | object | no_provider_account: the consumer has no active pix-celcoin funding source in this organization — or is not this organization's consumer at all. dda_document_not_owned ALSO answers 422, and only for one cause: the account has no verified document on file at all, so no document can be shown to be theirs. Sending a different document will not help; finish onboarding and retry. |
500 | object | object | NOT the usual error envelope. Three reads — the consumer's account, the verified document, and the authorizing mandate — run before the handler's own error handling, so a failure in any of them is answered by the framework's default: { statusCode, error, message }, plus code when the underlying failure carried one. All three sit before the provider is called, so nothing was registered. |
502 | object | dda_provider_unavailable, from two conditions the response does not separate: the provider answered and the answer was not a success (the local row is marked error), or the call was in flight and whether it arrived is genuinely not known here. Because the caller cannot tell them apart, the guidance is the same for both — retry with the SAME idempotency_key, which makes the retry the same registration rather than a second one. |
503 | object | dda_claim_unavailable: the local claim failed and NOTHING was sent to the provider. This is stated apart from the 502 on purpose — here it is PROVEN that nothing left. Retry with the SAME idempotency_key. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
client_request_id | string | yes | The provider-side anchor for this registration. Derived from (organization, consumer, document, idempotency_key), so the SAME key against a DIFFERENT document is a DIFFERENT registration. |
document | string | yes | Digits only. This is the document the consumer SIGNED in the mandate's allowlist, not the spelling that arrived in the body. |
mandate_id | string | yes | The active mandate whose signed allowlist authorized this. |
replayed | boolean | yes | true when this call registered nothing. Either an earlier attempt under this key already holds the registration, or one is in flight right now. |
status | "pending" | "active" | "error" | "deleted" | yes | A registration this call made is always pending, and becomes active only when the provider's confirmation webhook lands. A 200 (nothing registered by this call) reports the row's current state, which can be any of the four. |
curl -X POST https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"document": "string",
"client_name": "Example",
"idempotency_key": "string"
}'POST /v1/consumers/{consumerId}/dda/subscriptions HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"document": "string",
"client_name": "Example",
"idempotency_key": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"document": "string",
"client_name": "Example",
"idempotency_key": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"document": "string",
"client_name": "Example",
"idempotency_key": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/consumers/{consumerId}/dda/subscriptions", {
path: {
consumerId: "csm_0000000000000000"
},
body: {
document: "string",
client_name: "Example",
idempotency_key: "string"
}
});
// r.status is one of the documented statuses (200, 403, 422),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"document": "string",
"status": "pending",
"client_request_id": "clientrequest_0000000000000000",
"mandate_id": "mandate_0000000000000000",
"replayed": true
}GET /v1/consumers/{consumerId}/dda/subscriptions/{document}
https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions/{document}Read a DDA registration
Whether this consumer's document is registered with DDA, Brazil's national boleto-visibility register, and what state the registration is in. A pure local read: there is nothing to poll upstream, because the provider's confirmation webhook is the only thing that moves the status off pending.
The path parameter accepts a punctuated or bare CPF/CNPJ; the digits are what is matched. A value carrying no digits is 400. Another organization's registration answers 404, not 403, so its existence cannot be probed. Scoped to the organization, not the project: the underlying table carries no project column.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
document | string | yes | CPF or CNPJ. Punctuation is accepted and stripped. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The path's document carries no CPF/CNPJ digits. |
404 | object | No DDA registration for this consumer and document. Also the answer for a consumer another organization holds. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
celcoin_subscription_id | string,null | yes | The provider's own id, null until the confirmation webhook supplies it. |
client_request_id | string | yes | The idempotency correlation carried to the provider on the registration. |
document | string | yes | Digits only. The stored form, which is what the provider leg was sent. |
status | "pending" | "active" | "error" | "deleted" | yes | pending until the provider's confirmation webhook lands. Nothing polls the provider for this: the webhook is the only source of a status change, so a registration can sit pending indefinitely if a delivery was lost. |
curl -X GET https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions/{document} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/consumers/{consumerId}/dda/subscriptions/{document} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions/{document}",
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/consumers/{consumerId}/dda/subscriptions/{document}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/consumers/{consumerId}/dda/subscriptions/{document}", {
path: {
consumerId: "csm_0000000000000000",
document: "string"
}
});{
"document": "string",
"status": "pending",
"client_request_id": "clientrequest_0000000000000000",
"celcoin_subscription_id": "celcoinsubscription_0000000000000000"
}DELETE /v1/consumers/{consumerId}/dda/subscriptions/{document}
https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions/{document}End a DDA registration
Withdraws the document from the national register, then marks the local row deleted. The local row and its mirrored boleto history are kept, not dropped.
Deliberately NOT consent-gated, unlike the registration: a consumer who revoked their mandate must still be able to withdraw a document registered in their name, and gating this would trap them in the exposure. Existence is the tenancy guard, which is also what stops a caller sending the provider a delete for a document its organization never registered.
Convergent rather than idempotency-keyed: the target state is not registered, which repetition cannot overshoot. A row already deleted answers success without calling the provider again.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
document | string | yes | CPF or CNPJ. Punctuation is accepted and stripped. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The path's document carries no CPF/CNPJ digits. |
404 | object | No DDA registration for this consumer and document. Also returned when the row disappeared between the ownership read and the provider call, because in that case nothing was sent upstream. |
502 | object | The registration could not be ended upstream. Nothing about the local row changed; retry later. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
document | string | yes | — |
status | "deleted" | yes | — |
curl -X DELETE https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions/{document} \
-H "Authorization: Bearer $CODESPAR_API_KEY"DELETE /v1/consumers/{consumerId}/dda/subscriptions/{document} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.delete(
"https://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions/{document}",
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/consumers/{consumerId}/dda/subscriptions/{document}", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.delete("/v1/consumers/{consumerId}/dda/subscriptions/{document}", {
path: {
consumerId: "csm_0000000000000000",
document: "string"
}
});{
"document": "string",
"status": "deleted"
}Pix out
2 operations under /v1/consumers/{consumerId}/pix/devolutions (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Consents
4 operations under /v1/consents (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.