Skip to main content

Funding Sources

2 operations under /v1/funding-sources (GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.

5 min read
View MarkdownEdit on GitHub

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

Every operation below requires a Bearer token. See Authentication.

GET /v1/funding-sources

GEThttps://api.codespar.dev/v1/funding-sources

List consumer funding sources

The funding sources of this project's consumers.

This path does NOT fetch live balance, and that is not an omission. The alias GET /v1/consumers/funding-sources accepts ?with_balance=1 and, for the pix-celcoin rows, fetches the balance from the provider one by one. The canonical path leaves that out on purpose: a listing of every consumer with live balance is a fan-out of N provider calls per request. If you need a balance, ask for the balance of ONE consumer, and the suppression here fails CLOSED: any route this handler does not recognize as the alias does not get a balance.

unattributed_count counts the rows no consumer claims.

Query parameters

NameTypeRequiredDescription
consumer_idstringno—
limitintegerno1 to 200; the default is 50.

Responses

StatusBodyDescription
200objectOK
400objectThe query did not match the schema.

Response 200

FieldTypeRequiredDescription
funding_sourcesarray of objectyes—
unattributed_countintegeryes—
Example request
curl -X GET https://api.codespar.dev/v1/funding-sources \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/funding-sources HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/funding-sources",
    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/funding-sources", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/funding-sources");
Example response 200
application/json
{
  "funding_sources": [
    {
      "id": "obj_0000000000000000",
      "project_id": "prj_0000000000000000",
      "consumer_id": "csm_0000000000000000",
      "rail": "string",
      "currency": "BRL",
      "provider": "string",
      "display_label": "Example",
      "branch": "string",
      "status": "active",
      "pix_key": "string",
      "pix_key_type": "string",
      "created_at": "2026-01-15T12:00:00.000Z",
      "expires_at": "2026-01-15T12:00:00.000Z",
      "revoked_at": "2026-01-15T12:00:00.000Z",
      "metadata": {}
    }
  ],
  "unattributed_count": 1
}

GET /v1/funding-sources/{id}

GEThttps://api.codespar.dev/v1/funding-sources/{id}

Read one consumer funding source

One consumer rail: which provider holds it, which currency it authorizes, and whether it is still usable. Requires the consumers:fund scope.

RESOLUTION IS SCOPED TO THE ORGANIZATION AND THE PROJECT the credential authenticates for. A funding source held by a SIBLING project in the same organization answers 404 here, indistinguishable from one that never existed. Note that the sibling operations in this group do not all draw the line in the same place: the consent-record read below is scoped to the organization only.

NO LIVE BALANCE, and no query parameter asks for one. balance_minor is always null; the balance lives in the ledger.

No debit credential leaves. The row's provider token is an opaque vault pointer and is not in the response, and metadata is always {}: the account number and the holder's document stay on the row.

Path parameters

NameTypeRequiredDescription
idstringyesThe funding source id.

Responses

StatusBodyDescription
200objectOK
404objectNot Found. No funding source with this id in the caller's project. A row belonging to a sibling project, and a row the 0129 backfill left unattributed, both answer here.

Response 200

FieldTypeRequiredDescription
balance_minornullyesALWAYS null on this operation, and present so a caller does not have to tell an absent field from an unknown balance. Live balance left the canonical surface with D4: the balance lives in the ledger. This read is new enough to have no legacy alias that could be relying on the old behaviour, so it never calls the provider.
branchstring,nullyesThe account's branch (agência), lifted out of the row's metadata.branch when it holds a string, else null. Present on every row this group returns.
consumer_idstringyesThe consumer, in the caller's own namespace.
created_atstring (date-time)yes—
currency"BRL" | "USD" | "MXN" | "COP" | "ARS" | "USDC" | "BRLA"yesClosed by consumer_funding_sources_currency_check. That constraint NAME appears in exactly one file under packages/api/src/migrations/, so nothing later drops, relaxes or replaces it — the instrument is the name, not a list of the migrations that came after, because the list is what rots while the conclusion holds.
display_labelstring,nullyesCaller-supplied label, or null.
expires_atstring,null (date-time)yes—
idstringyescfs_- or fs_celcoin_-prefixed, depending on which writer created it.
metadataobjectyesAlways {}. The row's metadata holds the account number, the branch and, when the onboarding carried one, the holder's CPF or CNPJ; none of it is returned. What a caller uses is projected into fields of its own: branch, pix_key and pix_key_type (and account_masked on the listing). The key stays an object so a parser that requires it keeps working.
pix_keystring,nullyesLifted out of metadata.pixKey when that key holds a string, else null. It is the DICT entry registered for the account, not a credential.
pix_key_typestring,nullyesLifted out of metadata.pixKeyType, else null.
project_idstringyesAlways a string here, never null. Rows the 0129 backfill could not attribute carry project_id IS NULL, and the resolving predicate is a strict = against the credential's project, which no NULL satisfies — so an unattributed row answers 404 on this path rather than returning with a null.
providerstringyesThe provider behind the rail. Derived, not chosen: celcoin from the onboarding writer, and rail.split("-")[0] from the consent writer, so it inherits the same openness rail has.
railstringyesAn OPEN string, and deliberately not an enum here. consumer_funding_sources constrains it only with CHECK (length(rail) > 0), and the writers do not agree on a closed set: celcoin-onboarding.ts writes the literal pix-celcoin, routes/consents.ts writes one of its own five-value RAILS list on the single-rail path, and its multi-slot path writes methodToRail(slot.rail) — where the slot's rail is validated as z.string().min(1).max(40) and methodToRail returns its argument unchanged when no mapping matches. So a caller who opened a consent with an unmapped slot rail reads that same string back here. The values a reader should expect in practice: pix-celcoin, pix-consent, card-token, ted-debit-auth, usd-ach-debit, usdc-onchain. Match on those and tolerate anything else.
revoked_atstring,null (date-time)yesNon-null exactly when status is revoked; the table enforces the pair.
status"active" | "revoked" | "expired"yesClosed by consumer_funding_sources_status_check, whose name likewise appears in exactly one migration file. Read usability from status being active. A companion constraint keeps revoked_at and status = 'revoked' in step, so they cannot disagree.
Example request
curl -X GET https://api.codespar.dev/v1/funding-sources/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/funding-sources/{id} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

res = requests.get(
    "https://api.codespar.dev/v1/funding-sources/{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/funding-sources/{id}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/funding-sources/{id}", {
  path: {
    id: "fundingsource_0000000000000000"
  }
});
Example response 200
application/json
{
  "id": "obj_0000000000000000",
  "project_id": "prj_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "rail": "string",
  "currency": "BRL",
  "provider": "string",
  "display_label": "Example",
  "branch": "string",
  "status": "active",
  "pix_key": "string",
  "pix_key_type": "string",
  "created_at": "2026-01-15T12:00:00.000Z",
  "expires_at": "2026-01-15T12:00:00.000Z",
  "revoked_at": "2026-01-15T12:00:00.000Z",
  "metadata": {}
}
Funding Sources | CodeSpar