Skip to main content

Bank consents (legacy /ofb path)

5 operations under /v1/ofb/consents (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.

15 min read
View MarkdownEdit on GitHub

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

Every operation below requires a Bearer token. See Authentication.

POST /v1/ofb/consents

POSThttps://api.codespar.dev/v1/ofb/consents
Deprecated

Initiate a bank consent (legacy path)

DEPRECATED alias of POST /v1/bank-consents (ent#979), kept for two releases. Same handler, same scope; switch the path and nothing else changes.

Everything the canonical path says still holds here, including the two that cost money to learn late: the BANK IS CALLED BEFORE THE ROW IS WRITTEN, so a refusal after that point leaves a bank-side consent with nothing of ours pointing at it; and one open consent per consumer per bank per organization, which a sibling project can be holding.

Request body

FieldTypeRequiredDescription
bank_idstringyes—
consumer_idstringyes—
redirect_urlstring (uri)yesWhere the bank returns the consumer after authorisation. You compose it and you host it.
scopesarray of stringyes—
wallet_idstringnoBinds the consent to a wallet, so the statements it ingests carry that wallet.

Responses

StatusBodyDescription
201objectCreated. The consent is pending and authorisation_url is set.
400objectThe body did not match the schema. details.issues carries the Zod issues.
409objectbank_consent_held_elsewhere when another project in this organization holds an open (pending or authorised) consent for this consumer at this bank. details.remediation says what to do and details.retriable is false: one project's bank grant is never shared with another, so re-sending this request unchanged will not clear it. consent_active_for_consumer when YOUR OWN project already holds one that is still inside its validity window. Revoke it first. One past its window does not refuse: it is moved to expired as the new consent is created. db_error when the consent could not be persisted for any other reason. The cause is in our logs under the request id and is deliberately not in this body, because the driver's own message names another project's key values.
422objectofb_not_live: this is a LIVE project and the deployment has no real Open Finance adapter, only the stub, which serves test projects. TERMINAL for this project: use a test project. Nothing was written and no bank was contacted.
500objectconsent_disappeared: the row was written and could not be read back. Do not retry blind; both the row and the bank-side consent may exist.
502objectThe bank refused the request or could not be reached, before anything was written. The message is the adapter's own.
503objectconsent_lock_timeout: another request is issuing a consent for this consumer at this bank and the wait ended without an answer, so NOTHING WAS DECIDED. We do not know whether a sibling holds the grant, and saying so is why this is not the 409. details.retriable is true. No consent row was created; a bank-side consent was, and is orphaned. ofb_adapter_unavailable: the Open Finance adapter this deployment selects cannot serve (details.reason: missing_credentials, not_implemented or unknown_mode). Ours to fix, not yours; nothing was written and no bank was contacted.

Response 201

FieldTypeRequiredDescription
authorisation_urlstring,nullyesWhere the consumer authorises the consent. This is the URL to send them to.
authorized_atstring,null (date-time)yesSpelled with a z, unlike authorised in status. Both spellings are column names, not typos.
bank_consent_idstring,nullyesThe bank's own identifier for the consent. Null until the bank issues one.
bank_idstringyesLowercase kebab-case, for example itau or bb.
consumed_atstring,null (date-time)yes—
consumer_idstringyes—
created_atstring (date-time)yes—
expires_atstring,null (date-time)yesMirrors the bank-side consent expiry, as the bank reported it at initiation.
idstringyes—
metadataobjectyes—
revoked_atstring,null (date-time)yes—
scopesarray of stringyes—
status"pending" | "authorised" | "revoked" | "expired" | "consumed"yes—
token_expires_atstring,null (date-time)yes—
wallet_idstring,nullyesSet when the consent was bound to a wallet at initiation; null when it was not.
Example request
curl -X POST https://api.codespar.dev/v1/ofb/consents \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "bank_id": "bank_0000000000000000",
       "consumer_id": "csm_0000000000000000",
       "scopes": [
         "string"
       ],
       "redirect_url": "https://example.com/hook",
       "wallet_id": "wlt_0000000000000000"
     }'
POST /v1/ofb/consents HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "bank_id": "bank_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "scopes": [
    "string"
  ],
  "redirect_url": "https://example.com/hook",
  "wallet_id": "wlt_0000000000000000"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/ofb/consents",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "bank_id": "bank_0000000000000000",
      "consumer_id": "csm_0000000000000000",
      "scopes": [
        "string"
      ],
      "redirect_url": "https://example.com/hook",
      "wallet_id": "wlt_0000000000000000"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/ofb/consents", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "bank_id": "bank_0000000000000000",
    "consumer_id": "csm_0000000000000000",
    "scopes": [
      "string"
    ],
    "redirect_url": "https://example.com/hook",
    "wallet_id": "wlt_0000000000000000"
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/ofb/consents", {
  body: {
    bank_id: "bank_0000000000000000",
    consumer_id: "csm_0000000000000000",
    scopes: [
      "string"
    ],
    redirect_url: "https://example.com/hook",
    wallet_id: "wlt_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);
}
Example response 201
application/json
{
  "id": "obj_0000000000000000",
  "bank_id": "bank_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "wallet_id": "wlt_0000000000000000",
  "status": "pending",
  "scopes": [
    "string"
  ],
  "bank_consent_id": "bankconsent_0000000000000000",
  "authorisation_url": "https://example.com/hook",
  "token_expires_at": "2026-01-15T12:00:00.000Z",
  "created_at": "2026-01-15T12:00:00.000Z",
  "authorized_at": "2026-01-15T12:00:00.000Z",
  "expires_at": "2026-01-15T12:00:00.000Z",
  "revoked_at": "2026-01-15T12:00:00.000Z",
  "consumed_at": "2026-01-15T12:00:00.000Z",
  "metadata": {}
}

