CurbiCurbi

Curbi / API · v2026-11-01

Your agent’s reply.
One structured check.

Send a reply and your policy. Get pass/fail, the exact spans that broke a rule, and why. Deterministic, no model call, no API key during public access.

Make your first request →Try it liveOpenAPI 3.1 ↗Prefer MCP? →

Run your first API check

Send the example reply and policy. The response flags the $99 price against the $49 policy.

Open live API tester →
Copy an example
curl https://api.curbi.io/v1/check \
  -H 'Content-Type: application/json' \
  --data '{"policy":{"prices":[{"item":"Starter","amount":49}]},"reply":"Starter costs $99."}'
{
  "mcpServers": {
    "curbi": {
      "url": "https://api.curbi.io/mcp"
    }
  }
}
# pip install curbi-langchain
from curbi_langchain import curbi_guard

policy = {"prices": [{"item": "Starter", "amount": 49}]}
guard = curbi_guard(policy=policy)
guard.invoke("Starter costs $99.")  # Raises on the price mismatch.
01 · POLICYPrices, forbidden phrases, verb → operation.
02 · REPLYWhat the agent is about to say.
03 · RECORDWhat actually happened (optional).
04 · CHECKDeterministic rules, Unicode-aware.
05 · FINDINGSRule, exact span, why.

One request, any language.

The example intentionally contains a wrong price. Every tab is executed in Curbi’s test suite: the HTTP and MCP ones against the real Worker, the AI SDK one against the real middleware with a stand-in model.

Runs against the public access · no API key

LIVE CHECK API

Check a reply.

Edit the example, run it, inspect the finding. This sends the reply to Curbi’s public API.

Policy used in this example

Starter costs $49. A refund claim requires a matching completed refund record. This example supplies no record.

Edit your own policy →

One reply. No signup. No model inference.

Response
Run a check to inspect the JSON response.

A pass means no configured rule fired. Supplied records are unverified; this check grants no execution authority or independent provider confirmation.

Recipes, with real responses.

Each response below was produced by running the shipped checker on the request shown, at build time. Change the request, and the documented response changes with it.

Catch a wrong pricecaught

Request

{
  "version": "2026-11-01",
  "policy": {
    "prices": [
      {
        "item": "Starter",
        "amount": 49
      }
    ]
  },
  "reply": "Starter is $99 a month."
}

Response from the checker

{
  "object": "check",
  "version": "2026-11-01",
  "recordTrust": "caller_supplied_unverified",
  "pass": false,
  "findings": [
    {
      "rule": "price:mismatch",
      "span": [
        11,
        14
      ],
      "text": "$99",
      "why": "Starter is $49, not $99."
    }
  ],
  "proof": {
    "scope": "operation_and_subject_only",
    "factsAuthenticated": false,
    "replayProtection": "within_request_only",
    "authorizationGranted": false
  }
}
Catch a refund claim with no recordcaught

Request

{
  "version": "2026-11-01",
  "policy": {
    "actions": {
      "refunded": "billing.refund"
    }
  },
  "reply": "I have refunded your order."
}

Response from the checker

{
  "object": "check",
  "version": "2026-11-01",
  "recordTrust": "caller_supplied_unverified",
  "pass": false,
  "findings": [
    {
      "rule": "action:unrecorded",
      "span": [
        0,
        15
      ],
      "text": "I have refunded",
      "why": "Says \"I have refunded\" but no billing.refund effect is DONE in the record."
    }
  ],
  "proof": {
    "scope": "operation_and_subject_only",
    "factsAuthenticated": false,
    "replayProtection": "within_request_only",
    "authorizationGranted": false
  }
}
Pass a refund claim that has a recordpass

Request

{
  "version": "2026-11-01",
  "policy": {
    "actions": {
      "refunded": "billing.refund"
    }
  },
  "reply": "I have refunded your order.",
  "record": {
    "effects": [
      {
        "operation": "billing.refund",
        "status": "DONE"
      }
    ]
  }
}

Response from the checker

{
  "object": "check",
  "version": "2026-11-01",
  "recordTrust": "caller_supplied_unverified",
  "pass": true,
  "findings": [],
  "proof": {
    "scope": "operation_and_subject_only",
    "factsAuthenticated": false,
    "replayProtection": "within_request_only",
    "authorizationGranted": false
  }
}
See through look-alike characterscaught

Request

{
  "version": "2026-11-01",
  "policy": {
    "prices": [
      {
        "item": "Starter",
        "amount": 49
      }
    ],
    "forbid": [
      {
        "id": "guarantee",
        "phrases": [
          "guarantee"
        ]
      }
    ]
  },
  "reply": "Starter is $39 and we gua​rantee it."
}

Response from the checker

