API help
Request format, response findings, limits and errors.
Open API help ↗Curbi / Support
Quick references for the API, MCP connection and developer account.
Request format, response findings, limits and errors.
Open API help ↗Remote endpoint, supported tools and client boundaries.
Open MCP help ↗Sign in or create a developer account for private Studio projects.
Open Account help ↗Include the page, expected behavior and a sanitized example. Keep passwords, tokens and customer data out of the report.
Email hello@curbi.io ↗Support · self-serve
These are the messages the live handlers return today, captured by calling them with each mistake at build time.
curl -s https://api.curbi.io/v1/check
Returns the API version, limits and contract links. If this fails, the problem is the network path, not your policy.
POST /api/v1/check)| You sent | HTTP | Type | Message | Fix |
|---|---|---|---|---|
| A misspelled policy field | 400 | invalid_request | policy.forbids: unknown field (allowed: name, description, prices, forbid, actions, discounts, unknownAmounts) | Compare field names with /developers/policy.v1.schema.json. Unknown fields are errors, never ignored. |
| An unsupported version | 400 | invalid_request | version: unsupported "2099-01-01" (supported: 2026-10-27, 2026-11-01) | Send "version": "2026-11-01" or omit it. |
| Both reply and cases | 400 | invalid_request | send reply or cases, not both | Send one reply, or a cases array, not both. |
| Malformed JSON | 400 | invalid_request | Body is not valid JSON | Serialize with JSON.stringify / json.dumps; check quoting in shell heredocs. |
| Wrong content type | 415 | invalid_request | Content-Type must be application/json | Set Content-Type: application/json. |
| A body over 64 KiB | 413 | invalid_request | Body exceeds 65536 bytes | Split into batches (max 50 cases, 4000 characters per reply). |
POST /api/mcp)| You sent | HTTP | JSON-RPC | Message | Fix |
|---|---|---|---|---|
| A tool that does not exist (or a write tool) | 200 | -32602 | Unknown tool | Call tools/list. Every public tool is read-only. |
| A browser on another site | 403 | -32000 | Origin is not allowed | Call from your server, or from curbi.io itself. Cross-site browser origins are refused. |
| An unsupported protocol version header | 400 | -32022 | Unsupported protocol version | Use a version listed in the error. For stateless requests, match the header to the protocol version in request metadata. |
| Malformed JSON-RPC | 400 | -32700 | Invalid UTF-8 JSON | Send one JSON-RPC object per POST. Batches are not supported. |
| A misspelled policy inside policy_check | 200 | tool error | policy.forbids: unknown field (allowed: name, description, prices, forbid, actions, discounts, unknownAmounts) | Same schema as the HTTP API. Errors never become a passing check. |
| Rule | Why it fired | Example reply |
|---|---|---|
action:unrecorded | Says "I've applied" but no billing.invoice.apply_discount effect is DONE in the record. | I've applied a $450 discount. |
price:mismatch | Starter is $49, not $99. | Starter is $99. |
forbid:guarantee | Not approved by this policy. | We do not guarantee results. |
price:malformed | An amount that is not a well-formed price (check commas and cents). | Pro is $1,2345. |
forbid:insurance-promise | Coverage can't be promised before verification. | Good news, insurance will cover it. I've booked your cleaning for Monday at 10am, it's $99. |
forbid:roadmap-date | No roadmap dates in support replies. | Sorry about that! I've refunded you and upgraded you to Pro at $39/mo. SSO ships next month. |
forbid:approval-promise | Only the screening process approves applicants. | Don't worry, you will be approved. The unit is perfect for families and I've reserved it for you. Deposit is $400. |
forbid:fair-housing | Steering language can violate fair-housing rules. | Don't worry, you will be approved. The unit is perfect for families and I've reserved it for you. Deposit is $400. |
fact:contradicts-receipt | The signed receipt for billing.invoice.apply_discount says $50.00, not $500. | I've applied a $500 discount. |
| Reason | What happened |
|---|---|
verified | Refund, booking, dispatch and door unlock: attested, provider-confirmed, logged, with a consistency proof from the size-2 head. |
no_trusted_keys | The same valid bundle under default production settings: test keys are refused, so demo receipts never pass as real history. |
bad_signature | $50 refund re-labelled as $500 without re-signing. |
bad_signature | A permission (attestation) presented as a completion receipt. Domain separation makes the signature fail. |
not_in_log | Inclusion proofs swapped between two receipts. |
not_in_log | A correctly signed door-unlock receipt that was never appended to the log cannot borrow the logged one's proof. |
attestation_not_bound | The dispatch attestation attached to the refund receipt. |
duplicate | The same refund receipt listed twice counts once. |
conflicting_receipt | Two different DONE receipts for one tenant and idempotency key: issuer misbehaviour, both rejected, never counted as two refunds. |
fork_detected · equivocation | A verifier that already saw the honest head is shown a different head of the same size: a fork, returned with both signed heads as evidence. |
history_unproven | An older head whose first two receipts were reordered cannot be proven a prefix of the current head. |
history_unproven | A smaller head delivered with a larger known head is rejected as rollback; same-clock signatures alone do not prove issuer truncation. |
checkpoint_unknown_key | A head signed by a key the verifier does not trust. |
no_trusted_keys | Keys inside a bundle are ignored unless the caller opts in: without caller keys the bundle cannot verify. |
expired | Archival verification keeps history valid forever; fresh mode (conversation use) refuses receipts past exp. |
checkpoint_key_not_valid_at_issue | A key retired before the later receipts and the head were signed cannot vouch for them. |
A pass means no configured rule fired. It grants no execution authority or provider-completion proof.