Account Applications
2 operations under /v1/account-applications (POST GET): 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.
POST /v1/account-applications
https://api.codespar.dev/v1/account-applicationsOpen a consumer account application
Opens a consumer's account application at the provider, and this is the path by which a consumer comes to have an account and a Pix funding source.
The response is not the account, it is the application. What comes back is verification_id and a status from the provider; the account and the funding source appear when GET /v1/account-applications/{id} is read after the approval: that read is what provisions. A flow that expects an account here keeps waiting.
The disclosure is there to be rendered. It names the institution that holds the account and CodeSpar's role as correspondente; the text comes in pt-BR to appear as it is, not to be rewritten.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
buyer | object | yes | The identity fields the provider asks for: documentNumber (CPF or CNPJ), full name, date of birth, address. Passed through as an object: the handler does not validate it field by field here, and whatever the provider refuses comes back in the provider's own refusal. Two exceptions are checked before anything is sent: the document's check digits, and, for a natural person (CPF) in a live project, financialDetails = { declaredIncome, occupation, netWorth } in the provider's codes. That is the titular's own declaration, and CodeSpar never fills it in for them. |
consumer_id | string | no | The consumer who becomes the owner of the account. It becomes the onboarding's clientCode and the owner of the funding source the approval produces. |
opt_ins | array of string | no | Opt-ins collected on the same screen. Today only dda does anything, and it requires the document to be this consumer's verified one AND to be on a signed dda_allowlist. Without that the registration is refused and the rest of the onboarding continues. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The body did not match the schema, the document did not pass the check, or a natural person's onboarding in a live project came without buyer.financialDetails (one issue per missing code, its path naming it). BARE BODY, not the { error: { code, message } } envelope: this handler writes its failure inline and there is no request_id. |
403 | object | object | The organization's policy decided, and nothing was dispatched. approval_required: a rule holds the call for a person; approval_id is the row in the approvals queue and expires_at when it lapses. Approving it runs the call then, through the approvals lane, not this request. policy_denied: a deny rule (or the non-overridable deny-list) refused it; rule_id names the rule. Bare body. |
409 | object | ONBOARDING COLLISION, and it is caller state and not a provider failure, which is why 409 and not 502. provider_code carries the provider's code (OBE064 proposal already open, CBE022 account already exists, OBE062 clientCode already linked) so the audit trail records the ground for the refusal. Bare body. |
502 | object | The provider call failed, or the router landed on an institution that is not the expected one. Bare body; message carries the underlying sentence and is what separates a provider outage from a refusal by the provider. |
503 | object | The policy engine could not answer, so the call was refused fail-closed and nothing was dispatched. Infrastructure, not a rule: retry. Bare body. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
dda_opt_in | object | no | Present only when opt_ins asked for dda. pending is the normal path: the registration is asynchronous and turns active when the provider's confirmation arrives, so treat pending as in progress and not as a failure. |
disclosure | object,null | yes | Null when the router did not land on an institution with registered text. |
hosted_url | string,null | yes | The verification page, when the provider uses one. Null when it is all done through the API. |
status | string | yes | Where the proposal stands at the provider, in the provider's own word. |
verification_id | string | yes | The provider's proposal id. It is what GET /v1/account-applications/{id} reads. |
curl -X POST https://api.codespar.dev/v1/account-applications \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"buyer": {},
"consumer_id": "csm_0000000000000000",
"opt_ins": [
"string"
]
}'POST /v1/account-applications HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"buyer": {},
"consumer_id": "csm_0000000000000000",
"opt_ins": [
"string"
]
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/account-applications",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"buyer": {},
"consumer_id": "csm_0000000000000000",
"opt_ins": [
"string"
]
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/account-applications", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"buyer": {},
"consumer_id": "csm_0000000000000000",
"opt_ins": [
"string"
]
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/account-applications", {
body: {
buyer: {},
consumer_id: "csm_0000000000000000",
opt_ins: [
"string"
]
}
});
// 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);
}{
"verification_id": "verification_0000000000000000",
"status": "string",
"hosted_url": "https://example.com/hook",
"disclosure": {
"institution": "string",
"institution_document": "string",
"codespar_role": "correspondente",
"text": "string"
},
"dda_opt_in": {
"status": "pending",
"message": "string"
}
}GET /v1/account-applications/{id}
https://api.codespar.dev/v1/account-applications/{id}Read the status of a consumer account application
Where a consumer's account onboarding stands, and — once the provider has approved it — the account and the Pix funding source it produced. Requires the kyc:read scope.
THIS IS NOT A PURE READ, and a caller should plan around that. On the approved branch the handler resolves the account from the provider and then persists: it inserts (or re-resolves) the consumer's pix-celcoin funding source, registers a Pix key for it, and anchors the consumer record. Every approval path in the product converges on that one place, so polling this endpoint is how an application becomes a usable funding source. It is idempotent — the funding source id is derived from the consumer id, and re-provisioning merges into the existing row rather than replacing it, so a Pix key already registered is not erased.
document_number IS REQUIRED, and the reason is ownership. It is never used to address the provider: the handler loads the proposal recorded under this id and compares. A mismatch, or an id with no recorded proposal, is refused before any provider call and provisions nothing — the attack it closes is an attacker's own approved proposal presented with a victim's document.
THOSE OWNERSHIP REFUSALS ARRIVE AS 502, not 403. The handler's catch is blanket, so a typed ownership refusal is relabelled kyc_status_failed with the refusal's own sentence in message. Read the message, not just the code, before treating a 502 here as a provider outage.
The query is NOT schema-validated. The handler reads request.query directly and checks only that document_number is present; any other parameter is ignored rather than rejected.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | The provider's proposal id, returned by POST /v1/account-applications. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumer_id | string | no | A fallback only. The recorded proposal's own consumer id wins; this value is used when the recorded row carries none. Omitted, the proposal id stands in. |
document_number | string | yes | REQUIRED. The CPF or CNPJ the caller claims this application is about, checked against the document recorded for the proposal. Formatting is normalized before the comparison. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Bad Request — document_number was not supplied. A BARE error body, not the { error: { code, message } } envelope: this handler writes its failures inline and there is no request_id in the body. |
502 | object | Bad Gateway. The provider call failed — and ALSO where the typed ownership refusals land, because the handler's catch is blanket. message carries the underlying sentence and is the only thing that separates a provider outage from a refusal to bind a document the proposal did not verify. Bare body, no request_id. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
account | string,null | yes | The provisioned account number once one exists, else null. Stringified: the provider returns it as either a string or a number. The branch is resolved in the same fetch and is NOT returned by this operation; it is returned as the funding source's branch. |
funding_source_id | string,null | yes | The pix-celcoin funding source, or null before one exists. Deterministic — fs_celcoin_ followed by the consumer id, one account per consumer — so a repeat call resolves the same row instead of opening a second one. |
hosted_url | string,null | yes | The documentoscopia webview the consumer opens, taken from the proposal's first documentscopys entry. Null on rejected and on approved. |
pix_key | string,null | yes | The DICT entry registered for the account, once registration has succeeded. Null before that. |
pix_key_registration_error | string | no | ABSENT from the body unless Pix-key registration failed for an account that is otherwise real and usable. Deliberately non-blocking and deliberately not swallowed: the account works, the wallet's own lazy registration retries the step the next time the consumer needs to receive, and the failure is surfaced here so a caller can log or alert on it. Its presence does not make the application unapproved. |
pix_key_type | string,null | yes | — |
status | "pending" | "documentscopy_pending" | "approved" | "rejected" | yes | FOUR values, and the exported CelcoinOnboardingStatus union has five. unknown is in the type and is not in this enum because resolveCelcoinOnboarding — the only producer this route has — never returns it: its four return statements carry rejected, documentscopy_pending, pending (twice) and approved. The only other "unknown" in that file is a conflict_case, which is a different field on a different shape. pending is also the answer when the proposal is past the known pending states but no account number could be parsed from the provider's fetch response. That case is logged with the raw body truncated, so a genuine not-minted-yet and a parse that is wrong look the same to the caller and different in the log. |
curl -X GET https://api.codespar.dev/v1/account-applications/{id} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/account-applications/{id} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/account-applications/{id}",
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/account-applications/{id}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/account-applications/{id}", {
path: {
id: "accountapplication_0000000000000000"
},
query: {
document_number: "string"
}
});{
"status": "pending",
"hosted_url": "https://example.com/hook",
"account": "string",
"funding_source_id": "fundingsource_0000000000000000",
"pix_key": "string",
"pix_key_type": "string",
"pix_key_registration_error": "string"
}KYC
2 operations under /v1/kyc/onboard (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Counterparties
3 operations under /v1/counterparties (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.