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.
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.
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." } ] }
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);
Curbi compares the reply with two inputs you supply:
What your business has approved: prices per item, phrases it never says, and which verbs map to which operations.
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.
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.
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 | |
|---|---|
version | Optional. 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 fields | Rejected with 400 and the field's path, so a typo like forbids can't turn a check green. |
| Same answer everywhere | The playground and the API run the same checker; findings and spans match for the same input. Only the API verifies receipts. |
| Unicode | Text is matched after removing invisible characters, folding compatibility forms ($ → $) and common look-alike letters. Spans always point into your original text. |
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/checkBody is JSON, either one reply or a batch. GET on the same path returns version and limits.
| Field | Type | |
|---|---|---|
policy | object | Required. See Policy object. |
reply | string | One reply, up to 4,000 characters. |
record | object | Optional, unverified outcomes supplied by the caller. See Records. |
cases | array | Batch instead of reply: up to 50 of {id?, reply, record?}. Returns object: "check.batch" with results[] and an overall pass. |
| Field | Shape | |
|---|---|---|
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.
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.
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…"]
}
}| Check | Rejected as |
|---|---|
| Signature by a published Curbi key | unknown_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 request | duplicate |
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.
| Rule | Meaning |
|---|---|
price:mismatch | Amount differs from every approved amount for that item. |
price:unattributed | Dollar figure not tied to any item. |
price:malformed | An 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:unrecorded | Claimed an action with no DONE effect for its operation. |
fact:contradicts-receipt | A 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-record | Stated a time, date or amount that the record contradicts. |
Each finding has rule, span (character offsets), text and why.
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.
| Status | When |
|---|---|
400 | Invalid JSON or shape. error.message names the field. |
405 | Method other than GET, POST, OPTIONS. |
413 | Body over 64 KB. |
415 | Content-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.
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.
| Date | Change |
|---|---|
| 2026-10-27 | API 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-27 | Signed effect receipts (Ed25519), verify: "required", /.well-known/curbi-receipt-keys.json, site-wide security headers. |
| 2026-10-27 | Check API v1: policy + record checks, batch mode, literal-phrase policies, /check/ playground with four industry templates. |
| 2026-10-27 | Platform catalog at /api/platform. |
| 2026-10 | Claim Check on the homepage (Curbi's own wording rules, runs in the browser). |
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.
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.
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.
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.
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.
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
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.
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.