CurbiCurbi
Developers · Check API v1 Public access

Check what your agent is about to claim.

One HTTP call checks an AI reply against your rules and against the records you supply. Public rate-limited checks require no SDK or API key. The checker does not persist request bodies.

QuickstartOpen the playground

Quickstart

Send a policy and a reply. You get a verdict and the exact spans that broke a rule.

# 1. check one reply
curl https://api.curbi.io/v1/check \
  -H 'Content-Type: application/json' \
  -d '{
  "policy": {
    "prices":  [{"item": "Pro", "amounts": [49, 490]}],
    "forbid":  [{"id": "refund-promise", "phrases": ["full refund"]}],
    "actions": {"refunded": "billing.refund"}
  },
  "reply": "I have refunded you, and Pro is $39/mo."
}'
// 2. response
{
  "object": "check",
  "pass": false,
  "findings": [
    { "rule": "action:unrecorded", "text": "I have refunded",
      "why": "Says \"I have refunded\" but no billing.refund effect is DONE in the record." },
    { "rule": "price:mismatch", "text": "$39", "why": "Pro is $49 or $490, not $39." }
  ]
}

Use the findings in your app

const r = await fetch('https://api.curbi.io/v1/check', {
  method: 'POST', headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ policy, reply: draft, record: { effects } })
}).then(r => r.json());
if (!r.pass) return escalateToHuman(draft, r.findings); // never send a failing reply
await send(draft);

How it works

Curbi compares the reply with two inputs you supply:

Policy

What your business has approved: prices per item, phrases it never says, and which verbs map to which operations.

Record

Outcomes and facts your system supplies, such as a completed booking or refund. A matching DONE record can satisfy a configured action rule; it is not independent provider evidence.

The public checker does not query providers or a hosted ledger. Supply the records your system chose to rely on, and review their source separately.

Check the reply after a write

AI agent quickstart · Capability manifest · Signed amount lab

A tool call, permission to run it, and a completed action are different records. Curbi’s Check API evaluates the reply against your policy and supplied outcome; it does not authorize or execute a database write.

Example: a discount request without a grant should stop in your execution layer. A permitted write that fails still cannot support “I applied the discount.” A supplied DONE record can satisfy the configured action rule, but is not independent provider proof. The homepage replays these three synthetic cases with the real checker.

Put authorization before the provider call. Check the reply after the outcome. Keep your provider’s evidence and bind it to the operation, arguments, tenant and request before treating it as trusted completion.

Canonical public origin: https://api.curbi.io, with /v1/check and /mcp. Existing https://curbi.io/api/ routes remain compatible; account and project routes stay on the website origin.

Contract & versions

The API is described by an OpenAPI 3.1 contract and a JSON Schema for policies. The release tests compare the published schemas with the runtime on a labeled corpus. The contract test runner also exercises this page’s request examples against the Worker.

Rule
versionOptional. Current: 2026-11-01. Also accepted: 2026-10-27. Anything else is a 400, never silently treated as current. Every response says which version produced it.
Unknown fieldsRejected with 400 and the field's path, so a typo like forbids can't turn a check green.
Same answer everywhereThe playground and the API run the same checker; findings and spans match for the same input. Only the API verifies receipts.
UnicodeText is matched after removing invisible characters, folding compatibility forms ($ → $) and common look-alike letters. Spans always point into your original text.

Batch

Batch mode: submit multiple cases per request against one policy (see Limits & errors for the current cap). The batch passes only if every case passes.

curl https://api.curbi.io/v1/check \
  -H 'Content-Type: application/json' \
  -d '{
  "version": "2026-11-01",
  "policy": { "prices": [{"item": "Pro", "amounts": [49, 490]}] },
  "cases": [
    {"id": "a", "reply": "Pro is $49 a month."},
    {"id": "b", "reply": "Pro is $59 a month."}
  ]
}'
// response (abridged)
{
  "object": "check.batch", "version": "2026-11-01", "pass": false,
  "results": [
    { "id": "a", "pass": true, "findings": [] },
    { "id": "b", "pass": false, "findings": [
      { "rule": "price:mismatch", "span": [7, 10], "text": "$59" } ] }
  ]
}

