Sellers
6 operations under /v1/sellers (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Base URL: https://api.codespar.dev
Every operation below requires a Bearer token. See Authentication.
POST /v1/sellers
https://api.codespar.dev/v1/sellersOnboard a seller (KYB) and affiliate it at Rinne
Creates the sell-side counterparty. The body mirrors Rinne's create_merchant input: full_name, document_number, document_type, document_tax_type, mcc, contact, address and transfer_configurations are required, the rest are optional. contact and address are passed through as free objects and are not validated field by field here.
IDEMPOTENT ON THE DOCUMENT, and this is the field an integrator most needs to know. Before anything is created the flow searches Rinne for a merchant with the same document and for an existing active affiliation, and the local row is written with ON CONFLICT (org_id, project_id, document_number) that reuses the SAME seller_id instead of minting a second one. A repeat call therefore answers 201 again, with the id you already had. The whole flow is serialized per (org, project, document) under an advisory lock, so two concurrent calls cannot both decide the merchant is new.
linked_consumer_id is set on first write and then kept: a later call that sends a different one does not overwrite it. The other KYB fields are overwritten by the re-POST.
document_number must be a CPF (11 digits) or a CNPJ (14 characters, letters allowed in the twelve base positions per IN RFB 2229/2024) whose check digits verify, and document_type / document_tax_type must agree with it (CPF ↔ PF, CNPJ ↔ PJ | MEI | ME); any of those failing is 400 invalid_body with the offending field in issues[].path, before Rinne is reached. It is then stored with every non-digit stripped. A value that survives the schema but contains no digits at all leaves nothing to store, and that surfaces as 502 seller_document_required, not as 400: the check lives in the provisioning flow, past the point where the body was validated.
The response is the seller row, not Rinne's. status and affiliation_status reflect whatever the affiliation was at that instant, which for a fresh onboarding is normally pending rather than active.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
address | object | yes | — |
allowed_capture_methods | array of string | no | — |
allowed_payment_methods | array of string | no | — |
company_logo_url | string | no | — |
contact | object | yes | — |
declared_income | number | no | — |
declared_revenue | number | no | — |
document_number | string | yes | — |
document_tax_type | "PJ" | "MEI" | "ME" | "PF" | yes | — |
document_type | "CNPJ" | "CPF" | yes | — |
fee_policy_id | string | no | — |
full_name | string | yes | — |
linked_consumer_id | string | no | — |
mcc | string | yes | — |
metadata | object | no | — |
name | string | no | — |
net_worth | number | no | — |
transfer_configurations | object | yes | — |
website_url | string | no | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | OK |
400 | object | Bad Request — the body or query did not match the schema. |
502 | object | Bad Gateway. Provisioning did not complete. error carries the failing step's code when the throw had one (for example rinne_create_merchant_failed, rinne_merchant_ambiguous, rinne_credentials_unavailable, seller_document_required) and seller_provisioning_failed otherwise. The code set is not closed: the Rinne codes are built from the call's action name. Nothing was written locally when this is returned. |
Response 201
| Field | Type | Required | Description |
|---|---|---|---|
affiliation_status | string | yes | Rinne's own status string, stored verbatim (for example PENDING_APPROVAL, ACTIVE, REJECTED). This is the column with NO CHECK constraint, and that is deliberate: a status Rinne introduces later is stored as it came instead of turning into a hard write failure. Treat it as an open set. |
created_at | string (date-time) | yes | — |
document_number | string | yes | Digits only. Everything non-numeric is stripped before the row is written. |
full_name | string | yes | — |
linked_consumer_id | string,null | yes | — |
rinne_affiliation_id | string,null | yes | — |
rinne_merchant_id | string,null | yes | — |
seller_id | string | yes | slr_ + nanoid. Stable across a re-POST of the same document. |
status | string | yes | CodeSpar's normalized lifecycle, derived from affiliation_status by deriveSellerStatus. A CHECK constraint on the column admits exactly five values — pending, affiliation_pending, active, rejected, failed — and a write outside that set fails rather than being stored. Three of the five are what this API produces: ACTIVE maps to active, REJECTED to rejected, and every other Rinne string, recognized or not, to affiliation_pending rather than to a failure state. pending is the column default and no onboarding write leaves it in place; nothing writes failed. |
updated_at | string (date-time) | yes | — |
wallet_id | string,null | yes | The BRL wallet this seller's settlements credit. Null until the first settlement lands, because the wallet is provisioned lazily on that credit. |
curl -X POST https://api.codespar.dev/v1/sellers \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"full_name": "Example",
"name": "Example",
"document_number": "string",
"document_type": "CNPJ",
"document_tax_type": "PJ",
"mcc": "string",
"contact": {},
"address": {},
"transfer_configurations": {
"automatic_transfer_enabled": true,
"transfer_frequency": "DAILY",
"rail": "PIX",
"utc_hour_of_day": 0,
"day_of_week": 0,
"day_of_month": 0,
"min_balance": 1000
},
"declared_revenue": 0,
"declared_income": 0,
"net_worth": 0,
"company_logo_url": "https://example.com/hook",
"website_url": "https://example.com/hook",
"fee_policy_id": "feepolicy_0000000000000000",
"metadata": {},
"linked_consumer_id": "linkedconsumer_0000000000000000",
"allowed_capture_methods": [
"string"
],
"allowed_payment_methods": [
"string"
]
}'POST /v1/sellers HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"full_name": "Example",
"name": "Example",
"document_number": "string",
"document_type": "CNPJ",
"document_tax_type": "PJ",
"mcc": "string",
"contact": {},
"address": {},
"transfer_configurations": {
"automatic_transfer_enabled": true,
"transfer_frequency": "DAILY",
"rail": "PIX",
"utc_hour_of_day": 0,
"day_of_week": 0,
"day_of_month": 0,
"min_balance": 1000
},
"declared_revenue": 0,
"declared_income": 0,
"net_worth": 0,
"company_logo_url": "https://example.com/hook",
"website_url": "https://example.com/hook",
"fee_policy_id": "feepolicy_0000000000000000",
"metadata": {},
"linked_consumer_id": "linkedconsumer_0000000000000000",
"allowed_capture_methods": [
"string"
],
"allowed_payment_methods": [
"string"
]
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/sellers",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"full_name": "Example",
"name": "Example",
"document_number": "string",
"document_type": "CNPJ",
"document_tax_type": "PJ",
"mcc": "string",
"contact": {},
"address": {},
"transfer_configurations": {
"automatic_transfer_enabled": True,
"transfer_frequency": "DAILY",
"rail": "PIX",
"utc_hour_of_day": 0,
"day_of_week": 0,
"day_of_month": 0,
"min_balance": 1000
},
"declared_revenue": 0,
"declared_income": 0,
"net_worth": 0,
"company_logo_url": "https://example.com/hook",
"website_url": "https://example.com/hook",
"fee_policy_id": "feepolicy_0000000000000000",
"metadata": {},
"linked_consumer_id": "linkedconsumer_0000000000000000",
"allowed_capture_methods": [
"string"
],
"allowed_payment_methods": [
"string"
]
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/sellers", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"full_name": "Example",
"name": "Example",
"document_number": "string",
"document_type": "CNPJ",
"document_tax_type": "PJ",
"mcc": "string",
"contact": {},
"address": {},
"transfer_configurations": {
"automatic_transfer_enabled": true,
"transfer_frequency": "DAILY",
"rail": "PIX",
"utc_hour_of_day": 0,
"day_of_week": 0,
"day_of_month": 0,
"min_balance": 1000
},
"declared_revenue": 0,
"declared_income": 0,
"net_worth": 0,
"company_logo_url": "https://example.com/hook",
"website_url": "https://example.com/hook",
"fee_policy_id": "feepolicy_0000000000000000",
"metadata": {},
"linked_consumer_id": "linkedconsumer_0000000000000000",
"allowed_capture_methods": [
"string"
],
"allowed_payment_methods": [
"string"
]
}),
});
const data = await res.json();const result = await cs.api.post("/v1/sellers", {
body: {
full_name: "Example",
name: "Example",
document_number: "string",
document_type: "CNPJ",
document_tax_type: "PJ",
mcc: "string",
contact: {},
address: {},
transfer_configurations: {
automatic_transfer_enabled: true,
transfer_frequency: "DAILY",
rail: "PIX",
utc_hour_of_day: 0,
day_of_week: 0,
day_of_month: 0,
min_balance: 1000
},
declared_revenue: 0,
declared_income: 0,
net_worth: 0,
company_logo_url: "https://example.com/hook",
website_url: "https://example.com/hook",
fee_policy_id: "feepolicy_0000000000000000",
metadata: {},
linked_consumer_id: "linkedconsumer_0000000000000000",
allowed_capture_methods: [
"string"
],
allowed_payment_methods: [
"string"
]
}
});{
"seller_id": "slr_0000000000000000",
"status": "string",
"affiliation_status": "string",
"rinne_merchant_id": "rinnemerchant_0000000000000000",
"rinne_affiliation_id": "rinneaffiliation_0000000000000000",
"document_number": "string",
"linked_consumer_id": "linkedconsumer_0000000000000000",
"full_name": "Example",
"wallet_id": "wlt_0000000000000000",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}GET /v1/sellers/{sellerId}
https://api.codespar.dev/v1/sellers/{sellerId}Read one seller
The stored row, with no call to Rinne. Use /status instead when the answer has to be current rather than last known.
Scoped to the caller's org AND project, so a seller onboarded under a different project of the same organization answers 404 here.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
sellerId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found. No seller with that id under the caller's org and project. A seller that exists in another tenant answers identically, so a 404 here is not evidence that the id was never minted. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
affiliation_status | string | yes | Rinne's own status string, stored verbatim (for example PENDING_APPROVAL, ACTIVE, REJECTED). This is the column with NO CHECK constraint, and that is deliberate: a status Rinne introduces later is stored as it came instead of turning into a hard write failure. Treat it as an open set. |
created_at | string (date-time) | yes | — |
document_number | string | yes | Digits only. Everything non-numeric is stripped before the row is written. |
full_name | string | yes | — |
linked_consumer_id | string,null | yes | — |
rinne_affiliation_id | string,null | yes | — |
rinne_merchant_id | string,null | yes | — |
seller_id | string | yes | slr_ + nanoid. Stable across a re-POST of the same document. |
status | string | yes | CodeSpar's normalized lifecycle, derived from affiliation_status by deriveSellerStatus. A CHECK constraint on the column admits exactly five values — pending, affiliation_pending, active, rejected, failed — and a write outside that set fails rather than being stored. Three of the five are what this API produces: ACTIVE maps to active, REJECTED to rejected, and every other Rinne string, recognized or not, to affiliation_pending rather than to a failure state. pending is the column default and no onboarding write leaves it in place; nothing writes failed. |
updated_at | string (date-time) | yes | — |
wallet_id | string,null | yes | The BRL wallet this seller's settlements credit. Null until the first settlement lands, because the wallet is provisioned lazily on that credit. |
curl -X GET https://api.codespar.dev/v1/sellers/{sellerId} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/sellers/{sellerId} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/sellers/{sellerId}",
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/sellers/{sellerId}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/sellers/{sellerId}", {
path: {
sellerId: "slr_0000000000000000"
}
});{
"seller_id": "slr_0000000000000000",
"status": "string",
"affiliation_status": "string",
"rinne_merchant_id": "rinnemerchant_0000000000000000",
"rinne_affiliation_id": "rinneaffiliation_0000000000000000",
"document_number": "string",
"linked_consumer_id": "linkedconsumer_0000000000000000",
"full_name": "Example",
"wallet_id": "wlt_0000000000000000",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}GET /v1/sellers/{sellerId}/custody
https://api.codespar.dev/v1/sellers/{sellerId}/custodyCompare this seller's ledger against Rinne's custody
The pre-payout reconciliation: the wallet's available BRL against what Rinne reports holding for the bound merchant. Both sides are BRL MINOR units (centavos), never decimals and never the 6-decimal atomic units the USDC surface uses.
ledger_exceeds_custody is the state to alarm on: the ledger shows more than Rinne holds, and spending against that balance can fail at payout. unattributed_credit is ordinary while a settlement webhook is in flight, and the pending-settlement read above is where to look next.
A seller with no wallet yet compares as zero on the ledger side rather than erroring, so unattributed_credit on a never funded seller is expected. A seller with no Rinne merchant id answers 409 instead of comparing zero against zero and calling it reconciled.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
sellerId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found. No seller with that id under the caller's org and project. |
409 | object | Conflict. The seller has no rinne_merchant_id yet, so there is no custody to compare against. |
502 | object | Bad Gateway. The balance read failed or could not be understood: rinne_get_balance_failed when Rinne answered non-ok, rinne_get_balance_unparseable when the response carried no balance field this code recognizes, seller_custody_failed as the fallback. The code set is not closed. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
difference_minor | string | yes | rinne_minor minus ledger_minor. Signed and never clamped: the negative direction is the dangerous one and has to survive to the reader. |
ledger_minor | string | yes | What the wallet ledger says is available, BRL minor units (centavos). |
note | string | yes | One sentence for the same three states, ready to render. |
rinne_merchant_id | string | yes | — |
rinne_minor | string | yes | What Rinne reports as this merchant's balance, BRL minor units. |
seller_id | string | yes | — |
state | "reconciled" | "unattributed_credit" | "ledger_exceeds_custody" | yes | reconciled when the difference is zero. unattributed_credit when Rinne holds more than the ledger attributed, which is the ordinary state while a settlement webhook is in flight. ledger_exceeds_custody when the ledger shows more than Rinne holds, which is the state a payout can fail on. |
curl -X GET https://api.codespar.dev/v1/sellers/{sellerId}/custody \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/sellers/{sellerId}/custody HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/sellers/{sellerId}/custody",
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/sellers/{sellerId}/custody", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/sellers/{sellerId}/custody", {
path: {
sellerId: "slr_0000000000000000"
}
});{
"seller_id": "slr_0000000000000000",
"rinne_merchant_id": "rinnemerchant_0000000000000000",
"ledger_minor": "1000",
"rinne_minor": "1000",
"difference_minor": "1000",
"state": "reconciled",
"note": "string"
}GET /v1/sellers/{sellerId}/ledger
https://api.codespar.dev/v1/sellers/{sellerId}/ledgerThis seller's wallet ledger, without learning the wallet id
A passthrough to the seller's own wallet ledger, scoped by its wallet_id, so a caller addresses the seller it already knows instead of the internal wallet. Same rows, same page shape and the same query as GET /v1/wallets/{id}/ledger. A settlement credit from Rinne lands here as fund and a reversed one as reverse; every other movement on the same wallet appears too, because this reads the whole ledger for that wallet and not a settlement-only view of it.
Newest first. next_before is the id to send as before_id for the next page, and it is null on the last page. It is only non-null when the page came back exactly full, so a final page that happens to be full yields one more request that returns nothing.
A seller that has never been settled has no wallet yet, and that is a normal state rather than an error: it answers 200 with an empty page. That short-circuit runs BEFORE the query is validated, so a malformed limit or before_id on such a seller also returns the empty page instead of 400.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
sellerId | string | yes | — |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
before_id | string | no | — |
kind | "fund" | "hold" | "release" | "debit" | "reconcile" | "reverse" | "fee" | no | — |
limit | integer | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Bad Request — the body or query did not match the schema. |
404 | object | Not Found. No seller with that id under the caller's org and project. A seller that exists in another tenant answers identically, so a 404 here is not evidence that the id was never minted. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
entries | array of object | yes | — |
next_before | string,null | yes | — |
curl -X GET https://api.codespar.dev/v1/sellers/{sellerId}/ledger \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/sellers/{sellerId}/ledger HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/sellers/{sellerId}/ledger",
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/sellers/{sellerId}/ledger", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/sellers/{sellerId}/ledger", {
path: {
sellerId: "slr_0000000000000000"
}
});{
"entries": [
{
"id": "obj_0000000000000000",
"wallet_id": "wlt_0000000000000000",
"org_id": "org_0000000000000000",
"currency": "BRL",
"amount_minor": "1000",
"kind": "fund",
"mandate_id": "mandate_0000000000000000",
"attempt_id": "attempt_0000000000000000",
"external_ref": "string",
"reconciled_at": "2026-01-15T12:00:00.000Z",
"posted_at": "2026-01-15T12:00:00.000Z",
"metadata": {}
}
],
"next_before": "string"
}GET /v1/sellers/{sellerId}/pending-settlement
https://api.codespar.dev/v1/sellers/{sellerId}/pending-settlementWhat Rinne reports as sold but not yet settled
A read-through view of the seller's Rinne statement, filtered to the movements explicitly marked unsettled. INFORMATIONAL ONLY: this endpoint never writes to the wallet balance or the wallet ledger. The credit happens later, when the settlement webhook reports the movement settled. Money listed here is coming, it is not spendable.
READ unparsed_entries BEFORE READING THE TOTAL. A movement whose settled field is not a real boolean is excluded rather than guessed, so an unrecognized statement shape produces a SMALLER total, not an error. When unparsed_entries is greater than zero, total_pending_minor: "0" means the statement was not understood, not that nothing is pending. The statement shape has not been verified against live Rinne traffic.
409 is the not-yet case rather than a fault: the seller exists locally but has no Rinne merchant id, so there is nothing to ask about.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
sellerId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found. No seller with that id under the caller's org and project. |
409 | object | Conflict. The seller has no rinne_merchant_id yet, so it was never provisioned at Rinne and has no statement to read. |
502 | object | Bad Gateway. The statement read failed. error carries the failing step's code when the throw had one (for example rinne_get_statement_failed, rinne_credentials_unavailable) and seller_pending_settlement_failed otherwise. The code set is not closed. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
entries | array of object | yes | — |
parsed_entries | integer | yes | How many of those carried a settled field this code could read as a boolean. |
raw_entries | integer | yes | How many movements the statement returned, before any filtering. |
rinne_merchant_id | string | yes | — |
seller_id | string | yes | — |
total_pending_minor | string | yes | Sum of amount_minor over the entries below. Informational only: it is not a balance, it is not spendable, and nothing here is ever posted to the wallet ledger. |
unparsed_entries | integer | yes | raw_entries minus parsed_entries. Greater than zero means total_pending_minor understates what Rinne reported, so a total of 0 is not evidence that nothing is pending. |
curl -X GET https://api.codespar.dev/v1/sellers/{sellerId}/pending-settlement \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/sellers/{sellerId}/pending-settlement HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/sellers/{sellerId}/pending-settlement",
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/sellers/{sellerId}/pending-settlement", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/sellers/{sellerId}/pending-settlement", {
path: {
sellerId: "slr_0000000000000000"
}
});{
"seller_id": "slr_0000000000000000",
"rinne_merchant_id": "rinnemerchant_0000000000000000",
"total_pending_minor": "1000",
"entries": [
{
"id": "obj_0000000000000000",
"amount_minor": "1000",
"currency": "BRL",
"type": "string",
"operation": "string",
"transaction_id": "transaction_0000000000000000",
"created_at": "string",
"raw": {}
}
],
"raw_entries": 0,
"parsed_entries": 0,
"unparsed_entries": 0
}GET /v1/sellers/{sellerId}/status
https://api.codespar.dev/v1/sellers/{sellerId}/statusRefresh a seller's affiliation status from Rinne
Reads the affiliation at Rinne and, when the status changed, writes the new one to the row before answering. On-demand, not a scheduled sweep.
DEGRADES RATHER THAN FAILING, and says so. When the Rinne read does not land the last known row is still returned with 200, carrying stale: true and a refresh_error. Read those two before trusting status: a 200 here is not by itself a confirmation. A seller with nothing at Rinne yet (both provider ids null) is not stale, because there is no upstream status for it to lag, so it comes back with refreshed_at: null and refresh_error: null.
The 502 is the narrower case where the call threw outright, for instance because no Rinne credential could be resolved. A non-ok response from Rinne does not reach it; that is the stale path above.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
sellerId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found. No seller with that id under the caller's org and project. A seller that exists in another tenant answers identically, so a 404 here is not evidence that the id was never minted. |
502 | object | Bad Gateway. The status refresh threw before a row could be rendered. error is always seller_status_failed; the underlying reason is in message. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
affiliation_status | string | yes | Rinne's own status string, stored verbatim (for example PENDING_APPROVAL, ACTIVE, REJECTED). This is the column with NO CHECK constraint, and that is deliberate: a status Rinne introduces later is stored as it came instead of turning into a hard write failure. Treat it as an open set. |
created_at | string (date-time) | yes | — |
document_number | string | yes | Digits only. Everything non-numeric is stripped before the row is written. |
full_name | string | yes | — |
linked_consumer_id | string,null | yes | — |
refresh_error | string,null | yes | Why the Rinne read did not land. Null on success and on a seller with nothing at Rinne yet. |
refreshed_at | string,null (date-time) | yes | When the Rinne read that produced this row landed. Null when no read landed, which covers both a failed read and a seller with nothing at Rinne yet. |
rinne_affiliation_id | string,null | yes | — |
rinne_merchant_id | string,null | yes | — |
seller_id | string | yes | slr_ + nanoid. Stable across a re-POST of the same document. |
stale | boolean | yes | Derived as refresh_error !== null, so it can never disagree with the field beside it. True means the body is the last known row, not what Rinne says right now. |
status | string | yes | CodeSpar's normalized lifecycle, derived from affiliation_status by deriveSellerStatus. A CHECK constraint on the column admits exactly five values — pending, affiliation_pending, active, rejected, failed — and a write outside that set fails rather than being stored. Three of the five are what this API produces: ACTIVE maps to active, REJECTED to rejected, and every other Rinne string, recognized or not, to affiliation_pending rather than to a failure state. pending is the column default and no onboarding write leaves it in place; nothing writes failed. |
updated_at | string (date-time) | yes | — |
wallet_id | string,null | yes | The BRL wallet this seller's settlements credit. Null until the first settlement lands, because the wallet is provisioned lazily on that credit. |
curl -X GET https://api.codespar.dev/v1/sellers/{sellerId}/status \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/sellers/{sellerId}/status HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/sellers/{sellerId}/status",
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/sellers/{sellerId}/status", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/sellers/{sellerId}/status", {
path: {
sellerId: "slr_0000000000000000"
}
});{
"seller_id": "slr_0000000000000000",
"status": "string",
"affiliation_status": "string",
"rinne_merchant_id": "rinnemerchant_0000000000000000",
"rinne_affiliation_id": "rinneaffiliation_0000000000000000",
"document_number": "string",
"linked_consumer_id": "linkedconsumer_0000000000000000",
"full_name": "Example",
"wallet_id": "wlt_0000000000000000",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z",
"refreshed_at": "2026-01-15T12:00:00.000Z",
"refresh_error": "string",
"stale": true
}Counterparties
3 operations under /v1/counterparties (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Agents
6 operations under /v1/agents (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.