OFB
Generated HTTP reference for the 5 operations the published OpenAPI document describes under ofb.
OFB
This page is generated from the published OpenAPI document. It is complete with respect to that document and says nothing about surfaces the document does not describe yet. See what is generated here for what that means.
Base URL: https://api.codespar.dev
Every operation below requires a Bearer token. See Authentication.
POST /v1/ofb/consents
Initiate a bank consent
Request body
| Field | Type | Required | Description |
|---|---|---|---|
bank_id | string | yes | — |
consumer_id | string | yes | — |
redirect_url | string (uri) | yes | Where the bank returns the consumer after authorisation. You compose it and you host it. |
scopes | array of string | yes | — |
wallet_id | string | no | Binds the consent to a wallet, so the statements it ingests carry that wallet. |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | Created. The consent is pending and authorisation_url is set. |
400 | object | The body did not match the schema. details.issues carries the Zod issues. |
409 | object | bank_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. Revoke it first. 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. |
500 | object | consent_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. |
502 | object | The bank refused the request or could not be reached, before anything was written. The message is the adapter's own. |
503 | object | consent_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. |
Response 201
| Field | Type | Required | Description |
|---|---|---|---|
authorisation_url | string,null | yes | Where the consumer authorises the consent. This is the URL to send them to. |
authorized_at | string,null (date-time) | yes | Spelled with a z, unlike authorised in status. Both spellings are column names, not typos. |
bank_consent_id | string,null | yes | The bank's own identifier for the consent. Null until the bank issues one. |
bank_id | string | yes | Lowercase kebab-case, for example itau or bb. |
consumed_at | string,null (date-time) | yes | — |
consumer_id | string | yes | — |
created_at | string (date-time) | yes | — |
expires_at | string,null (date-time) | yes | Mirrors the bank-side consent expiry, as the bank reported it at initiation. |
id | string | yes | — |
metadata | object | yes | — |
revoked_at | string,null (date-time) | yes | — |
scopes | array of string | yes | — |
status | "pending" | "authorised" | "revoked" | "expired" | "consumed" | yes | — |
token_expires_at | string,null (date-time) | yes | — |
wallet_id | string,null | yes | Set when the consent was bound to a wallet at initiation; null when it was not. |
Example response
{
"id": "obj_0000000000000000",
"bank_id": "bank_0000000000000000",
"consumer_id": "csm_0000000000000000",
"status": "pending",
"scopes": [
"string"
],
"created_at": "2026-01-15T12:00:00.000Z",
"metadata": {}
}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"
}'GET /v1/ofb/consents/{id}
Read one bank consent
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | ofb_-prefixed consent id. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not 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
| Field | Type | Required | Description |
|---|---|---|---|
authorisation_url | string,null | yes | The URL the consumer opens to authorise the consent at their bank. |
authorized_at | string,null (date-time) | yes | American spelling here, unlike status: "authorised"; both mirror their own column names. Stamped in the same statement as the transition into authorised. |
bank_consent_id | string,null | yes | The bank's own identifier for the consent, as returned when it was opened. |
bank_id | string | yes | Lowercase kebab-case, as supplied when the consent was opened: itau, bradesco, bb, inter. |
consumed_at | string,null (date-time) | yes | — |
consumer_id | string | yes | The consumer, in the caller's own namespace. |
created_at | string (date-time) | yes | — |
expires_at | string,null (date-time) | yes | Mirror of the bank-side consent expiry, stamped when the consent is opened. |
id | string | yes | ofb_-prefixed. The id every other operation in this family takes. |
metadata | object | yes | — |
revoked_at | string,null (date-time) | yes | — |
scopes | array of string | yes | The OFB scopes the consent was opened for. |
status | "pending" | "authorised" | "revoked" | "expired" | "consumed" | yes | authorised 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_at | string,null (date-time) | yes | Expiry of the bank access token. The token references themselves are never returned by this API. |
wallet_id | string,null | yes | The 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 response
{
"id": "obj_0000000000000000",
"bank_id": "bank_0000000000000000",
"consumer_id": "csm_0000000000000000",
"status": "pending",
"scopes": [
"string"
],
"created_at": "2026-01-15T12:00:00.000Z",
"metadata": {}
}Example request
curl -X GET https://api.codespar.dev/v1/ofb/consents/{id} \
-H "Authorization: Bearer $CODESPAR_API_KEY"POST /v1/ofb/consents/{id}/callback
Finalise a consent after the consumer authorises it
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
auth_code | string | yes | The code the bank put on the redirect. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The body did not match the schema. details.issues carries the Zod issues. |
404 | object | No 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. |
409 | object | The 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. |
500 | object | missing_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. |
502 | object | The 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. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
authorisation_url | string,null | yes | Where the consumer authorises the consent. This is the URL to send them to. |
authorized_at | string,null (date-time) | yes | Spelled with a z, unlike authorised in status. Both spellings are column names, not typos. |
bank_consent_id | string,null | yes | The bank's own identifier for the consent. Null until the bank issues one. |
bank_id | string | yes | Lowercase kebab-case, for example itau or bb. |
consumed_at | string,null (date-time) | yes | — |
consumer_id | string | yes | — |
created_at | string (date-time) | yes | — |
expires_at | string,null (date-time) | yes | Mirrors the bank-side consent expiry, as the bank reported it at initiation. |
id | string | yes | — |
metadata | object | yes | — |
revoked_at | string,null (date-time) | yes | — |
scopes | array of string | yes | — |
status | "pending" | "authorised" | "revoked" | "expired" | "consumed" | yes | — |
token_expires_at | string,null (date-time) | yes | — |
wallet_id | string,null | yes | Set when the consent was bound to a wallet at initiation; null when it was not. |
Example response
{
"id": "obj_0000000000000000",
"bank_id": "bank_0000000000000000",
"consumer_id": "csm_0000000000000000",
"status": "pending",
"scopes": [
"string"
],
"created_at": "2026-01-15T12:00:00.000Z",
"metadata": {}
}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}/refresh-statement
Fetch the bank statement and ingest it
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
since | string (date-time) | no | — |
until | string (date-time) | no | — |
Responses
| Status | Body | Description |
|---|---|---|
202 | object | Accepted. The statement was fetched and ingested; reconciliation happens after. |
400 | object | The body did not match the schema: since and until must be RFC 3339 timestamps carrying an offset. details.issues carries the Zod issues. |
404 | object | No 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. |
409 | object | consent_not_authorised: the consent is not in authorised. details.status carries what it is. |
500 | object | consent_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. |
502 | object | The 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. |
Response 202
| Field | Type | Required | Description |
|---|---|---|---|
consent_id | string | yes | — |
duplicates | integer | yes | Entries a previous call had already ingested, recognised by the dedup key and not credited a second time. |
imported | integer | yes | Entries ingested as new events by this call. |
next_recon_cycle_within_seconds | integer | yes | A constant hint about how often reconciliation runs. Not a queue estimate and not a promise about this run. |
run_id | string | yes | Identifies this refresh in the consent's event log, on the started, completed and failed events alike. Quote it in support. |
Example response
{
"consent_id": "consent_0000000000000000",
"run_id": "run_0000000000000000",
"imported": 0,
"duplicates": 0,
"next_recon_cycle_within_seconds": 0
}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}/revoke
Revoke a bank consent
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | Recorded on the revoked event. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The body did not match the schema. details.issues carries the Zod issues. |
404 | object | No 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. |
409 | object | The 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
| Field | Type | Required | Description |
|---|---|---|---|
authorisation_url | string,null | yes | Where the consumer authorises the consent. This is the URL to send them to. |
authorized_at | string,null (date-time) | yes | Spelled with a z, unlike authorised in status. Both spellings are column names, not typos. |
bank_consent_id | string,null | yes | The bank's own identifier for the consent. Null until the bank issues one. |
bank_id | string | yes | Lowercase kebab-case, for example itau or bb. |
consumed_at | string,null (date-time) | yes | — |
consumer_id | string | yes | — |
created_at | string (date-time) | yes | — |
expires_at | string,null (date-time) | yes | Mirrors the bank-side consent expiry, as the bank reported it at initiation. |
id | string | yes | — |
metadata | object | yes | — |
revoked_at | string,null (date-time) | yes | — |
scopes | array of string | yes | — |
status | "pending" | "authorised" | "revoked" | "expired" | "consumed" | yes | — |
token_expires_at | string,null (date-time) | yes | — |
wallet_id | string,null | yes | Set when the consent was bound to a wallet at initiation; null when it was not. |
Example response
{
"id": "obj_0000000000000000",
"bank_id": "bank_0000000000000000",
"consumer_id": "csm_0000000000000000",
"status": "pending",
"scopes": [
"string"
],
"created_at": "2026-01-15T12:00:00.000Z",
"metadata": {}
}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"
}'