Skip to main content

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.

9 min read
View MarkdownEdit on GitHub

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

Every operation below requires a Bearer token. See Authentication.

GET /v1/consumers/{consumerId}/dda/boletos

GEThttps://api.codespar.dev/v1/consumers/{consumerId}/dda/boletos

The 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

NameTypeRequiredDescription
consumerIdstringyes—

Query parameters

NameTypeRequiredDescription
from_datestringyes—
to_datestringyes—

Responses

StatusBodyDescription
200objectOK
400objectfrom_date or to_date is missing or is not YYYY-MM-DD. details.issues carries the validation issues.

Response 200

FieldTypeRequiredDescription
boletosarray of objectyes—
countintegeryesThe length of boletos. No pagination, so this is the total for the window.
from_datestringyesEchoes the validated from_date query parameter, so exactly YYYY-MM-DD.
to_datestringyesEchoes the validated to_date query parameter, so exactly YYYY-MM-DD.
Example request
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_KEY
import 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"
  }
});
Example response 200
application/json
{
  "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

POSThttps://api.codespar.dev/v1/consumers/{consumerId}/dda/subscriptions

Register 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

NameTypeRequiredDescription
consumerIdstringyes—

Request body

FieldTypeRequiredDescription
client_namestringno—
documentstringyes—
idempotency_keystringyes—

Responses

StatusBodyDescription
200objectNothing 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.
201objectThis call registered the document. replayed is false and status is pending.
400objectinvalid_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.
403objectdda_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.
422objectno_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.
500object | objectNOT 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.
502objectdda_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.
503objectdda_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

FieldTypeRequiredDescription
client_request_idstringyesThe 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.
documentstringyesDigits only. This is the document the consumer SIGNED in the mandate's allowlist, not the spelling that arrived in the body.
mandate_idstringyesThe active mandate whose signed allowlist authorized this.
replayedbooleanyestrue 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"yesA 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.
Example request
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);
}
Example response 200
application/json
{
  "document": "string",
  "status": "pending",
  "client_request_id": "clientrequest_0000000000000000",
  "mandate_id": "mandate_0000000000000000",
  "replayed": true
}

GET /v1/consumers/{consumerId}/dda/subscriptions/{document}

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

NameTypeRequiredDescription
consumerIdstringyes—
documentstringyesCPF or CNPJ. Punctuation is accepted and stripped.

Responses

StatusBodyDescription
200objectOK
400objectThe path's document carries no CPF/CNPJ digits.
404objectNo DDA registration for this consumer and document. Also the answer for a consumer another organization holds.

Response 200

FieldTypeRequiredDescription
celcoin_subscription_idstring,nullyesThe provider's own id, null until the confirmation webhook supplies it.
client_request_idstringyesThe idempotency correlation carried to the provider on the registration.
documentstringyesDigits only. The stored form, which is what the provider leg was sent.
status"pending" | "active" | "error" | "deleted"yespending 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.
Example request
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_KEY
import 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"
  }
});
Example response 200
application/json
{
  "document": "string",
  "status": "pending",
  "client_request_id": "clientrequest_0000000000000000",
  "celcoin_subscription_id": "celcoinsubscription_0000000000000000"
}

DELETE /v1/consumers/{consumerId}/dda/subscriptions/{document}

DELETEhttps://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

NameTypeRequiredDescription
consumerIdstringyes—
documentstringyesCPF or CNPJ. Punctuation is accepted and stripped.

Responses

StatusBodyDescription
200objectOK
400objectThe path's document carries no CPF/CNPJ digits.
404objectNo 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.
502objectThe registration could not be ended upstream. Nothing about the local row changed; retry later.

Response 200

FieldTypeRequiredDescription
documentstringyes—
status"deleted"yes—
Example request
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_KEY
import 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"
  }
});
Example response 200
application/json
{
  "document": "string",
  "status": "deleted"
}
Boletos you owe (DDA) | CodeSpar