GET /v1/ofb/consents/{id}

GEThttps://api.codespar.dev/v1/ofb/consents/{id}
Deprecated

Read one bank consent

DEPRECATED alias of GET /v1/bank-consents/{id} (ent#979), kept for two releases. Same handler, same required scope; switch the path and nothing else changes.

The body is the consent projection: the same shape POST /v1/bank-consents returns on create, and the same one the callback and revoke transitions return. The refresh-statement operation in this family is the exception, answering with a run summary rather than a consent.

Resolution is scoped to the organization AND the project the credential authenticates for. A consent held by a SIBLING project in the same organization answers 404 here, indistinguishable from one that never existed. That is deliberate rather than incidental: one project's upstream bank grant is never readable from another.

No token material leaves. The row carries references to the bank's access and refresh tokens; they are dropped before the body is built, and token_expires_at is the only thing about the bank token that is returned.

Requires the consents:read scope.

Path parameters

NameTypeRequiredDescription
idstringyesofb_-prefixed consent id.

Responses

StatusBodyDescription
200objectOK
404objectNot Found. No consent with this id in the caller's project. A consent belonging to another project in the same organization answers here too.

Response 200

FieldTypeRequiredDescription
authorisation_urlstring,nullyesThe URL the consumer opens to authorise the consent at their bank.
authorized_atstring,null (date-time)yesAmerican spelling here, unlike status: "authorised"; both mirror their own column names. Stamped in the same statement as the transition into authorised.
bank_consent_idstring,nullyesThe bank's own identifier for the consent, as returned when it was opened.
bank_idstringyesLowercase kebab-case, as supplied when the consent was opened: itau, bradesco, bb, inter.
consumed_atstring,null (date-time)yes—
consumer_idstringyesThe consumer, in the caller's own namespace.
created_atstring (date-time)yes—
expires_atstring,null (date-time)yesMirror of the bank-side consent expiry, stamped when the consent is opened.
idstringyesofb_-prefixed. The id every other operation in this family takes.
metadataobjectyes—
revoked_atstring,null (date-time)yes—
scopesarray of stringyesThe OFB scopes the consent was opened for.
status"pending" | "authorised" | "revoked" | "expired" | "consumed"yesauthorised carries the British spelling, which is the column's own and not a typo. revoked, expired and consumed are terminal: no transition leaves them. Read "is this consent usable now" from status being authorised, never from revoked_at being null.
token_expires_atstring,null (date-time)yesExpiry of the bank access token. The token references themselves are never returned by this API.
wallet_idstring,nullyesThe wallet named on the create request, or null. Written once at creation and never rewritten: neither state transition on this consent touches the column. It is a label, not routing. A statement refresh copies this value onto every event it ingests, and nothing downstream reads it: reconciliation resolves a wallet from the funding source behind an event's connection_id, and the events this family publishes carry no such key. Binding a consent to a wallet therefore does not, on its own, put the ingested statement into that wallet's reconciliation.
Example request
curl -X GET https://api.codespar.dev/v1/ofb/consents/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
GET /v1/ofb/consents/{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/ofb/consents/{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/ofb/consents/{id}", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
  },
});

