Counterparties
3 operations under /v1/counterparties (GET POST): 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.
GET /v1/counterparties
https://api.codespar.dev/v1/counterpartiesList this project's counterparties
Most recently seen first, capped by limit. There is NO CURSOR: a project with more counterparties than limit (max 200) cannot be walked past the first page from here.
identifier and identifier_kind filter only when BOTH are present. Either one on its own is accepted by the validator and then silently ignored, so a lookup by bare identifier returns the unfiltered page rather than an error or an empty list.
The canonical path. GET /v1/commerce-memory/counterparties is the deprecated alias of this operation and is kept for two releases. The two paths are the same handler with the same required scope, so switching one for the other changes nothing about the request, the response or the credential.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
identifier | string | no | — |
identifier_kind | "cnpj" | "cpf" | "email" | "phone" | "wallet_address" | "other" | no | — |
kind | "supplier" | "customer" | "agent_peer" | no | — |
limit | integer | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | invalid_query, with the validator's issue list under details.issues. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
counterparties | array of object | yes | — |
curl -X GET https://api.codespar.dev/v1/counterparties \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/counterparties HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/counterparties",
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/counterparties", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/counterparties");{
"counterparties": [
{
"id": "obj_0000000000000000",
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {},
"created_at": "2026-01-15T12:00:00.000Z",
"last_seen_at": "2026-01-15T12:00:00.000Z"
}
]
}POST /v1/counterparties
https://api.codespar.dev/v1/counterpartiesRegister a counterparty, or merge into the one already there
An UPSERT when identifier and identifier_kind are both supplied, keyed on that pair within the caller's org AND project. Registering the same CNPJ a second time from a different agent run in the same project does not create a second row: name is replaced, last_seen_at is bumped to now, and metadata is merged over the stored object at the top level only, so a nested object is replaced wholesale rather than deep merged. Two different projects that both register that CNPJ keep two rows and two separate histories.
Omit both identifier fields and there is no dedup key, so every call creates a new row. The two fields travel together: one without the other is 400.
The status is 201 on both paths, including the merge. A 201 here is not a promise that a row was created.
The canonical path. POST /v1/commerce-memory/counterparties is the deprecated alias of this operation and is kept for two releases. The two paths are the same handler with the same required scope, so switching one for the other changes nothing about the request, the response or the credential.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
identifier | string | no | Set it together with identifier_kind or omit both. One without the other is refused. |
identifier_kind | "cnpj" | "cpf" | "email" | "phone" | "wallet_address" | "other" | no | — |
kind | "supplier" | "customer" | "agent_peer" | yes | — |
metadata | object | no | — |
name | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | Created, or merged into the existing row. |
400 | object | invalid_body. details.issues carries the validator's own issue list, including the case where only one of identifier / identifier_kind was sent. |
Response 201
| Field | Type | Required | Description |
|---|---|---|---|
created_at | string (date-time) | yes | — |
id | string | yes | cp_ prefixed. |
identifier | string,null | yes | Null when the counterparty was registered without one. |
identifier_kind | "cnpj" | "cpf" | "email" | "phone" | "wallet_address" | "other" | yes | — |
kind | "supplier" | "customer" | "agent_peer" | yes | — |
last_seen_at | string (date-time) | yes | Bumped by a re-registration of the same identifier and by every interaction written against this counterparty. |
metadata | object | yes | — |
name | string | yes | — |
curl -X POST https://api.codespar.dev/v1/counterparties \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {}
}'POST /v1/counterparties HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {}
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/counterparties",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {}
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/counterparties", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {}
}),
});
const data = await res.json();const result = await cs.api.post("/v1/counterparties", {
body: {
kind: "supplier",
name: "Example",
identifier: "string",
identifier_kind: "cnpj",
metadata: {}
}
});{
"id": "obj_0000000000000000",
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {},
"created_at": "2026-01-15T12:00:00.000Z",
"last_seen_at": "2026-01-15T12:00:00.000Z"
}GET /v1/counterparties/{id}
https://api.codespar.dev/v1/counterparties/{id}Read one counterparty
The same row the list returns, by id.
A counterparty that belongs to another org, or to a sibling project of the same org, is indistinguishable from one that does not exist: both are 404. Counterparty ids are unique per project and not globally, so the same id can name a different company in another project.
The canonical path. GET /v1/commerce-memory/counterparties/{id} is the deprecated alias of this operation, described in this document under that path and kept for two releases. The two paths are the same handler with the same required scope, so switching one for the other changes nothing about the request, the response or the credential.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | cp_ prefixed. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | not_found, including for a counterparty outside the caller's project. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
created_at | string (date-time) | yes | — |
id | string | yes | cp_ prefixed. |
identifier | string,null | yes | Null when the counterparty was registered without one. |
identifier_kind | "cnpj" | "cpf" | "email" | "phone" | "wallet_address" | "other" | yes | CHECK (identifier_kind IN ('cnpj', 'cpf', 'email', 'phone', 'wallet_address', 'other')), migration 0050. Null exactly when identifier is null: the pair is held together by commerce_counterparties_identifier_pair, a table constraint, so one without the other cannot be stored. |
kind | "supplier" | "customer" | "agent_peer" | yes | CHECK (kind IN ('supplier', 'customer', 'agent_peer')), migration 0050. |
last_seen_at | string (date-time) | yes | Bumped by a re-registration of the same identifier and by every interaction recorded against this row. |
metadata | object | yes | — |
name | string | yes | — |
curl -X GET https://api.codespar.dev/v1/counterparties/{id} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/counterparties/{id} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/counterparties/{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/counterparties/{id}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/counterparties/{id}", {
path: {
id: "counterpartie_0000000000000000"
}
});{
"id": "obj_0000000000000000",
"kind": "supplier",
"name": "Example",
"identifier": "string",
"identifier_kind": "cnpj",
"metadata": {},
"created_at": "2026-01-15T12:00:00.000Z",
"last_seen_at": "2026-01-15T12:00:00.000Z"
}Account Applications
2 operations under /v1/account-applications (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Sellers
6 operations under /v1/sellers (POST GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.