Skip to main content

Consent Records

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

4 min read
View MarkdownEdit on GitHub

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

Every operation below requires a Bearer token. See Authentication.

GET /v1/consent-records

GEThttps://api.codespar.dev/v1/consent-records

List consent evidence

The organization's consent evidence, most recent first. It is append-only: nothing here is edited after it is signed.

The response is { consents: [...] }.

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
consentsarray of objectyes—
Example request
curl -X GET https://api.codespar.dev/v1/consent-records \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/consent-records HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
import os
import requests

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

const data = await res.json();
const result = await cs.api.get("/v1/consent-records");
Example response 200
application/json
{
  "consents": [
    {
      "id": "obj_0000000000000000",
      "consumer_id": "csm_0000000000000000",
      "funding_source_id": "fundingsource_0000000000000000",
      "agent_id": "agt_0000000000000000",
      "kind": "grant",
      "hmac_secret_version": 0,
      "cap_minor": "1000",
      "per_tx_cap_minor": "1000",
      "currency": "BRL",
      "purpose": "string",
      "ip_address": "string",
      "signed_at": "2026-01-15T12:00:00.000Z",
      "expires_at": "2026-01-15T12:00:00.000Z",
      "metadata": {}
    }
  ]
}

GET /v1/consent-records/{id}

GEThttps://api.codespar.dev/v1/consent-records/{id}

Read one consent record

One row of the consent log: the moment a consumer authorized a specific agent to debit a specific funding source up to a specific cap. Requires the consents:read scope.

APPEND-ONLY EVIDENCE. consumer_consents is the LGPD audit log — rows are never rewritten and never deleted, and a revocation is a new row with kind = 'revoke'. That is what lets a revocation be replayed from the log alone, and it is why this read returns exactly what the listing returns: there is no derived state for a point read to add.

RESOLUTION IS SCOPED TO THE ORGANIZATION ONLY — the query predicates on org_id and the id, with no project term. A consent record created under a sibling project in the same organization IS readable with this credential. The funding-source read in this same group is narrower, so do not carry an assumption across the two.

NO SIGNATURE MATERIAL LEAVES. The row carries payload_hash and hmac_signature; both are dropped before the body is built. What crosses the wire is hmac_secret_version — the stamp that says which secret signed it — and its note explains why that stamp is what makes secret rotation safe rather than destructive.

PERSONAL DATA, TRUNCATED. ip_address is the network of the consumer's address (/24 for IPv4, /64 for IPv6); the full address stays on the row, recorded because a consent event has to be replayable with its origin.

Path parameters

NameTypeRequiredDescription
idstringyesThe consent record id.

Responses

StatusBodyDescription
200objectOK
404objectNot Found. No consent record with this id in the caller's organization.

Response 200

FieldTypeRequiredDescription
agent_idstring,nullyesThe agent the consumer authorized, when the row names one.
cap_minorstring,nullyesA STRING, not a number. The column is bigint and the driver hands bigints back as strings; the handler passes the value straight through. Parse it as an integer in the row's currency, and do not assume it survives a float. Null on revoke / amend rows.
consumer_idstringyes—
currency"BRL" | "USD" | "MXN" | "COP" | "ARS" | "USDC" | "BRLA"yesClosed by consumer_consents_currency_check (name in exactly one migration file), which admits NULL as well as the seven values. Non-null on grant rows, which the completeness constraint requires to declare one.
expires_atstring,null (date-time)yes—
funding_source_idstring,nullyesThe consumer_funding_sources row this consent authorizes debits against. Nullable on the column, and required by consumer_consents_grant_completeness on kind = 'grant' rows: a grant must declare what it authorizes, while revoke and amend reference the original grant.
hmac_secret_versionintegeryesWhich version of the consumer's HMAC secret signed this row. This is the field that makes rotation safe, and the half that is usually left out when the rule is quoted. Rotating a consumer's secret invalidates FUTURE mandates only, and the reason is here rather than in the rotation: the verifier resolves the secret by (consumer, version) and the mandate itself carries the version it was signed under, so a mandate signed under the prior version still resolves the prior secret and still verifies. Rotation marks the old row and inserts version+1; it deletes nothing. Without the version stamp the sentence would mean the opposite — that rotating breaks the audit trail.
idstringyesccsnt_-prefixed on rows the mandate-lifecycle writer creates.
ip_addressstring,nullyesThe network of the consumer's IP at signing time: an IPv4 address truncated to its /24 (203.0.113.0), an IPv6 address to its /64 (2001:db8:85a3:8d3::), null when none was recorded or the stored value does not parse. The inet column keeps the full address, because a consent event has to be replayable with timestamp and origin; only the response is truncated. user_agent is recorded alongside it and is NOT returned.
kind"grant" | "revoke" | "amend"yesClosed by consumer_consents_kind_check, whose name appears in exactly one migration file. The log is append-only: a revocation is a NEW row with kind = 'revoke', never a rewrite of the grant, which is what makes a revocation replayable from the log alone.
metadataobjectyesDefaults to {}. Rows written by the mandate-lifecycle handler carry the transition instead: action, from, to, and reason when the caller gave one.
per_tx_cap_minorstring,nullyesSame bigint-as-string treatment as cap_minor.
purposestring,nullyesThe purpose the consumer signed. Non-null on grant rows.
signed_atstring (date-time)yesWhen the consent event happened. The listing orders by this.
Example request
curl -X GET https://api.codespar.dev/v1/consent-records/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/consent-records/{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/consent-records/{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/consent-records/{id}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/consent-records/{id}", {
  path: {
    id: "consentrecord_0000000000000000"
  }
});
Example response 200
application/json
{
  "id": "obj_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "funding_source_id": "fundingsource_0000000000000000",
  "agent_id": "agt_0000000000000000",
  "kind": "grant",
  "hmac_secret_version": 0,
  "cap_minor": "1000",
  "per_tx_cap_minor": "1000",
  "currency": "BRL",
  "purpose": "string",
  "ip_address": "string",
  "signed_at": "2026-01-15T12:00:00.000Z",
  "expires_at": "2026-01-15T12:00:00.000Z",
  "metadata": {}
}
Consent Records | CodeSpar