Render text and why as text, never as HTML: they can contain whatever the reply or policy contained.

POST /v1/check

Body is JSON, either one reply or a batch. GET on the same path returns version and limits.

FieldType
policyobjectRequired. See Policy object.
replystringOne reply, up to 4,000 characters.
recordobjectOptional, unverified outcomes supplied by the caller. See Records.
casesarrayBatch instead of reply: up to 50 of {id?, reply, record?}. Returns object: "check.batch" with results[] and an overall pass.

Policy object

FieldShape
prices[]{item, amount | amounts[], aliases?[]}A dollar figure in a sentence is bound to the item named before it (or after it if none precedes). Plurals match.
forbid[]{id, phrases[], why?, negatable?, match?}Literal phrases, case-insensitive. The default match: "word" includes simple English inflections. Opt in to match: "prefix" to match a stem such as garanti in garantili. It can also match unrelated words that start with that stem. Turkish negation is outside the built-in negation rules. A negation word within four words before the phrase, in the same clause, passes it unless negatable: false. "Not only" and "no doubt" are not negations.
actions{verb: operation}e.g. "booked": "calendar.book". Matches "I booked", "we've booked", "I had booked", and passive claims like "you're booked" or "it's been booked".
unknownAmounts"flag" | "allow"Default flag: a dollar figure tied to no item fails.

Phrases are never interpreted as regular expressions, so a policy can't slow the checker down.

Records

Trust boundary: records are caller-supplied and unverified. The API does not query providers or the Curbi ledger, authenticate events, or bind an event to a customer. recordTrust is caller_supplied_unverified. A passing result means no configured rule matched; it is not permission to send, spend or execute.

{
  "effects": [{ "operation": "calendar.book", "status": "DONE" }],
  "facts":   [{ "near": "booked", "kind": "time", "value": "9am" }]
}

Only DONE counts. PENDING, FAILED and UNKNOWN do not, because an agent must not tell a customer something happened when the system doesn't know that it did. Fact kinds: time, date, money. The first value of that kind within 80 characters after near must equal value.

Signed receipts

Effects you list yourself are labelled caller_supplied_unverified. The API can authenticate an Ed25519-signed operation statement from a configured key. Production action signing is not connected yet. A signature authenticates the signer’s statement; it does not independently verify a booking, amount, time or customer.

{
  "verify": "required",                 // claims need a signed receipt
  "reply": "I've booked you for Monday.",
  "record": {
    "subject": "conv_9",               // the conversation the receipts belong to
    "receipts": ["curbi1.eyJ2IjoxLC…"]
  }
}
CheckRejected as
Signature by a published Curbi keyunknown_key, bad_signature
Receipt subject matches the supplied subject (caller identity is not authenticated)wrong_subject
Inside its validity window (at most 7 days)expired_or_not_yet_valid
Duplicates within one requestduplicate

Responses report recordTrust: curbi_signed, mixed or caller_supplied_unverified, plus a per-receipt list of rejections. Public keys are at /.well-known/curbi-receipt-keys.json, so anyone can verify a receipt offline. Check key validity and revocation under your trust policy. Production signing is off. The separate offline verifier checks bundles using explicitly trusted keys.

Scope: receipts sign operation, subject and effect key—not action arguments or booking details. Caller-provided facts remain unverified and produce mixed trust. The same receipt can be checked again until expiry; this API has no cross-request replay ledger and grants no authority.

Findings

RuleMeaning
price:mismatchAmount differs from every approved amount for that item.
price:unattributedDollar figure not tied to any item.
price:malformedAn amount like $1,2345 or $19.999. Recognized forms: $39, -$39, US$39, USD 39, 39 dollars.
forbid:<id>Used a forbidden phrase without negating it.
action:unrecordedClaimed an action with no DONE effect for its operation.
fact:contradicts-receiptA claim backed by a signed completion receipt states a different amount than the receipt signs (e.g. says $500, receipt says $50.00).
fact:contradicts-recordStated a time, date or amount that the record contradicts.

