Learn the control plane first
Attestify OS is the hosted x402-paid agent spend router and execution control plane on Base. The main builder path is POST /api/run, which routes work, applies budgets and policies, executes, settles payment, stores a receipt, and returns pricing, evidence, verification, and settlement metadata.
These docs start with concepts so builders understand the system correctly, then provide a compact endpoint index for direct implementation work.
POST /api/run. Lower-level surfaces like POST /api/loop are useful, but they are not the main first-run integration path.Router-first execution
The core Attestify OS mental model: send a task, let the router select or confirm the agent lane, evaluate governance, execute the work, settle payment, and return structured evidence around what happened.
POST /api/run
Content-Type: application/json
X-API-Key: atst_your_key_here
{
"session_id": "docs-page-001",
"task": "Research the latest x402 adoption trends and summarize the major patterns.",
"preferred_agent_id": "researcher-v2",
"budget": {
"budget_id": "budget_research_monthly",
"max_price_usdc": 0.03,
"soft_max_price_usdc": 0.025,
"strict": true,
"currency": "USDC"
},
"policy": {
"policy_ids": ["policy_research_evidence", "policy_default_governance"],
"mode": "default"
},
"options": {
"include_memory": true,
"write_memory": true,
"verify": true
},
"idempotency_key": "idem-docs-page-001"
}Complete POST /api/run response
Every successful run returns a single JSON object. All top-level keys are shown below — destructure exactly what you need.
HTTP 200 OK
{
"status": "success", // "success" | "error"
"loop_id": "loop1715000000000abcdef12", // primary run identifier
"run_id": "run_9kXm2pQvZ", // shorter alias; same run
"lane_id": "researcher-v2", // lane that executed the task
"result": {
"output": "…model response text…",
"model": "gpt-4o",
"tokens_used": 812
},
"pricing": {
"price_usdc": 0.030,
"base_lane_price_usdc": 0.025,
"orchestration_price_usdc": 0.005,
"pricing_version": "2026-05-tiered-v1",
"cost_model": "2026-05-cost-v1"
},
"routing": {
"selected_lane": "researcher-v2",
"routing_version": "2026-05-routing-v1",
"confidence": 0.94,
"fallback_used": false
},
"budget_outcome": {
"outcome": "approved", // "approved" | "warning" | "downgrade" | "blocked" | "rejected"
"budget_id": "budget_research_monthly",
"evaluated_at": "2026-06-18T11:20:00Z"
},
"policy_applied": {
"policies_matched": ["policy_research_evidence"],
"mode": "default",
"outcome": "approved"
},
"verification": {
"verified": true,
"score": 0.82,
"grade": "B"
},
"evidence": {
"version": "2026-05-evidence-v1",
"items": [
{ "kind": "routing", "source": "router" },
{ "kind": "pricing", "source": "pricing-engine" },
{ "kind": "budget", "source": "budget-record" },
{ "kind": "policy", "source": "policy-engine" },
{ "kind": "verification", "source": "heuristic-verifier" },
{ "kind": "settlement", "source": "x402" }
]
},
"memory": {
"read": true,
"written": true,
"session_id": "docs-page-001"
},
"receipt_url": "/receipts/loop1715000000000abcdef12",
"settlement": {
"success": true,
"network": "eip155:8453",
"tx_hash": "0xabc123…",
"paid": true,
"simulated": false
},
"idempotency_key": "idem-docs-page-001"
}Pricing and paid runs
The final charged amount is the selected lane base price plus the orchestration fee. The definitive charged value appears in the returned pricing block from a paid run.
paid_run_price_usdc = base_lane_price_usdc + orchestration_price_usdc Example: researcher-v2 = 0.025 + 0.005 = 0.030 USDC
{
"pricing": {
"price_usdc": 0.03,
"base_lane_price_usdc": 0.025,
"orchestration_price_usdc": 0.005,
"pricing_version": "2026-05-tiered-v1",
"cost_model": "2026-05-cost-v1"
}
}Budgets and policies
Governance is part of the execution flow. Budget and policy inputs can allow, warn, downgrade, or block execution, reflected in budget_outcome and policy_applied.
Receipts, evidence, and verification
A completed run is auditable. Attestify returns a receipt reference, structured evidence items, verification signals, and settlement details.
{
"receipt_url": "/receipts/loop1715000000000abcdef12",
"evidence": {
"version": "2026-05-evidence-v1",
"items": [
{ "kind": "routing", "source": "router" },
{ "kind": "pricing", "source": "pricing-engine" },
{ "kind": "budget", "source": "budget-record" },
{ "kind": "policy", "source": "policy-engine" },
{ "kind": "verification", "source": "heuristic-verifier" },
{ "kind": "settlement", "source": "x402" }
]
},
"verification": { "verified": true, "score": 0.82, "grade": "B" },
"settlement": { "success": true, "network": "eip155:8453" }
}Error code reference
Every error response follows the shape {"error": "…", "code": "…", "status": 4xx}. Handle by code, not by message string.
| HTTP | code | Meaning | Recovery |
|---|---|---|---|
| 400 | invalid_request | Malformed JSON or missing required field (task, lane_id, etc.) | Fix the request payload. Check required fields against the schema above. |
| 401 | unauthorized | Missing or invalid X-API-Key header. | Include a valid atst_ key. Provision one at /onboarding. |
| 402 | payment_required | x402 challenge — payment metadata returned, no run executed. | Submit a paid POST with the X-PAYMENT header. See x402 flow. |
| 403 | forbidden | Key exists but lacks permission for this endpoint or lane. | Upgrade your plan or check lane access for your key tier. |
| 404 | not_found | Lane ID, receipt ID, budget ID, or policy ID does not exist. | Verify the ID with GET /api/lanes, /api/receipts, or /api/budgets. |
| 409 | idempotency_conflict | A run with this idempotency_key already completed with different params. | Use a new idempotency_key, or omit it to allow a fresh run. |
| 422 | budget_blocked | Budget governance blocked the run before execution. | Raise max_price_usdc, set strict: false, or adjust the budget record. |
| 422 | policy_blocked | A matched policy blocked the run. | Review policy_ids in the request, or update the policy via /api/policies. |
| 429 | rate_limited | Too many requests. Per-key rate limit exceeded. | Back off exponentially. Default limit: 60 req/min per key. |
| 500 | execution_error | Lane execution failed after payment was accepted. | Retry with the same idempotency_key — the run will not be double-charged. |
| 502 | upstream_error | Upstream model or provider returned an error. | Retry after a short delay. Check /api/health for lane status. |
| 503 | lane_unavailable | The requested lane is temporarily offline. | Use the router (omit preferred_agent_id) to fall back to an available lane. |
Endpoint index
Once the model is clear, these are the main public surfaces to implement against.
Execution
Financial control plane
Routing and discovery
Memory
x-api-key header and call it from curl, fetch(), LangChain, CrewAI, AutoGen, LlamaIndex, or any HTTP client — see /onboarding for runtime-specific snippets. Native MCP server support is not live yet.Proof of what an agent did
A separate control plane from everything above: register an agent's identity, have it sign evidence about work it did, and get back a verifiable receipt. It doesn't require the work to have run through POST /api/runat all — an agent running entirely on someone else's infrastructure can still register and submit evidence here.
Free on every plan, including Free itself — 1,000 receipts/month, no card. See /trust for the full pitch and /plans for receipt quotas by tier.
Register, sign, verify — the whole loop
The keypair is generated client-side and never leaves your process — Attestify only ever sees the public key.
import { createClient } from 'attestify-os-sdk';
const attestify = createClient({ apiKey: process.env.ATTESTIFY_API_KEY! });
// 1. Generate a keypair once — the private key never leaves your process.
const { publicKey, privateKey } = attestify.trust.generateKeyPair();
// 2. Register the agent and its key. Free. No card.
const agent = await attestify.trust.createAgent({ displayName: 'Invoice Bot' });
await attestify.trust.registerKey(agent.id, publicKey);
// 3. Sign and submit evidence for real work the agent did.
const receipt = await attestify.trust.submitEvidence({
agentId: agent.id,
schema: 'work-completion/v1',
payload: { summary: 'Extracted 3 line items from a sample invoice' },
}, privateKey);
// 4. Anyone can verify it — no API key required.
const result = await attestify.trust.verify(receipt.id);
console.log(result.integrity_verified); // truePython SDK mirrors this exactly (snake_case). Raw HTTPS also works — see the endpoint index below; every call is a plain POST/GET with an x-api-key header, same as the Router endpoints above.
Running in CI — no SDK code required
If the agent submitting evidence runs as a GitHub Action — a coding agent, a PR bot, a scheduled job — attestifyagent/trust-actionwraps the same register/sign/submit loop above as a drop-in workflow step. Registration and key generation happen once, locally, via the SDK's own CLI — never inside CI, since a workflow's default token can't write repo secrets and a log isn't a safe place to hold a private key.
# One-time, on your own machine:
npx attestify trust-init --repo your-org/your-repo
# In the workflow:
- uses: attestifyagent/trust-action@v1
with:
api-key: ${{ secrets.TRUST_API_KEY }}
agent-id: ${{ secrets.TRUST_AGENT_ID }}
private-key: ${{ secrets.TRUST_PRIVATE_KEY }}Every run signs a ci-run/v1evidence event and posts the receipt as a check on the commit — see the repo's README for the exact schema and what it does (and doesn't) attest to.
Delegation — agent-to-agent, with terms
One agent can grant another agent a bounded mandate — an allow-list of actions, usage caps, an expiry — as a signed request the receiving agent accepts, declines, or counters. Evidence submitted against an active delegation (scopeRef) is checked server-side against those exact terms; the result is a policy-governed L4 receipt, and a deliberate overstep still issues a receipt with within_parameters: false rather than being silently dropped.
// Agent A asks Agent B for scoped access.
const delegation = await attestify.trust.requestDelegation({
fromAgentId: agentA.id,
toAgentId: agentB.id,
intentCode: 'data-extraction.invoice-processing',
grantedParameters: {
allowedActions: ['read_invoice', 'extract_line_items'],
constraints: { max_documents: 50 },
expiresAt: '2026-09-01T00:00:00Z',
},
}, agentAPrivateKey);
// Agent B accepts, declines, or counters.
await attestify.trust.respondToDelegation(
delegation.id, agentB.id, { decision: 'accept' }, agentBPrivateKey,
);
// Agent B's later evidence, scoped to the grant:
await attestify.trust.submitEvidence({
agentId: agentB.id,
schema: 'work-completion/v1',
payload: { declared_action: 'extract_line_items', declared_usage: { documents: 1 } },
scopeRef: delegation.id,
}, agentBPrivateKey);Endpoint index
All under /api/trust/v1.
Agents & keys
Evidence & receipts
Delegations
Public — no API key required
Authenticated — requires x-api-key
Metered access to attested agent data
Earn licenses the aggregate of what agents do — built from the same signed evidence that Trust produces — back to agents that need it, priced per call in USDC over x402. One endpoint, POST /api/earn/query, with a type field selecting the query.
Preview status: the contract below is stable and safe to build against. Results are illustrative and no call is billed yet — a request carrying a payment header returns a preview: true payload with header X-Earn-Preview: true and is never settled. Billing and live results activate together once the corpus is production-ready. Reserve a place for your agent at /earn.
The call
GET the endpoint with no body for a self-describing contract — every query type, its price, and its input shape. To run a query, POST with a type and its inputs. With no payment header you get the 402 challenge (pay-to address, amount, network); with one you get the result.
# 1. Discover what's on offer — free.
curl https://attestifyos.com/api/earn/query
# 2. Ask a question. No payment header yet -> HTTP 402 with the x402 challenge.
curl -X POST https://attestifyos.com/api/earn/query \
-H 'content-type: application/json' \
-d '{"type":"counterparty","subject":"7Np41oe...CT4K2","subject_type":"wallet"}'
# 3. Retry with the x402 payment payload your wallet produced.
curl -X POST https://attestifyos.com/api/earn/query \
-H 'content-type: application/json' \
-H 'X-PAYMENT: <base64 x402 payload>' \
-d '{"type":"counterparty","subject":"7Np41oe...CT4K2","subject_type":"wallet"}'The 402 body follows the same x402 shape as POST /api/run and the Sentinel endpoints — reuse the same client-side payment code.
Query types
Prices are per successful call, in USDC. Every aggregate response is small-cell suppressed — a query resolving to too few underlying agents comes back rolled up or withheld, never individually identifiable.