const data = await res.json();
const result = await cs.api.get("/v1/ofb/consents/{id}", {
  path: {
    id: "consent_0000000000000000"
  }
});
Example response 200
application/json
{
  "id": "obj_0000000000000000",
  "bank_id": "bank_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "wallet_id": "wlt_0000000000000000",
  "status": "pending",
  "scopes": [
    "string"
  ],
  "bank_consent_id": "bankconsent_0000000000000000",
  "authorisation_url": "https://example.com/hook",
  "token_expires_at": "2026-01-15T12:00:00.000Z",
  "created_at": "2026-01-15T12:00:00.000Z",
  "authorized_at": "2026-01-15T12:00:00.000Z",
  "expires_at": "2026-01-15T12:00:00.000Z",
  "revoked_at": "2026-01-15T12:00:00.000Z",
  "consumed_at": "2026-01-15T12:00:00.000Z",
  "metadata": {}
}

POST /v1/ofb/consents/{id}/callback

POSThttps://api.codespar.dev/v1/ofb/consents/{id}/callback
Deprecated

Finalise a consent after the consumer authorises it (legacy path)

DEPRECATED alias of POST /v1/bank-consents/{id}/callback (ent#979), kept for two releases. Same handler, same scope. Only a pending consent can be finalised, so a replayed callback answers 409 rather than minting a second token pair.

Path parameters

NameTypeRequiredDescription
idstringyes—

Request body

FieldTypeRequiredDescription
auth_codestringyesThe code the bank put on the redirect.

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match the schema. details.issues carries the Zod issues.
404objectNo such consent for this credential's organization AND project. A consent created by a sibling project in the same organization answers the same way: the resolver requires both predicates on purpose, because resolving on organization alone let one project read, transition and refresh another project's consent.
409objectillegal_transition: the consent's current status does not permit authorisation. details.from and details.to carry the pair. consent_expired: the consent is pending and its validity window (details.expires_at) has elapsed. TERMINAL: the grant can no longer be authorised. The consent is moved to expired by this same call, which frees this project's one open consent for the consumer at this bank: initiate a new consent. details.status is the consent's status after the call, which is not expired when a concurrent transition won; if it is still pending, revoke the consent before initiating another. Repeating the callback answers consent_expired again. An expired consent cannot be revoked (illegal_transition).
422objectofb_not_live: this is a LIVE project and the deployment has no real Open Finance adapter, only the stub, which serves test projects. TERMINAL for this project: use a test project. Nothing was written and no bank was contacted.
500objectmissing_bank_consent_id: the stored consent carries no bank-side id, so there is nothing to finalise. Ours to fix, not yours; quote the request id.
502objectThe bank refused the code or could not be reached. What this handler wrote is nothing: the UPDATE runs only after the exchange returns, so the consent is still pending and no token pointer was stored. Whether the code itself is still usable is the BANK's state and not ours to report, so this answer does not claim either way. The message is the adapter's own, and empty when what the adapter threw was not an Error.
503objectofb_adapter_unavailable: the Open Finance adapter this deployment selects cannot serve (details.reason: missing_credentials, not_implemented or unknown_mode). Ours to fix, not yours; nothing was written and no bank was contacted.

Response 200

FieldTypeRequiredDescription
authorisation_urlstring,nullyesWhere the consumer authorises the consent. This is the URL to send them to.
authorized_atstring,null (date-time)yesSpelled with a z, unlike authorised in status. Both spellings are column names, not typos.
bank_consent_idstring,nullyesThe bank's own identifier for the consent. Null until the bank issues one.
bank_idstringyesLowercase kebab-case, for example itau or bb.
consumed_atstring,null (date-time)yes—
consumer_idstringyes—
created_atstring (date-time)yes—
expires_atstring,null (date-time)yesMirrors the bank-side consent expiry, as the bank reported it at initiation.
idstringyes—
metadataobjectyes—
revoked_atstring,null (date-time)yes—
scopesarray of stringyes—
status"pending" | "authorised" | "revoked" | "expired" | "consumed"yes—
token_expires_atstring,null (date-time)yes—
wallet_idstring,nullyesSet when the consent was bound to a wallet at initiation; null when it was not.
Example request
curl -X POST https://api.codespar.dev/v1/ofb/consents/{id}/callback \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "auth_code": "string"
     }'
