Consumer funding
4 operations under /v1/consumers/{consumerId}/fund (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/consumers/{consumerId}/fund/pix-in
https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-inMint a sandbox Pix charge to fund a consumer (deprecated path)
DEPRECATED alias of POST /v1/test/pix-in (ent#979), kept for two releases. One real difference: here the consumer comes in the route SEGMENT, and the body's consumer_id is ignored.
Mints a sandbox Pix charge and returns the copia-e-cola string. Nothing is credited here: this step produces the code, and settlement is the next step.
Keep the transaction_id: it is what settlement uses as the idempotency key.
A consumer whose account is bound to this Test project (the project's open account binding, its collect capability active) gets the charge on that account's OWN Pix key, through the default provider; the call is counted in the project's provider call budget first. The charge is recorded like any charge of the consumer's, so its settlement, its webhook and the reconciler credit it once.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount_minor | integer | yes | BRL cents. |
consumer_id | string | no | Required on the /v1/test/* path, where the route has no consumer segment. Ignored on the /v1/consumers/{consumerId}/... alias, where the segment wins. |
description | string | no | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | OK |
400 | object | The body is outside the schema, or consumer_id is missing on the /v1/test/* path (where it is required, because the route has no consumer segment). |
403 | object | A LIVE environment key. These routes credit fake money and exist only in a test environment: use a csk_test_* key on a test project. The environment that was read comes in details.environment. |
422 | object | The sandbox provider refused the mint. |
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. |
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 |
|---|---|---|---|
amount_minor | integer | yes | — |
currency | "BRL" | yes | — |
pix_copia_e_cola | string | yes | The code that gets paid, the way a real Pix is paid. |
pix_key | string | yes | — |
rail | "pix-celcoin" | yes | — |
transaction_id | string | yes | The idempotency key of the settlement step. |
curl -X POST https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"description": "string"
}'POST /v1/consumers/{consumerId}/fund/pix-in HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"description": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"description": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"description": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/consumers/{consumerId}/fund/pix-in", {
path: {
consumerId: "csm_0000000000000000"
},
body: {
consumer_id: "csm_0000000000000000",
amount_minor: 1000,
description: "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);
}{
"pix_copia_e_cola": "string",
"transaction_id": "transaction_0000000000000000",
"pix_key": "string",
"amount_minor": 1000,
"currency": "BRL",
"rail": "pix-celcoin"
}POST /v1/consumers/{consumerId}/fund/pix-in/settle
https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in/settleSettles the charge the mint produced and credits the consumer.
Settle a sandbox Pix charge and credit the consumer (deprecated path)
DEPRECATED alias of POST /v1/test/settle-pix-in (ent#979), kept for two releases. One real difference: here the consumer comes in the route SEGMENT, and the body's consumer_id is ignored.
Settles the charge the mint produced and credits the consumer.
The transaction_id is the idempotency key: re-sending the same settlement does not credit twice.
For a consumer whose account is bound to this Test project, the settlement credits THAT account, for a charge the mint recorded for this consumer: transaction_id is required and must be the one the mint returned, and amount_minor must be the charge's amount. The credit is claimed before it is sent, so a retry answers from the claim (200, idempotent_replay), a charge already settled by its webhook or the reconciler answers the same way, and a credit whose outcome is unknown stays open (409 sandbox_funding_open_claim) and is never resent. It needs the project's payments capability, and it is counted in the provider call budget.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
amount_minor | integer | yes | — |
consumer_id | string | no | Required on the /v1/test/* path, where the route has no consumer segment. Ignored on the /v1/consumers/{consumerId}/... alias, where the segment wins. |
transaction_id | string | yes | The transaction_id the mint returned. It is the IDEMPOTENCY KEY of the credit and of the wallet mirror: re-sending the same settlement does not credit twice. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
201 | object | OK |
400 | object | The body is outside the schema, or consumer_id is missing on the /v1/test/* path (where it is required, because the route has no consumer segment). |
403 | object | A LIVE environment key. These routes credit fake money and exist only in a test environment: use a csk_test_* key on a test project. The environment that was read comes in details.environment. |
404 | object | charge_not_found: no single charge minted for this consumer in this project carries this transaction_id (Test bound account only). Nothing was sent. |
409 | object | Test bound account only. charge_not_pending: the charge is no longer open. capability_inactive: the project's payments capability is not active. sandbox_funding_open_claim: an earlier credit of the project is still open (unanswered, or credited without its ledger copy); it is never resent. Nothing was sent. |
422 | object | The sandbox provider refused the settlement (sandbox_funding_failed), or, for a Test bound account, amount_minor is not the charge's amount (settle_amount_mismatch). |
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 | The sandbox account was credited and the mirror in the wallet ledger failed: split state, and details.deposit_id is where to reconcile from. |
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 |
|---|---|---|---|
amount_minor | integer | yes | — |
currency | "BRL" | yes | — |
deposit_id | string,null | yes | The provider's credit. Null only on a replay of a charge its webhook or the reconciler settled. |
idempotent_replay | boolean | no | True when this answer is a replay: nothing was sent again. |
money_credited | true | yes | — |
settled | true | yes | — |
transaction_id | string | yes | — |
curl -X POST https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in/settle \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"transaction_id": "transaction_0000000000000000"
}'POST /v1/consumers/{consumerId}/fund/pix-in/settle HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"transaction_id": "transaction_0000000000000000"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in/settle",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"transaction_id": "transaction_0000000000000000"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in/settle", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"transaction_id": "transaction_0000000000000000"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/consumers/{consumerId}/fund/pix-in/settle", {
path: {
consumerId: "csm_0000000000000000"
},
body: {
consumer_id: "csm_0000000000000000",
amount_minor: 1000,
transaction_id: "transaction_0000000000000000"
}
});
// 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);
}{
"settled": true,
"transaction_id": "transaction_0000000000000000",
"deposit_id": "deposit_0000000000000000",
"idempotent_replay": true,
"amount_minor": 1000,
"currency": "BRL",
"money_credited": true
}POST /v1/consumers/{consumerId}/fund/sandbox
https://api.codespar.dev/v1/consumers/{consumerId}/fund/sandboxCredits the consumer's sandbox account directly, without going through a Pix.
Credit a consumer in the sandbox (deprecated path)
DEPRECATED alias of POST /v1/test/fund (ent#979), kept for two releases. One real difference: here the consumer comes in the route SEGMENT, and the body's consumer_id is ignored.
Credits the consumer's sandbox account directly, without going through a Pix. It is the shortcut for putting a balance on a test wallet before exercising a spend flow.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
account | string | no | The consumer's own sandbox account. Accepted only when it is the consumer's active pix-celcoin account in this project (otherwise 403 funding_account_not_owned); the default is that same account. |
amount_minor | integer | yes | BRL cents. |
consumer_id | string | no | Required on the /v1/test/* path, where the route has no consumer segment. Ignored on the /v1/consumers/{consumerId}/... alias, where the segment wins. |
idempotency_key | string | no | Retry key. In a project of the controlled or the restricted integrated proof, the same key answers the credit it already made (200, idempotent_replay: true) instead of crediting again. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
201 | object | OK |
400 | object | The body is outside the schema, or consumer_id is missing on the /v1/test/* path (where it is required, because the route has no consumer segment). |
403 | object | sandbox_funding_not_permitted: a LIVE environment key (these routes exist only in a test environment; details.environment carries what was read). funding_account_not_owned: the body names an account that is not the consumer's active Celcoin account in this project; only the consumer's own account is credited, and nothing was sent. |
422 | object | The consumer has no Celcoin account to credit, or the sandbox provider refused. sandbox_funding_refused: a bound account's credit was refused by a platform guard before it was sent (details.reason); nothing was sent. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
account | string | yes | — |
amount_minor | integer | yes | — |
attempt_id | string | yes | — |
currency | "BRL" | yes | — |
deposit_id | string | yes | — |
idempotent_replay | true | no | Present on a rerun answered from the credit already made. |
money_credited | true | yes | — |
status | string | yes | — |
curl -X POST https://api.codespar.dev/v1/consumers/{consumerId}/fund/sandbox \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"account": "string",
"idempotency_key": "string"
}'POST /v1/consumers/{consumerId}/fund/sandbox HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"account": "string",
"idempotency_key": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/consumers/{consumerId}/fund/sandbox",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"account": "string",
"idempotency_key": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/consumers/{consumerId}/fund/sandbox", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"consumer_id": "csm_0000000000000000",
"amount_minor": 1000,
"account": "string",
"idempotency_key": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/consumers/{consumerId}/fund/sandbox", {
path: {
consumerId: "csm_0000000000000000"
},
body: {
consumer_id: "csm_0000000000000000",
amount_minor: 1000,
account: "string",
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);
}{
"attempt_id": "attempt_0000000000000000",
"deposit_id": "deposit_0000000000000000",
"status": "string",
"account": "string",
"amount_minor": 1000,
"currency": "BRL",
"money_credited": true,
"idempotent_replay": true
}GET /v1/consumers/{consumerId}/fund/{txId}
https://api.codespar.dev/v1/consumers/{consumerId}/fund/{txId}Poll an onramp, and credit the wallet when it settles
THIS GET WRITES. It polls the ramp provider for the transaction's state and, when that state is a terminal success AND the delivered amount is positive, posts the credit to the consumer's wallet ledger. There is no separate confirm call: polling is the credit-on-settle path.
Safe to poll on a loop. The credit deduplicates on a deterministic external reference derived from the transaction id, so re-polling a settled transaction never double-credits. credited reports that this poll posted a credit, and it stays true on every later poll of the same settled transaction, because the same conditions still hold and the repeat post is a no-op. It is not a this call was the first flag.
Until the deposit is confirmed the status stays at the provider's pending value, the amount is not yet meaningful, and credited is false. A settled transaction that delivered a zero amount also stays credited: false, on purpose.
The provider connection is resolved by ORGANIZATION. A project match is only a tie-break preference when the organization holds more than one connection, never a filter, so this route is not scoped to the calling project the way the receipts routes are.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
consumerId | string | yes | — |
txId | string | yes | The ramp provider's transaction id, from the fund call. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | One code, and THREE distinct failures collapse into it. The route answers 404 exactly when the provider lookup yields nothing, and that lookup gives up in three separate places: 1. NOT CONNECTED. The organization has no ramp-provider account in connected state. A caller that never completed the provider connection sees this on every poll, for every transaction id. 2. CREDENTIAL GONE. The connection row exists and still reads as connected, but its stored credential no longer dereferences: revoked, rotated, or vaulted under a scope that no longer matches the row. Nothing else about the connection looks broken, which is what makes this one hard to recognize from the outside. 3. THE PROVIDER DID NOT RETURN IT. Any non-2xx from the provider lands here. That covers both this credential has no such transaction and the provider is down or refused, and the two are NOT distinguished: an outage is indistinguishable from an unknown id at this boundary. Only the third is worth retrying, and the response does not say which one happened. So read a 404 as this API cannot currently see the transaction, never as the transaction does not exist, and in particular never as grounds for starting a second onramp for the same deposit. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
amount_minor | integer | yes | USDC the wallet receives, in minor units. Only meaningful once the tx settles. |
credited | boolean | yes | True whenever this poll found the transaction in a terminal success state with a positive delivered amount, and therefore posted the ledger credit. It is true on EVERY such poll, not only the first: the credit deduplicates on a deterministic external reference derived from the transaction id, so the repeat post is a no-op. Read it as this transaction has settled and the credit exists, never as this call created the credit. |
currency | string | yes | — |
status | string | yes | The ramp provider's status, lower-cased and passed through (awaiting_deposit, processing, completed, and others it may add). Not a closed set. |
transaction_id | string | yes | Echoes the path parameter. |
curl -X GET https://api.codespar.dev/v1/consumers/{consumerId}/fund/{txId} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/consumers/{consumerId}/fund/{txId} 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}/fund/{txId}",
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}/fund/{txId}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/consumers/{consumerId}/fund/{txId}", {
path: {
consumerId: "csm_0000000000000000",
txId: "tx_0000000000000000"
}
});{
"transaction_id": "transaction_0000000000000000",
"status": "string",
"amount_minor": 1000,
"currency": "BRL",
"credited": true
}Receipts
3 operations under /v1/consumers (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Meters
9 operations under /v1/meters (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.