Your first authorization
From an issued key to a signed verdict, and back out through deny, review and revoke — all in the sandbox.
This walks the loop the whole product is built around: write a mandate, ask to spend, read the verdict. Everything runs in the sandbox with the keys issued at onboarding — nothing here settles real money.
You need one thing from your onboarding email: an organization key (sk_sandbox_...). The agent and its key we create now.
Register an agent
curl -X POST https://api.saifuro.com/v1/agents \
-H "Authorization: Bearer sk_sandbox_..." \
-d '{ "name": "procurement_01" }'The response carries agent_key — shown once, never again. In production it goes to the agent's runtime and nowhere else; in the sandbox, export it in your shell and move on. The agent's id is agt_ followed by the name — here agt_procurement_01. If that name is already taken in your org, pick another and use the matching agt_<name> everywhere below.
Write a mandate
Authority first, spending second. Without this step the agent's requests return deny_no_mandate, which you can verify by skipping ahead — nothing bad happens, that is the system working.
curl -X POST https://api.saifuro.com/v1/mandates \
-H "Authorization: Bearer sk_sandbox_..." \
-d '{
"principal": "role:procurement_lead",
"agent": "agt_procurement_01",
"limits": { "per_transaction": 500, "per_month": 5000, "currency": "USD" },
"categories": ["saas"],
"review_above": 250,
"expires": "2026-12-01"
}'Spend inside the limits
Now as the agent, with the agent key:
curl -X POST https://api.saifuro.com/v1/authorizations \
-H "Authorization: Bearer ak_sandbox_..." \
-d '{ "amount": 120.00, "currency": "USD", "recipient": "vendor:acme_saas", "category": "saas" }'Verdict: allow. The response carries a verdict_token — paste it into the check on verifying a verdict and it verifies against the public key set, kid starting with sandbox-. Do it within five minutes: verdicts expire, by design, so check a fresh one.
Break a limit
curl -X POST https://api.saifuro.com/v1/authorizations \
-H "Authorization: Bearer ak_sandbox_..." \
-d '{ "amount": 720.00, "currency": "USD", "recipient": "vendor:acme_saas", "category": "saas" }'Verdict: deny_limit, reason amount 720.00 exceeds per_transaction limit 500.00. Note what did not happen: no error, no exception — a 201 with a denial inside, metered and logged like the approval was. Try a category of travel for deny_category; each of the six denial codes points at a different conversation.
Cross the review threshold
An amount over 250 but under the hard cap comes back review. Ask for one as the agent:
curl -X POST https://api.saifuro.com/v1/authorizations \
-H "Authorization: Bearer ak_sandbox_..." \
-d '{ "amount": 340.00, "currency": "USD", "recipient": "vendor:acme_saas", "category": "saas" }'Verdict: review. The response carries a review object; take its review id (rev_...) and resolve it with the organization key — substitute your own id for rev_3c17:
curl -X POST https://api.saifuro.com/v1/reviews/rev_3c17/approve \
-H "Authorization: Bearer sk_sandbox_..."The approval is a fresh decision — new nonce, its own five-minute expiry, supersedes pointing at the original. The original stays in the log.
Revoke
Use the mandate id returned in step 2 (mnd_...), in place of mnd_7f3a:
curl -X POST https://api.saifuro.com/v1/mandates/mnd_7f3a/revoke \
-H "Authorization: Bearer sk_sandbox_..."The agent's next request returns deny_revoked. The agent's code did not change, and no payment provider was involved. That is the property everything else rests on: authority is data, and data can be taken back.
Where this goes next
In production the same loop runs against real traffic — but it starts in observe mode, where verdicts are recorded and nothing blocks, so the first month costs you no incidents while the policies calibrate. When you want the full picture per field, the API reference is the contract.