POST /v1/ofb/consents/{id}/callback HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "auth_code": "string"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/ofb/consents/{id}/callback",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "auth_code": "string"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/ofb/consents/{id}/callback", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "auth_code": "string"
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/ofb/consents/{id}/callback", {
  path: {
    id: "consent_0000000000000000"
  },
  body: {
    auth_code: "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);
}
Example response 200
application/json
{
  "id": "obj_0000000000000000",
  "bank_id": "bank_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "wallet_id": "wlt_0000000000000000",
  "status": "pending",
  "scopes": [
    "string"
  ],
  "bank_consent_id": "bankconsent_0000000000000000",
  "authorisation_url": "https://example.com/hook",
  "token_expires_at": "2026-01-15T12:00:00.000Z",
  "created_at": "2026-01-15T12:00:00.000Z",
  "authorized_at": "2026-01-15T12:00:00.000Z",
  "expires_at": "2026-01-15T12:00:00.000Z",
  "revoked_at": "2026-01-15T12:00:00.000Z",
  "consumed_at": "2026-01-15T12:00:00.000Z",
  "metadata": {}
}

POST /v1/ofb/consents/{id}/refresh-statement

POSThttps://api.codespar.dev/v1/ofb/consents/{id}/refresh-statement
Deprecated

Fetch the bank statement and ingest it (legacy path)

DEPRECATED alias of POST /v1/bank-consents/{id}/refresh-statement (ent#979), kept for two releases. Same handler, same scope. Still safe to call twice — the dedup key puts a repeat in duplicates rather than crediting it again — and still 202, because the ingest is synchronous and the reconciliation is not.

Path parameters

NameTypeRequiredDescription
idstringyes—

Request body

FieldTypeRequiredDescription
sincestring (date-time)no—
untilstring (date-time)no—

Responses

StatusBodyDescription
202objectAccepted. The statement was fetched and ingested; reconciliation happens after.
400objectThe body did not match the schema: since and until must be RFC 3339 timestamps carrying an offset. details.issues carries the Zod issues.
404objectNo such consent for this credential's organization AND project. A consent created by a sibling project in the same organization answers the same way: the resolver requires both predicates on purpose, because resolving on organization alone let one project read, transition and refresh another project's consent.
409objectconsent_not_authorised: the consent is not in authorised. details.status carries what it is. consent_expired: the consent's validity window (details.expires_at) has elapsed. The consent is moved to expired by this same call and nothing is fetched. TERMINAL: initiate a new consent. consent_token_expired: the consent is still valid but its access token (details.token_expires_at) has expired. NOT terminal: the consent stays authorised, because the grant is alive at the bank and the token is renewable. Nothing was fetched; do not initiate a new consent.
422objectofb_not_live: this is a LIVE project and the deployment has no real Open Finance adapter, only the stub, which serves test projects. TERMINAL for this project: use a test project. Nothing was written and no bank was contacted.
500objectconsent_missing_tokens: the consent says authorised but is missing something the fetch needs. TWO conditions raise it and the code does not distinguish them: no stored access-token pointer, OR no bank-side consent id. The second is not a token, so read the code as this authorised consent is not usable rather than as a statement about credentials. Ours to fix, not yours; quote the request id.
502objectThe bank refused or could not be reached. Partial progress is KEPT: each entry is committed as it is ingested, so entries taken before the failure stay ingested and a retry re-imports only what is missing. The message is the adapter's own unless the failure came from our own database, in which case it is replaced by a fixed sentence rather than naming our tables.
503objectofb_adapter_unavailable: the Open Finance adapter this deployment selects cannot serve (details.reason: missing_credentials, not_implemented or unknown_mode). Ours to fix, not yours; nothing was written and no bank was contacted.

Response 202

FieldTypeRequiredDescription
consent_idstringyes—
duplicatesintegeryesEntries a previous call had already ingested, recognised by the dedup key and not credited a second time.
importedintegeryesEntries ingested as new events by this call.
next_recon_cycle_within_secondsintegeryesA constant hint about how often reconciliation runs. Not a queue estimate and not a promise about this run.
run_idstringyesIdentifies this refresh in the consent's event log, on the started, completed and failed events alike. Quote it in support.
Example request
curl -X POST https://api.codespar.dev/v1/ofb/consents/{id}/refresh-statement \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "since": "2026-01-15T12:00:00.000Z",
       "until": "2026-01-15T12:00:00.000Z"
     }'
POST /v1/ofb/consents/{id}/refresh-statement HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "since": "2026-01-15T12:00:00.000Z",
  "until": "2026-01-15T12:00:00.000Z"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/ofb/consents/{id}/refresh-statement",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "since": "2026-01-15T12:00:00.000Z",
      "until": "2026-01-15T12:00:00.000Z"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/ofb/consents/{id}/refresh-statement", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "since": "2026-01-15T12:00:00.000Z",
    "until": "2026-01-15T12:00:00.000Z"
  }),
});