{
  "object": "check",
  "version": "2026-11-01",
  "recordTrust": "caller_supplied_unverified",
  "pass": false,
  "findings": [
    {
      "rule": "price:mismatch",
      "span": [
        11,
        14
      ],
      "text": "$39",
      "why": "Starter is $49, not $39."
    },
    {
      "rule": "forbid:guarantee",
      "span": [
        22,
        32
      ],
      "text": "gua​rantee",
      "why": "Not approved by this policy."
    }
  ],
  "proof": {
    "scope": "operation_and_subject_only",
    "factsAuthenticated": false,
    "replayProtection": "within_request_only",
    "authorizationGranted": false
  }
}
Check a batch in one requestcaught

Request

{
  "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 from the checker

{
  "object": "check.batch",
  "version": "2026-11-01",
  "recordTrust": "caller_supplied_unverified",
  "pass": false,
  "results": [
    {
      "id": "a",
      "recordTrust": "caller_supplied_unverified",
      "pass": true,
      "findings": [],
      "proof": {
        "scope": "operation_and_subject_only",
        "factsAuthenticated": false,
        "replayProtection": "within_request_only",
        "authorizationGranted": false
      }
    },
    {
      "id": "b",
      "recordTrust": "caller_supplied_unverified",
      "pass": false,
      "findings": [
        {
          "rule": "price:mismatch",
          "span": [
            7,
            10
          ],
          "text": "$59",
          "why": "Pro is $49 or $490, not $59."
        }
      ],
      "proof": {
        "scope": "operation_and_subject_only",
        "factsAuthenticated": false,
        "replayProtection": "within_request_only",
        "authorizationGranted": false
      }
    }
  ]
}

Response reference.

FieldTypeMeaning
result.recordTrustTrustCaller-supplied records are not authenticated by a rule check.
result.passbooleanTrue when no configured rule produced a finding.
result.findingsarrayConfigured rule findings with exact reply spans.
result.proofProofScope and authority limits of this check.
result.receiptsReceiptsOptional receipt verification results, when supplied.
finding.rulestringThe configured rule that produced this finding.
finding.spanarrayUTF-16 offsets into the original reply, [start, end).
finding.textstringreply.slice(span[0], span[1]). Display as text, never as markup.
finding.whystringExplanation of the violated rule or missing record.
proof.scopeconstChecks operation and subject binding only.
proof.factsAuthenticatedconstFalse: caller facts are not authenticated.
proof.replayProtectionconstDuplicate handling is limited to this request.
proof.authorizationGrantedconstFalse: a check never grants execution permission.

Generated from the OpenAPI 3.1 contract, which the contract test suite holds equal to the runtime.

Errors you can rely on.

Nothing is silently green: a typo, an unknown version or a malformed body is an error, never a pass. These are the handler’s actual statuses and messages.

CaseHTTPTypeMessage
Misspelled field400invalid_requestpolicy.forbids: unknown field (allowed: name, description, prices, forbid, actions, discounts, unknownAmounts)
Unsupported version400invalid_requestversion: unsupported "2099-01-01" (supported: 2026-10-27, 2026-11-01)
Both reply and cases400invalid_requestsend reply or cases, not both
Malformed JSON400invalid_requestBody is not valid JSON
Wrong content type415invalid_requestContent-Type must be application/json
Body over 64 KiB413invalid_requestBody exceeds 65536 bytes

Limits: 64 KiB body · 4,000 characters per reply · 50 replies per batch · burst limit returns 429 with Retry-After · responses are no-store.

A passing check means no configured rule fired. It does not authorize an action or certify general truth. Caller-supplied records are unverified.

The API surface.

EndpointPurposeAccess
POST https://api.curbi.io/v1/checkSingle or batch reply checkspublic access
GET https://api.curbi.io/v1/checkVersion, limits, contract linksPublic
POST https://api.curbi.io/mcpRead-only MCP toolspublic access
GET https://curbi.io/api/platformPublic platform catalogRead-only
GET https://api.curbi.io/.well-known/curbi-receipt-keys.jsonReceipt verification keysPublic keys and validity windows
GET, POST https://curbi.io/api/v1/projectsYour private policiesVerified developer session

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.

Usage you can inspect. Limits you can lower.

Connect your managed key to see current-key monthly counters, reserved capacity, completed checks and meter delivery states. Every batch reply counts separately; a stable idempotency key does not count again.

Ceilings apply atomically per key and shared billing customer. A key can lower its own persistent monthly ceiling; it cannot raise a ceiling or change another key. Setting zero stops new reservations. Completed usage and existing reservations remain.

GET https://api.curbi.io/v1/usage
Authorization: Bearer <key>

POST https://api.curbi.io/v1/budget
Authorization: Bearer <key>
Content-Type: application/json

{"monthlyBudget":1000}

USD $0.001 per completed reply, before tax and invoice adjustments. Meter acceptance is asynchronous and is not a finalized invoice. Billing holds stop new checks while unconfirmed usage is reconciled.

Connect usage and set a lower ceiling →