Pix keys (DICT)
3 operations under /v1/consumers/{consumerId}/pix-keys (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}/pix-keys
https://api.codespar.dev/v1/consumers/{consumerId}/pix-keysList the Pix keys on the consumer's account
The account's live DICT inventory, read from the provider on every call. The account is always resolved server-side from the credential's organization and the consumer id, never taken from the caller, so this can only ever reach an account the caller's own organization holds.
A failed inventory read is an ERROR, never an empty list. unanswered is not no keys, and rendering it as an empty account is the mistake this route refuses to make: a caller that read zero keys would go on to register a key the account may already have, spending one of its limited DICT slots.
In a Test project, a consumer with no active Pix funding source is served from the account bound to it by the project's open account binding, while the project's collect capability is active. That read is counted in the project's provider call budget first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
422 | object | no_provider_account: the consumer has no active Pix funding source, so there is no account to list. pix_key_provider_refused: the provider adjudicated and said no; details.provider_code names its business code, and retrying the same input cannot succeed. |
429 | object | project_access_call_cap: the project's provider call budget for the window is used up (Test bound account only). details names the window, the scope and the cap. Nothing was sent. |
502 | object | The listing could not be completed upstream. The outcome is unknown and the condition is transient; retry. details.provider_code still carries whatever code the body named, so a generic provider error is visible without being called terminal. |
503 | object | project_access_calls_unavailable: the project's provider call budget could not be read (Test bound account only). Nothing was sent; retry. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
account_masked | string | yes | Four mask characters followed by the account's last two digits. |
count | integer | yes | — |
keys | array of object | yes | — |
curl -X GET https://api.codespar.dev/v1/consumers/{consumerId}/pix-keys \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/consumers/{consumerId}/pix-keys 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}/pix-keys",
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}/pix-keys", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("get", "/v1/consumers/{consumerId}/pix-keys", {
path: {
consumerId: "csm_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
console.log(r.data);
}{
"account_masked": "string",
"keys": [
{
"key": "string",
"key_type": "string"
}
],
"count": 1
}POST /v1/consumers/{consumerId}/pix-keys
https://api.codespar.dev/v1/consumers/{consumerId}/pix-keysRegister a Pix key on the consumer's account
Registers a DICT entry on the consumer's account. The account is ALWAYS resolved on the server from (organization, consumerId) — there is no account field in the body — so a caller can only ever reach an account of a consumer its own organization holds.
key_type is explicit, EVP included, so nobody mints a random key by omission. For EVP the key field must be ABSENT (the provider mints it); for EMAIL, PHONE, CPF and CNPJ it is the key value. A CNPJ key is 14 ALPHANUMERIC characters, which is the DICT rule in force, not 14 digits.
A CPF/CNPJ KEY MAY ONLY BE THE HOLDER'S OWN DOCUMENT, which is a DICT rule. The check is against the document recorded on the account and it fails closed whenever that document is known; where the record predates it and carries none, the provider adjudicates.
AN EMAIL/PHONE KEY MAY REQUIRE PROOF OF POSSESSION: while that check is enforced on the deployment, the contact must have been verified first through POST /v1/consumers/{consumerId}/contact-verifications, and its absence answers 409 pix_key_ownership_unverified before the provider is called at all.
THE 409 pix_key_registration_uncertain IS THE CASE TO READ BEFORE INTEGRATING: the registration was accepted, the response was unreadable, and the account's key list could not settle whether the key landed. DO NOT REPEAT IT. Every repeated EVP mints ANOTHER key against the account's five-key allowance. List the keys with the GET on this same path and delete whatever should not be there.
422 AGAINST 502 IS ALSO DELIBERATE: 422 pix_key_provider_refused is the provider having ADJUDICATED and said no, and repeating the same input cannot succeed; 502 pix_key_provider_unavailable is transient and retriable. Their details differ, and the 502 block below says how — that field is not uniform across the three failures that produce it.
In a Test project, a consumer with no active Pix funding source is served from the account bound to it by the project's open account binding, while the project's collect capability is active. That account registers only a random key (key_type EVP): any other type answers 422 pix_key_type_unavailable before anything is sent, and the registration is counted in the project's provider call budget first.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
key | string | no | — |
key_type | "EVP" | "CPF" | "CNPJ" | "EMAIL" | "PHONE" | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | OK |
400 | object | invalid_body: the body did not match the schema (details.issues). pix_key_invalid: the value is not a valid key for the key_type asked for, or key came with an EVP request, or key was missing for every other type. Neither reached the provider. |
403 | object | pix_key_document_mismatch: a CPF/CNPJ key whose value is not the account holder's own document. Refused locally, without calling the provider. |
409 | object | pix_key_ownership_unverified: this e-mail or phone has no recent verification for this consumer. details carries the channel, the endpoint to call and the window a verification stays good for; start a contact verification, confirm the code, retry. Nothing reached the provider. pix_key_registration_uncertain: the registration was accepted and came back with no readable key, and the account's key list could not settle whether it landed. details carries retry_safe: false, inventory_known and inventory_count. LIST the keys instead of repeating the registration. |
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; nothing reached the provider. pix_key_provider_refused: the provider adjudicated and refused, with details.provider_status and details.provider_code (null when the body named no code); repeating the same input will not pass. pix_key_type_unavailable: a Test project's bound account registers only an EVP key; nothing was sent. |
429 | object | project_access_call_cap: the project's provider call budget for the window is used up (Test bound account only). details names the window, the scope and the cap. Nothing was sent. |
500 | object | object | NOT the usual error envelope. Resolving the consumer's account runs before the handler's own error handling, so a failure there is answered by the framework's default: { statusCode, error, message }, plus code when the underlying failure carried one. It happens before the provider is called, so nothing was registered. |
502 | object | pix_key_provider_unavailable, and THREE different failures produce it with THREE different details. Read the field, not the code. 1. A transient upstream failure — a 5xx, transport, credentials, a generic internal code, a codeless 429/408/404. details.provider_status and details.provider_code (null when the body named no code). 2. A registration that PROVABLY did not land: the account's key list answered and does not carry the key. details.verified_not_registered is true and details.inventory_count is the number of keys the list reported. There is NO provider_code here — no provider code was involved in reaching this conclusion. 3. Anything the handler did not classify. No details at all. All three are retriable; only the second one is retriable on evidence rather than on the absence of it. |
503 | object | project_access_calls_unavailable: the project's provider call budget could not be read (Test bound account only). Nothing was sent; retry. |
Response 201
| Field | Type | Required | Description |
|---|---|---|---|
account_masked | string | yes | The account, masked to its last two digits. |
cache_filled | boolean | yes | false when the key WAS registered but the local cache could not be filled. Not a registration failure. |
key | string | yes | The registered key. For EVP this is the random key the provider minted, and this response is the only place it appears in this call. |
key_type | string | yes | The type as the provider returned it. Deliberately an open string rather than the request's enum: the value comes back from upstream and is carried through untyped. |
reconciled | boolean | yes | true when the key returned here came from re-reading the account's key list, because the registration response was unreadable. The key belongs to the account; THIS call is not proven to be what created it. |
curl -X POST https://api.codespar.dev/v1/consumers/{consumerId}/pix-keys \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key_type": "EVP",
"key": "string"
}'POST /v1/consumers/{consumerId}/pix-keys HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"key_type": "EVP",
"key": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/consumers/{consumerId}/pix-keys",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"key_type": "EVP",
"key": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/consumers/{consumerId}/pix-keys", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"key_type": "EVP",
"key": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/consumers/{consumerId}/pix-keys", {
path: {
consumerId: "csm_0000000000000000"
},
body: {
key_type: "EVP",
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);
}{
"key": "string",
"key_type": "string",
"account_masked": "string",
"cache_filled": true,
"reconciled": true
}DELETE /v1/consumers/{consumerId}/pix-keys/{key}
https://api.codespar.dev/v1/consumers/{consumerId}/pix-keys/{key}Delete a Pix key from the consumer's account
Removes the key from DICT, then reconciles the local cache of the consumer's receiving key. The key is canonicalized before both steps, so a CPF registered bare and deleted punctuated is the same key.
The cache reconciliation is part of the contract, not a detail. An empty cache is what makes the wallet mint a fresh random EVP nobody asked for, burning a DICT slot and switching the consumer onto a key they did not choose. So when the delete clears the cache, the account's remaining keys are re-read and one is cached back, preferring a non-EVP key. cache_refilled_with reports which. The just-deleted value is excluded from the candidates, because DICT releases a deleted entry asynchronously and can still list it for a minute or more.
The key travels in a path segment. Fastify percent-decodes it, and a literal + in a path segment stays a +, so e-mail and phone keys are safe either way.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
key | string | yes | The key value. Trimmed; an empty value is 400. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The key in the path is empty after trimming. |
422 | object | no_provider_account: the consumer has no active Pix funding source. pix_key_provider_refused: the provider adjudicated and refused; retrying the same input cannot succeed. details.provider_code carries its business code. |
502 | object | The deletion could not be completed upstream. Whether the key is gone is unknown; retry, then list the keys to confirm. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
account_masked | string | yes | — |
cache_cleared | boolean | yes | Whether the deleted key was the one this API had cached as the receiving key. |
cache_refilled_with | string,null | yes | When the cache was cleared, the account's remaining keys are re-read and one is cached back, preferring a non-EVP key. Null when nothing was cleared, nothing remains, or the inventory read did not answer. |
deleted | true | yes | — |
key | string | yes | The canonical form actually sent upstream. A punctuated CPF or CNPJ is normalized before the delete, so this can differ from the value in the path. |
curl -X DELETE https://api.codespar.dev/v1/consumers/{consumerId}/pix-keys/{key} \
-H "Authorization: Bearer $CODESPAR_API_KEY"DELETE /v1/consumers/{consumerId}/pix-keys/{key} 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}/pix-keys/{key}",
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}/pix-keys/{key}", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("delete", "/v1/consumers/{consumerId}/pix-keys/{key}", {
path: {
consumerId: "csm_0000000000000000",
key: "string"
}
});
// r.status is one of the documented statuses (200, 422),
// each with its own body shape in r.data; nothing here throws on 422.
if (r.ok) {
console.log(r.data);
}{
"key": "string",
"account_masked": "string",
"deleted": true,
"cache_cleared": true,
"cache_refilled_with": "string"
}Agents
6 operations under /v1/agents (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Bank consents
6 operations under /v1/bank-consents (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.