KYC
2 operations under /v1/kyc/onboard (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/kyc/onboard
https://api.codespar.dev/v1/kyc/onboardOpen a consumer account application (deprecated path)
DEPRECATED alias of POST /v1/account-applications (ent#979), kept for two releases. Same handler, same required scope; switch the path and nothing else changes.
The response is the PROPOSAL, not the account: it returns verification_id and the provider's status, and it is the read by id, after approval, that provisions the account and the Pix funding source. disclosure comes in pt-BR, to be rendered verbatim.
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. |
consumer_id | string | no | The consumer that becomes the owner of the account and of the funding source. |
opt_ins | array of string | no | Opt-ins from the same screen. Today only dda does anything, and it has governance of its own. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Body outside the schema, or a document rejected at the check. RAW BODY, not the { error: { code, message } } envelope. |
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: caller state, not a provider failure. provider_code carries OBE064, CBE022 or OBE062. Raw body. |
502 | object | The provider call failed. Raw body; read message before treating it as an outage. |
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 | — |
disclosure | object,null | yes | — |
hosted_url | string,null | yes | — |
status | string | yes | — |
verification_id | string | yes | — |
curl -X POST https://api.codespar.dev/v1/kyc/onboard \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"buyer": {},
"consumer_id": "csm_0000000000000000",
"opt_ins": [
"string"
]
}'POST /v1/kyc/onboard 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/kyc/onboard",
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/kyc/onboard", {
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/kyc/onboard", {
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/kyc/onboard/{proposalId}/status
https://api.codespar.dev/v1/kyc/onboard/{proposalId}/statusPoll a KYC account application
DEPRECATED alias of GET /v1/account-applications/{id} (ent#979), kept for two releases. Same handler, same required scope; switch the path and nothing else changes. The canonical path spells this same value {id}.
Poll the KYC application and, once the provider approves it, the account and funding source that were provisioned from it. This read is not read-only underneath: approval is what provisions the funding source and registers the Pix key, so the first poll that finds an approved application is the call that creates them. Provisioning is idempotent, so polling again does not create a second one.
Possession is proved by the recorded proposal, never by the query. An application with no recorded proposal is refused rather than trusted, and a document_number that disagrees with the recorded document is refused before any provider call — an approved application of one's own plus somebody else's document number buys nothing.
Requires the kyc:read scope.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
proposalId | string | yes | The application id the create call returned. It addresses a recorded proposal, and an id with no recorded proposal is refused rather than looked up. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumer_id | string | no | The consumer to bind a provisioned account to, when the recorded proposal does not already name one. Precedence runs the other way from what the parameter suggests: the recorded proposal's consumer WINS, and this value is the fallback used only when the proposal recorded none. Omitting it falls back to the id in the path. |
document_number | string | yes | REQUIRED, and its absence is a 400 rather than a default. It is a CLAIM about what the application is for, checked against the document the recorded proposal actually verified, and it never addresses the provider: the account is always fetched for the proposal's own document. A claim that does not match the recorded one is refused before any provider call and nothing is written. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Bad Request — document_number was absent. A bare body with one key, not the { error: { code, message } } envelope the newer routes use. |
403 | object | Forbidden — the provisioning write named a resource outside the organization and project these credentials authenticate for. The body is the tenant control's own two keys and carries nothing from the database's message. |
502 | object | Bad Gateway — the provider lookup or the provisioning that follows it failed. Also the status for a refused ownership proof (no recorded proposal, or a document_number that does not match the one the proposal verified): message distinguishes them, the code does not. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
account | string,null | yes | The provisioned account number, once one exists. |
funding_source_id | string,null | yes | The consumer's funding source, written when the application is approved. Provisioning is idempotent on a key derived from the consumer, so polling this endpoint repeatedly after approval returns the same id and creates nothing new. |
hosted_url | string,null | yes | The document-check webview link while it is outstanding; null otherwise. |
pix_key | string,null | yes | The Pix key registered for the account, either just now or previously — registration returns early when the key already exists, so a repeated poll does not re-register. |
pix_key_registration_error | string | no | Present ONLY when the account was provisioned but its Pix-key registration failed. It is deliberately not an error status: the account is real and usable, and the key is retried lazily the next time the consumer needs to receive. Absent means either the key registered or there is no account yet — read pix_key to tell those apart. |
pix_key_type | string,null | yes | — |
status | "pending" | "documentscopy_pending" | "approved" | "rejected" | yes | Four values, and the fourth is not the type's fifth. The resolver that serves this route is the only producer, and enumerating every one of its returns gives exactly these four; unknown exists in the shared union and is returned by nothing on this path. documentscopy_pending means the holder still has to finish the document check, and hosted_url is where they finish it. pending is the widest of the four: it covers the provider's own PENDING and PROCESSING, the case where it reported no status at all, and a proposal that is past those states but whose account could not yet be read back. Poll again for all three. |
curl -X GET https://api.codespar.dev/v1/kyc/onboard/{proposalId}/status \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/kyc/onboard/{proposalId}/status HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/kyc/onboard/{proposalId}/status",
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/kyc/onboard/{proposalId}/status", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("get", "/v1/kyc/onboard/{proposalId}/status", {
path: {
proposalId: "proposal_0000000000000000"
},
query: {
document_number: "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);
}{
"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"
}Contact Verifications
2 operations under /v1/consumers/{consumerId}/contact-verifications (POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
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.