const data = await res.json();
const r = await cs.api.response("post", "/v1/ofb/consents/{id}/refresh-statement", {
  path: {
    id: "consent_0000000000000000"
  },
  body: {
    since: "2026-01-15T12:00:00.000Z",
    until: "2026-01-15T12:00:00.000Z"
  }
});
// 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);
}
Example response 202
application/json
{
  "consent_id": "consent_0000000000000000",
  "run_id": "run_0000000000000000",
  "imported": 0,
  "duplicates": 0,
  "next_recon_cycle_within_seconds": 0
}

POST /v1/ofb/consents/{id}/revoke

POSThttps://api.codespar.dev/v1/ofb/consents/{id}/revoke
Deprecated

Revoke a bank consent (legacy path)

DEPRECATED alias of POST /v1/bank-consents/{id}/revoke (ent#979), kept for two releases. Same handler, same scope. It still STOPS US AND NOT THE BANK: no adapter is called, so a grant that must also end at the bank has to be revoked in the bank's own channel.

Path parameters

NameTypeRequiredDescription
idstringyes—

Request body

FieldTypeRequiredDescription
reasonstringnoRecorded on the revoked event.

Responses

StatusBodyDescription
200objectOK
400objectThe body did not match the schema. details.issues carries the Zod issues.
404objectNo such consent for this credential's organization AND project. A consent created by a sibling project in the same organization answers the same way: the resolver requires both predicates on purpose, because resolving on organization alone let one project read, transition and refresh another project's consent.
409objectThe consent's current status does not permit this transition. details.from and details.to carry the pair. pending may become authorised, revoked or expired; authorised may become revoked, expired or consumed; revoked, expired and consumed are terminal and permit nothing.

Response 200

FieldTypeRequiredDescription
authorisation_urlstring,nullyesWhere the consumer authorises the consent. This is the URL to send them to.
authorized_atstring,null (date-time)yesSpelled with a z, unlike authorised in status. Both spellings are column names, not typos.
bank_consent_idstring,nullyesThe bank's own identifier for the consent. Null until the bank issues one.
bank_idstringyesLowercase kebab-case, for example itau or bb.
consumed_atstring,null (date-time)yes—
consumer_idstringyes—
created_atstring (date-time)yes—
expires_atstring,null (date-time)yesMirrors the bank-side consent expiry, as the bank reported it at initiation.
idstringyes—
metadataobjectyes—
revoked_atstring,null (date-time)yes—
scopesarray of stringyes—
status"pending" | "authorised" | "revoked" | "expired" | "consumed"yes—
token_expires_atstring,null (date-time)yes—
wallet_idstring,nullyesSet when the consent was bound to a wallet at initiation; null when it was not.
Example request
curl -X POST https://api.codespar.dev/v1/ofb/consents/{id}/revoke \
  -H "Authorization: Bearer $CODESPAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
       "reason": "string"
     }'
POST /v1/ofb/consents/{id}/revoke HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json

{
  "reason": "string"
}
import os
import requests

res = requests.post(
    "https://api.codespar.dev/v1/ofb/consents/{id}/revoke",
    headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
    json={
      "reason": "string"
    },
)
res.raise_for_status()
data = res.json()
const res = await fetch("https://api.codespar.dev/v1/ofb/consents/{id}/revoke", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "reason": "string"
  }),
});

const data = await res.json();
const result = await cs.api.post("/v1/ofb/consents/{id}/revoke", {
  path: {
    id: "consent_0000000000000000"
  },
  body: {
    reason: "string"
  }
});
Example response 200
application/json
{
  "id": "obj_0000000000000000",
  "bank_id": "bank_0000000000000000",
  "consumer_id": "csm_0000000000000000",
  "wallet_id": "wlt_0000000000000000",
  "status": "pending",
  "scopes": [
    "string"
  ],
  "bank_consent_id": "bankconsent_0000000000000000",
  "authorisation_url": "https://example.com/hook",
  "token_expires_at": "2026-01-15T12:00:00.000Z",
  "created_at": "2026-01-15T12:00:00.000Z",
  "authorized_at": "2026-01-15T12:00:00.000Z",
  "expires_at": "2026-01-15T12:00:00.000Z",
  "revoked_at": "2026-01-15T12:00:00.000Z",
  "consumed_at": "2026-01-15T12:00:00.000Z",
  "metadata": {}
}
Bank consents (legacy /ofb path) | CodeSpar