Each finding has rule, span (character offsets), text and why.

Limits & errors

POST requests have a best-effort burst guard of 30 per IP per minute within one Worker isolate. This is not a global quota; requests may reach different isolates. HTTP 429 includes Retry-After: 60. Managed access has separate per-key and shared-customer usage ceilings.

Body ≤ 64 KB · reply ≤ 4,000 chars · 50 cases · 50 prices · 50 forbid rules × 20 phrases · 40 actions · 100 effects · 20 facts.

StatusWhen
400Invalid JSON or shape. error.message names the field.
405Method other than GET, POST, OPTIONS.
413Body over 64 KB.
415Content-Type is not application/json.

Public access: no API key or SLA. The checker does not store submitted replies. Responses are Cache-Control: no-store. The interface may change before v1 is final; changes will be listed below.

Platform catalog

GET /api/platform returns Curbi's public ontology (People, Work, Tools, Agents, Evidence…) as read-only JSON with an ETag. It is a guide, not a permission source.

Changelog

DateChange
2026-10-27API version 2026-11-01: OpenAPI 3.1 contract and policy JSON Schema; optional version field; unknown fields and versions return 400; Unicode normalization; USD/dollars/negative amounts and price:malformed; passive action claims; tighter negation. Checks that passed before may now find issues they used to miss.
2026-10-27Signed effect receipts (Ed25519), verify: "required", /.well-known/curbi-receipt-keys.json, site-wide security headers.
2026-10-27Check API v1: policy + record checks, batch mode, literal-phrase policies, /check/ playground with four industry templates.
2026-10-27Platform catalog at /api/platform.
2026-10Claim Check on the homepage (Curbi's own wording rules, runs in the browser).

Rules, with their boundaries.

no action without grant Preview

Every mutation is checked against an exact grant (agent, tenant, operation, resource, amount limit) before it is signed for execution.

Prevention applies only where the target system runs the verifier.

no spend without budget Preview

An unknown or negative cost blocks signing, and an estimate over the remaining budget is refused.

Budget bounds run cost; savings are the customer's math.

no claim without done Live checker

A reply that says "I booked" or "you're refunded" fails unless the record you supply shows that operation DONE.

Records are supplied by the caller and unverified unless they are Curbi-signed receipts. Curbi does not query providers.

no receipt without signature Live checker

Curbi publishes receipt verification keys and their validity windows. Production signing is off. Unsigned caller records remain labeled caller_supplied_unverified.

Proof covers operation and subject (and amount for completion receipts), not facts or authorization.

no history rewrites Preview

The control-plane ledger design is append-only: database triggers reject updates and deletes.

Describes the schema and its tests, not a third-party audit; the reference attestation ledger is in-memory.

no self grading Live checker

The checker is deterministic and uses no model inference: the same input always gives the same findings.

Rule-based: it catches what the policy describes. A pass is not authorization to act.

YOUR NEXT ACTION

Catch one reply. Keep the failure.

Copy this command into a terminal. No signup or key. It sends a synthetic reply and policy to Curbi and returns three findings: action:unrecorded, price:mismatch and forbid:guarantee.

Copy and run
curl -fsS https://api.curbi.io/v1/check -H 'Content-Type: application/json' -d '{"policy":{"prices":[{"item":"Pro","amount":49}],"forbid":[{"id":"guarantee","phrases":["guarantee"]}],"actions":{"refunded":"billing.refund"}},"reply":"Done! I'\''ve refunded you, Pro is just $29, and we guarantee it."}'

What next: edit the policy in your sandbox, then export a runnable regression from the MCP console. Connect your framework → · All commands as JSON →

A pass means no configured rule fired. It grants no execution authority or provider-completion proof. public access; production signing is off.