Skip to main content

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.

10 min read
View MarkdownEdit on GitHub

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

Every operation below requires a Bearer token. See Authentication.

POST /v1/consumers/{consumerId}/fund/pix-in

POSThttps://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in
Deprecated

Mint 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

NameTypeRequiredDescription
consumerIdstringyes—

Request body

FieldTypeRequiredDescription
amount_minorintegeryesBRL cents.
consumer_idstringnoRequired on the /v1/test/* path, where the route has no consumer segment. Ignored on the /v1/consumers/{consumerId}/... alias, where the segment wins.
descriptionstringno—

Responses

StatusBodyDescription
201objectOK
400objectThe 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).
403objectA 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.
422objectThe sandbox provider refused the mint.
429objectproject_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.
503objectproject_access_calls_unavailable: the project's provider call budget could not be read (Test bound account only). Nothing was sent; retry.

Response 201

FieldTypeRequiredDescription
amount_minorintegeryes—
currency"BRL"yes—
pix_copia_e_colastringyesThe code that gets paid, the way a real Pix is paid.
pix_keystringyes—
rail"pix-celcoin"yes—
transaction_idstringyesThe idempotency key of the settlement step.
Example request
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);
}
Example response 201
application/json
{
  "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

POSThttps://api.codespar.dev/v1/consumers/{consumerId}/fund/pix-in/settle
Moves moneyDeprecated

Settles 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

NameTypeRequiredDescription
consumerIdstringyes—

Request body

FieldTypeRequiredDescription
amount_minorintegeryes—
consumer_idstringnoRequired on the /v1/test/* path, where the route has no consumer segment. Ignored on the /v1/consumers/{consumerId}/... alias, where the segment wins.
transaction_idstringyesThe 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

StatusBodyDescription
200objectOK
201objectOK
400objectThe 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).
403objectA 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.
404objectcharge_not_found: no single charge minted for this consumer in this project carries this transaction_id (Test bound account only). Nothing was sent.
409objectTest 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.
422objectThe 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).
429objectproject_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.
500objectThe sandbox account was credited and the mirror in the wallet ledger failed: split state, and details.deposit_id is where to reconcile from.
503objectproject_access_calls_unavailable: the project's provider call budget could not be read (Test bound account only). Nothing was sent; retry.

Response 200

FieldTypeRequiredDescription
amount_minorintegeryes—
currency"BRL"yes—
deposit_idstring,nullyesThe provider's credit. Null only on a replay of a charge its webhook or the reconciler settled.
idempotent_replaybooleannoTrue when this answer is a replay: nothing was sent again.
money_creditedtrueyes—
settledtrueyes—
transaction_idstringyes—
Example request
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);
}
Example response 200
application/json
{
  "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

POSThttps://api.codespar.dev/v1/consumers/{consumerId}/fund/sandbox
Moves moneyDeprecated

Credits 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

NameTypeRequiredDescription
consumerIdstringyes—

Request body

FieldTypeRequiredDescription
accountstringnoThe 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_minorintegeryesBRL cents.
consumer_idstringnoRequired on the /v1/test/* path, where the route has no consumer segment. Ignored on the /v1/consumers/{consumerId}/... alias, where the segment wins.
idempotency_keystringnoRetry 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

StatusBodyDescription
200objectOK
201objectOK
400objectThe 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).
403objectsandbox_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.
422objectThe 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

FieldTypeRequiredDescription
accountstringyes—
amount_minorintegeryes—
attempt_idstringyes—
currency"BRL"yes—
deposit_idstringyes—
idempotent_replaytruenoPresent on a rerun answered from the credit already made.
money_creditedtrueyes—
statusstringyes—
Example request
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);
}
Example response 200
application/json
{
  "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}

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

NameTypeRequiredDescription
consumerIdstringyes—
txIdstringyesThe ramp provider's transaction id, from the fund call.

Responses

StatusBodyDescription
200objectOK
404objectOne 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

FieldTypeRequiredDescription
amount_minorintegeryesUSDC the wallet receives, in minor units. Only meaningful once the tx settles.
creditedbooleanyesTrue 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.
currencystringyes—
statusstringyesThe ramp provider's status, lower-cased and passed through (awaiting_deposit, processing, completed, and others it may add). Not a closed set.
transaction_idstringyesEchoes the path parameter.
Example request
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_KEY
import 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"
  }
});
Example response 200
application/json
{
  "transaction_id": "transaction_0000000000000000",
  "status": "string",
  "amount_minor": 1000,
  "currency": "BRL",
  "credited": true
}
Consumer funding | CodeSpar