CodeEscrows
Developers
Automate deposits from CI, read agreement, verification and release status, receive signed webhooks and, for partners, onboard client agreements in bulk. Authenticate with an organisation API key (Settings → API keys) sent as a bearer token.
Quick start
curl -s https://staging.codeescrows.com/api/v1/agreements \
-H "Authorization: Bearer $CODEESCROWS_API_KEY"Create a client
The SDK uses the platform fetch and has no dependencies.
import { CodeEscrowsClient } from "@codeescrows/sdk";
const client = new CodeEscrowsClient({
baseUrl: "https://staging.codeescrows.com",
apiKey: process.env.CODEESCROWS_API_KEY!, // ce_live_…
});List agreements (all pages)
Iterates every agreement your organisation is a party to.
Scopes: agreements:read
for await (const agreement of client.agreements.listAll({ status: "active" })) {
console.log(agreement.public_id, agreement.status, agreement.health_score);
}Follow a deposit to sealed
Polls a deposit until it is sealed and prints its receipt verification link.
Scopes: deposits:read
const deposit = await client.deposits.waitUntilSettled(depositId);
if (deposit.state === "sealed" && deposit.receipt) {
console.log("Verify:", new URL(deposit.receipt.verify_path, client.baseUrl).toString());
}Upload a deposit
Starts an upload, PUTs each part to its presigned URL with its SHA-256, then completes. Use the codeescrows CLI for large archives.
Scopes: deposits:write
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
const archive = await readFile("product.tar.gz");
const checksum = createHash("sha256").update(archive).digest("base64");
const session = await client.deposits.create({
agreement_id: agreementId,
filename: "product.tar.gz",
content_type: "application/gzip",
size: archive.byteLength,
parts: [{ part_number: 1, checksum_sha256: checksum }],
});
const [part] = session.upload.parts;
const put = await fetch(part.url, { method: "PUT", headers: part.headers, body: archive });
await client.deposits.complete(session.deposit.id, [
{ part_number: 1, etag: put.headers.get("etag")! },
]);Watch release requests
Lists open release requests and their objection deadlines.
Scopes: releases:read
const { data } = await client.releases.list({ state: "objection_window" });
for (const r of data) console.log(r.agreement_id, r.state, r.objection_deadline_at);Handle errors
Every failure is a CodeEscrowsError with a stable code and the request id.
import { CodeEscrowsError } from "@codeescrows/sdk";
try {
await client.agreements.get(agreementId);
} catch (error) {
if (error instanceof CodeEscrowsError) {
// e.g. not_found, api_key_scope, rate_limited (see retryAfterSeconds)
console.error(error.status, error.code, error.requestId);
}
throw error;
}Register a webhook endpoint
The signing secret is returned once: store it with your receiver.
Scopes: webhooks:manage
const endpoint = await client.webhooks.endpoints.create({
url: "https://hooks.example.com/codeescrows",
events: ["deposit.sealed", "release.submitted"],
});
saveSecret(endpoint.secret); // whsec… shown once
await client.webhooks.endpoints.test(endpoint.id); // queues a webhook.pingVerify a webhook delivery
Check X-CodeEscrows-Signature over the raw body before trusting the event (Web Crypto; any runtime).
import { verifyWebhookSignature, WEBHOOK_SIGNATURE_HEADER } from "@codeescrows/sdk";
export async function POST(request: Request) {
const body = await request.text(); // raw body, before JSON.parse
const ok = await verifyWebhookSignature({
secret: process.env.CODEESCROWS_WEBHOOK_SECRET!,
header: request.headers.get(WEBHOOK_SIGNATURE_HEADER),
body,
});
if (!ok) return new Response("invalid signature", { status: 400 });
const event = JSON.parse(body);
// handle event.type idempotently (event.id may be delivered more than once)
return new Response(null, { status: 204 });
}Replay failed webhook deliveries
Finds dead deliveries in the 30-day log and queues each again (same event id).
Scopes: webhooks:manage
for await (const delivery of client.webhooks.deliveries.listAll({ status: "dead" })) {
await client.webhooks.deliveries.replay(delivery.id);
}Partner: import client agreements
Dry run, then commit drafts, then (separately) invite the counterparties. Needs a partner:write key.
Scopes: partner:write
import { readFile } from "node:fs/promises";
const batch = await client.partners.imports.validate({
target_org_id: clientOrgId,
csv: await readFile("escode-export.csv", "utf8"),
preset: "escode",
});
console.log(batch.valid_count, "valid,", batch.error_count, "invalid");
const committed = await client.partners.imports.commit(batch.id); // drafts only, nothing sent
await client.partners.imports.sendInvitations(committed.id); // explicit stepcurl: download the OpenAPI document
Public, no key needed; generate clients from it or import it into your API tool.
curl -s "https://staging.codeescrows.com/api/v1/openapi.json" -o codeescrows-openapi.jsoncurl: list agreements
Any HTTP client works: send the key as a bearer token.
Scopes: agreements:read
curl -s "https://staging.codeescrows.com/api/v1/agreements?limit=20" \
-H "Authorization: Bearer $CODEESCROWS_API_KEY"MCP: add to Claude Code
Read-only MCP server for AI assistants. Create a key with mcp:read plus the read scopes the tools need.
Scopes: mcp:read, agreements:read, deposits:read, verifications:read, releases:read
claude mcp add --transport http codeescrows https://staging.codeescrows.com/api/mcp \
--header "Authorization: Bearer $CODEESCROWS_API_KEY"MCP: Claude Desktop configuration
claude_desktop_config.json entry using the mcp-remote bridge to pass the API key header.
Scopes: mcp:read, agreements:read, deposits:read, verifications:read, releases:read
{
"mcpServers": {
"codeescrows": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://staging.codeescrows.com/api/mcp", "--header", "Authorization:${CODEESCROWS_AUTH}"],
"env": { "CODEESCROWS_AUTH": "Bearer ce_live_…" }
}
}
}Verifying webhooks
Every delivery carries X-CodeEscrows-Signature with a timestamp and an HMAC-SHA256 of the raw body. Verify it in constant time and refuse timestamps older than five minutes before parsing the payload.
import { createHmac, timingSafeEqual } from "node:crypto";
// header: X-CodeEscrows-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, `${t}.${rawBody}`)>
// During a secret rotation the header carries one v1= per valid secret.
export function verifyCodeEscrowsSignature(
rawBody: string,
header: string | null,
secret: string,
toleranceSeconds = 300,
): boolean {
if (!header) return false;
const parts = header.split(",").map((p) => p.trim().split("="));
const t = Number(parts.find(([k]) => k === "t")?.[1]);
const candidates = parts.filter(([k]) => k === "v1").map(([, v]) => v ?? "");
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = Buffer.from(
createHmac("sha256", secret).update(`${t}.${rawBody}`, "utf8").digest("hex"),
);
return candidates.some((sig) => {
const given = Buffer.from(sig);
return given.length === expected.length && timingSafeEqual(given, expected);
});
}Event types (25)
agreement.activated— agreement.activatedagreement.amended— agreement.amendedagreement.released— agreement.releasedagreement.resumed— agreement.resumedagreement.sent_for_signature— agreement.sent_for_signatureagreement.suspended— agreement.suspendedagreement.terminated— agreement.terminateddeposit.failed— deposit.faileddeposit.sealed— deposit.sealedparty.accepted— party.acceptedparty.removed— party.removedparty.signed— party.signedparty.withdrawn— party.withdrawnrelease.approved— release.approvedrelease.delivered— release.deliveredrelease.denied— release.deniedrelease.disputed— release.disputedrelease.expired— release.expiredrelease.objected— release.objectedrelease.submitted— release.submittedrelease.withdrawn— release.withdrawnverification.completed— verification.completedverification.failed— verification.failedverification.order_delivered— verification.order_deliveredwebhook.ping— webhook.ping
Partner API
Partner organisations issue keys with the partner:read or partner:write scope to list client organisations, create clients (their owner consents by email), import draft agreements from CSV (Escode and EscrowTech presets) and read the informational revenue-share report. Imports never send anything: drafts are created, invitations are a separate call.
# Dry run (nothing is created or sent), then commit the drafts
curl -s -X POST https://staging.codeescrows.com/api/v1/partners/imports \
-H "Authorization: Bearer $CODEESCROWS_PARTNER_KEY" -H "content-type: application/json" \
-d "$(jq -n --arg csv "$(cat agreements.csv)" --arg org "$CLIENT_ORG_ID" \
'{target_org_id: $org, preset: "escode", csv: $csv}')"
curl -s -X POST https://staging.codeescrows.com/api/v1/partners/imports/$IMPORT_ID/commit \
-H "Authorization: Bearer $CODEESCROWS_PARTNER_KEY"API reference CodeEscrows API 1.0 · 39 operations
Machine-readable OpenAPI 3.1 document (no key needed): /api/v1/openapi.json. Generate clients from it or import it into your API tool.
Agreements
GET/api/v1/agreementsList agreements
Agreements the key's organisation owns or is a party to, newest first. A key restricted to agreements sees only those (pages may then hold fewer items than `limit`). **Scope:** `agreements:read` — Read agreements. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified. **Pagination:** cursor — pass `next_cursor` as `cursor` until it is null.
API-key scopes: agreements:read
Parameters
status(query)type(query)role(query)limit(query) — Page size (1–100, default 50)cursor(query) — `next_cursor` of the previous page
Responses
200A page of agreements401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/agreements/{agreement_id}Get an agreement
Role-appropriate view: private notes and other parties' contact details are hidden. **Scope:** `agreements:read` — Read agreements. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: agreements:read
Parameters
agreement_id(path, required) — Agreement id
Responses
200The agreement401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/agreements/{agreement_id}/partiesList an agreement's parties
**Scope:** `agreements:read` — Read agreements. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: agreements:read
Parameters
agreement_id(path, required) — Agreement id
Responses
200The parties (one page)401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
Deposits
GET/api/v1/agreements/{agreement_id}/deposits/{deposit_id}/certificateGet a Deposit Certificate
Receipt (statement, DSSE envelope, public key) and RFC 3161 timestamps as JSON; `?format=pdf` answers a time-limited link to the PDF (`SignedLink`), `&redirect=1` a 302. **Scope:** `deposits:read` — Read deposits, receipts and manifests. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: deposits:read
Parameters
agreement_id(path, required) — Agreement iddeposit_id(path, required) — Deposit idformat(query)redirect(query)
Responses
200Certificate facts401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/depositsList an agreement's deposits
**Scope:** `deposits:read` — Read deposits, receipts and manifests. **Auth:** API key only (`Authorization: Bearer ce_…`). **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified. **Pagination:** cursor — pass `next_cursor` as `cursor` until it is null.
API-key scopes: deposits:read
Parameters
limit(query)cursor(query)state(query)agreement_id(query, required)
Responses
200A page of deposits401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/depositsStart a deposit upload
Creates a `pending_upload` deposit and its multipart plan; PUT each part to its presigned URL, then call `complete`. The key's organisation must be the agreement's depositor. **Scope:** `deposits:write` — Upload deposits (and follow their status). **Auth:** API key only (`Authorization: Bearer ce_…`). **Rate limit:** 60 upload sessions/hour per key, within the 600/min per-key budget.
API-key scopes: deposits:write
Request body: object
Responses
201Upload session401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)413Request body too large (`payload_too_large`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/deposits/{deposit_id}Get a deposit
State, sizes, Merkle root, receipt (id + verify path) and the Level 1 summary. **Scope:** `deposits:read` — Read deposits, receipts and manifests. **Auth:** API key only (`Authorization: Bearer ce_…`). **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: deposits:read
Parameters
deposit_id(path, required) — Deposit id
Responses
200The deposit401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/deposits/{deposit_id}/abortAbort a deposit upload
**Scope:** `deposits:write` — Upload deposits (and follow their status). **Auth:** API key only (`Authorization: Bearer ce_…`). **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: deposits:write
Parameters
deposit_id(path, required) — Deposit id
Request body: object
Responses
200The deposit, now failed401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/deposits/{deposit_id}/completeComplete a deposit upload
Completes the multipart upload; the pipeline (scan → seal → attest → notify) starts. **Scope:** `deposits:write` — Upload deposits (and follow their status). **Auth:** API key only (`Authorization: Bearer ce_…`). **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: deposits:write
Parameters
deposit_id(path, required) — Deposit id
Request body: object
Responses
202Accepted; follow `status_url`401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)413Request body too large (`payload_too_large`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/deposits/{deposit_id}/partsPresign more upload parts
**Scope:** `deposits:write` — Upload deposits (and follow their status). **Auth:** API key only (`Authorization: Bearer ce_…`). **Rate limit:** 2,000 part presigns/min per key, within the 600/min per-key budget.
API-key scopes: deposits:write
Parameters
deposit_id(path, required) — Deposit id
Request body: object
Responses
200Presigned part URLs401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
Meta
GET/api/v1/openapi.jsonGet this OpenAPI document
The OpenAPI 3.1 description of API v1 (this document). Public: no key needed. Cacheable (`ETag`, `If-None-Match` → 304). **Auth:** none (public). **Rate limit:** 120 requests/min per IP (no key needed).
Responses
200The OpenAPI document429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support503Feature not configured on this deployment
Partners
GET/api/v1/partners/clientsList client organisations
Client organisations of the key's (partner) organisation, pending and active links, with agreement count, deposit freshness and fee standing once the client consented. Every read of client data is audited in the client's chain. **Scope:** `partner:read` or `partner:write` — Partner API: list client organisations and the revenue-share report. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: partner:readpartner:write
Responses
200Client organisations401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/partners/clientsCreate a client organisation
Creates the client organisation with no members, invites `owner_email` as its owner and records a **pending** link: nothing of the client is visible until its owner consents. The key's creator must be an owner or admin; plan feature `partner`. `accept_url` is shown once. **Scope:** `partner:write` — Partner API: create client organisations and import draft agreements (never sends anything). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: partner:write
Request body: object
Responses
201The client, the owner invitation and its accept URL401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)409Conflicting state (specific code, e.g. `certificate_not_ready`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/partners/clients/{org_id}/agreementsCreate a draft agreement for a client
One **draft** agreement owned by a linked client (active link with `manage_agreements`); same body as creating an agreement in the app. The answer is the agreement service's camelCase view (as built); read it with getAgreement for the `Agreement` shape. Nothing is sent; invite parties afterwards. 404 without such a link, 403 `partner_scope` for a view-only link. **Scope:** `partner:write` — Partner API: create client organisations and import draft agreements (never sends anything). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: partner:write
Parameters
org_id(path, required) — Client organisation id
Request body: object
Responses
201The draft agreement401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/partners/importsList import batches
The partner organisation's import batches, newest first (without row reports). **Scope:** `partner:read` or `partner:write` — Partner API: list client organisations and the revenue-share report. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: partner:readpartner:write
Responses
200Import batches401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/partners/importsValidate an import (dry run)
Parses, maps (preset + overrides) and validates a CSV of agreements for a client (or the partner itself) and stores the row report as a `validated` batch. **Nothing is created or sent** until commit. Body up to 1.2 MB (CSV ≤ 1 MB). **Scope:** `partner:write` — Partner API: create client organisations and import draft agreements (never sends anything). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: partner:write
Request body: object
Responses
201The batch with its row report401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)413Request body too large (`payload_too_large`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/partners/imports/{import_id}Get an import batch
The batch with its row report (own organisation's batches only). **Scope:** `partner:read` or `partner:write` — Partner API: list client organisations and the revenue-share report. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: partner:readpartner:write
Parameters
import_id(path, required) — Import batch id
Responses
200The batch401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/partners/imports/{import_id}/commitCommit an import
Creates one **draft** agreement per valid row (idempotent by row hash, also across batches); the client link is re-checked. Nothing is sent. 409 `import_not_validated` for a batch that is not `validated`. **Scope:** `partner:write` — Partner API: create client organisations and import draft agreements (never sends anything). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: partner:write
Parameters
import_id(path, required) — Import batch id
Responses
200The batch with created / skipped / failed counts401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/partners/imports/{import_id}/discardDiscard an import
Drops a `validated` (uncommitted) batch. **Scope:** `partner:write` — Partner API: create client organisations and import draft agreements (never sends anything). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: partner:write
Parameters
import_id(path, required) — Import batch id
Responses
200The batch, now `discarded`401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/partners/imports/{import_id}/invitationsInvite the counterparties of an import
Explicit step after commit: invites each committed draft's counterparty by email (the normal party invitation). Rows without an email are skipped. 409 `import_not_committed` before commit. **Scope:** `partner:write` — Partner API: create client organisations and import draft agreements (never sends anything). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: partner:write
Parameters
import_id(path, required) — Import batch id
Responses
200The batch with invited / skipped / failed counts401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/partners/revenueGet the revenue-share report
Revenue share of one **closed** month (default: the last closed month) under the published partner terms. **Informational, not a payout.** `?format=csv` answers a `text/csv` attachment instead. The key's creator must be an owner, admin or billing member. 400 `period_not_closed` for an open month. **Scope:** `partner:read` or `partner:write` — Partner API: list client organisations and the revenue-share report. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: partner:readpartner:write
Parameters
period(query) — Closed month `YYYY-MM` (default: the last closed month)format(query) — `csv` answers a `text/csv` attachment instead of JSON
Responses
200The report (JSON)400Malformed JSON (`malformed_json`)401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
Releases
GET/api/v1/agreements/{agreement_id}/releasesList an agreement's release requests
**Scope:** `releases:read` — Read release requests. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified. **Pagination:** cursor — pass `next_cursor` as `cursor` until it is null.
API-key scopes: releases:read
Parameters
agreement_id(path, required) — Agreement idstate(query)limit(query)cursor(query)
Responses
200A page of release requests401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/agreements/{agreement_id}/releases/{release_id}Get a release request
**Scope:** `releases:read` — Read release requests. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: releases:read
Parameters
agreement_id(path, required) — Agreement idrelease_id(path, required) — Release request id
Responses
200The release request401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/agreements/{agreement_id}/releases/{release_id}/certificateGet a Release Certificate
Certificate facts once the package is delivered; `?format=pdf` a link to the signed PDF. 409 `certificate_not_ready` before delivery. **Scope:** `releases:read` — Read release requests. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: releases:read
Parameters
agreement_id(path, required) — Agreement idrelease_id(path, required) — Release request idformat(query)redirect(query)addendum(query)
Responses
200Certificate facts401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/releasesList release requests across agreements
**Scope:** `releases:read` — Read release requests. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified. **Pagination:** cursor — pass `next_cursor` as `cursor` until it is null.
API-key scopes: releases:read
Parameters
state(query)limit(query)cursor(query)
Responses
200A page of release requests401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
Verifications
GET/api/v1/agreements/{agreement_id}/deposits/{deposit_id}/verificationsList a deposit's Level 2 verifications
**Scope:** `verifications:read` — Read verification status (summaries and counts, never findings). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: verifications:read
Parameters
agreement_id(path, required) — Agreement iddeposit_id(path, required) — Deposit idlimit(query)
Responses
200Runs, newest first401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/agreements/{agreement_id}/deposits/{deposit_id}/verifications/{verification_id}Get a Level 2 verification
**Scope:** `verifications:read` — Read verification status (summaries and counts, never findings). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: verifications:read
Parameters
agreement_id(path, required) — Agreement iddeposit_id(path, required) — Deposit idverification_id(path, required) — Level 2 verification id
Responses
200The run401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/agreements/{agreement_id}/deposits/{deposit_id}/verifications/{verification_id}/reportGet a Level 2 verification report
Hashes, timestamps and the caller's projection (JSON); `?format=pdf` answers a 15-minute link to the signed PDF (`SignedLink`). 409 `report_not_ready` until the run finished. **Scope:** `verifications:read` — Read verification status (summaries and counts, never findings). **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: verifications:read
Parameters
agreement_id(path, required) — Agreement iddeposit_id(path, required) — Deposit idverification_id(path, required) — Level 2 verification idformat(query)redirect(query)
Responses
200Report facts401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
Webhooks
GET/api/v1/webhook-endpointsList webhook endpoints
**Scope:** `webhooks:read` — Read webhook endpoints and the delivery log. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: webhooks:read
Responses
200Endpoints (one page; at most 10 per organisation)401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/webhook-endpointsCreate a webhook endpoint
HTTPS URLs resolving to public addresses only. The signing secret is returned once (`shown_once: true`). **Scope:** `webhooks:manage` — Manage webhook endpoints (create, rotate, test, delete) and replay deliveries. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: webhooks:manage
Request body: WebhookEndpointCreate
Responses
201The endpoint and its secret401Missing, invalid, expired or revoked API key (`unauthorized`)402The organisation's plan does not include this feature (`payment_required`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)409Conflicting state (specific code, e.g. `certificate_not_ready`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support503Feature not configured on this deployment
DELETE/api/v1/webhook-endpoints/{endpoint_id}Delete a webhook endpoint
Deletes the endpoint and its delivery log. **Scope:** `webhooks:manage` — Manage webhook endpoints (create, rotate, test, delete) and replay deliveries. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: webhooks:manage
Parameters
endpoint_id(path, required) — Webhook endpoint id
Responses
200Deleted401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/webhook-endpoints/{endpoint_id}Get a webhook endpoint
**Scope:** `webhooks:read` — Read webhook endpoints and the delivery log. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: webhooks:read
Parameters
endpoint_id(path, required) — Webhook endpoint id
Responses
200The endpoint401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
PATCH/api/v1/webhook-endpoints/{endpoint_id}Change a webhook endpoint
URL, description, event filter, or `active` to disable / re-enable it. **Scope:** `webhooks:manage` — Manage webhook endpoints (create, rotate, test, delete) and replay deliveries. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: webhooks:manage
Parameters
endpoint_id(path, required) — Webhook endpoint id
Request body: WebhookEndpointUpdate
Responses
200The endpoint401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/webhook-endpoints/{endpoint_id}/rotate-secretRotate a webhook signing secret
Returns the new secret once. The previous secret keeps signing for 24 hours (two `v1=` values in the signature header). **Scope:** `webhooks:manage` — Manage webhook endpoints (create, rotate, test, delete) and replay deliveries. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: webhooks:manage
Parameters
endpoint_id(path, required) — Webhook endpoint id
Request body: object
Responses
200The endpoint and its new secret401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support503Feature not configured on this deployment
POST/api/v1/webhook-endpoints/{endpoint_id}/testSend a test event
Queues a signed `webhook.ping` delivery to the endpoint, whatever its event filter. **Scope:** `webhooks:manage` — Manage webhook endpoints (create, rotate, test, delete) and replay deliveries. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: webhooks:manage
Parameters
endpoint_id(path, required) — Webhook endpoint id
Request body: object
Responses
202The queued delivery401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/webhooks/deliveriesList webhook deliveries
The delivery log, newest first; kept 30 days. **Scope:** `webhooks:read` — Read webhook endpoints and the delivery log. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified. **Pagination:** cursor — pass `next_cursor` as `cursor` until it is null.
API-key scopes: webhooks:read
Parameters
endpoint_id(query)status(query)event_type(query)limit(query)cursor(query)
Responses
200A page of deliveries401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/webhooks/deliveries/{delivery_id}Get a webhook delivery
Payload, request/response excerpts (≤ 1 KiB) and status history. **Scope:** `webhooks:read` — Read webhook endpoints and the delivery log. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: webhooks:read
Parameters
delivery_id(path, required) — Webhook delivery id
Responses
200The delivery401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
POST/api/v1/webhooks/deliveries/{delivery_id}/replayReplay a webhook delivery
Queues the delivery again with the same event id and payload and a fresh signature. 409 when it is already queued or the endpoint is disabled. **Scope:** `webhooks:manage` — Manage webhook endpoints (create, rotate, test, delete) and replay deliveries. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 120 writes/min per key, within the 600/min per-key budget.
API-key scopes: webhooks:manage
Parameters
delivery_id(path, required) — Webhook delivery id
Request body: object
Responses
202The re-queued delivery401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)404Not found, or not visible to the key's organisation / agreement restriction (`not_found`)409Conflicting state (specific code, e.g. `certificate_not_ready`)429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
GET/api/v1/webhooks/eventsList webhook event types
**Scope:** `webhooks:read` — Read webhook endpoints and the delivery log. **Auth:** API key (`Authorization: Bearer ce_…`) or a signed-in session. **Rate limit:** 600 requests/min per key (all API-key requests) + 300/min per IP before the key is verified.
API-key scopes: webhooks:read
Responses
200The event catalogue401Missing, invalid, expired or revoked API key (`unauthorized`)403The key lacks the scope (`api_key_scope`) or the role grant (`forbidden`)422Validation failed (`validation_failed`): unknown fields, wrong types or out-of-range values429Rate limited (`rate_limited`); see `Retry-After`500Unexpected error (`internal_error`); quote `x-request-id` to support
Signed in? Manage keys, webhooks and the MCP endpoint under Settings → Developers.