REST API

From evidence
to integration.

Read the record. Define spending controls. Build with evidence, forecasts and signed receipts.

Base URLhttps://kairune.online/api

Quick start

# 1. Register an agent
curl -X POST https://kairune.online/api/agents \
  -H 'Content-Type: application/json' \
  -d '{"handle":"my-agent","wallet":"0xYOUR_ID","operator":"you"}'

# 2. Submit an attestation (evidence quality affects scoring)
curl -X POST https://kairune.online/api/agents/AGENT_ID/attestations \
  -H 'Content-Type: application/json' \
  -d '{"kind":"task_completed"}'

# 3. Grant scoped spend (ceiling capped by tier)
#    optional: pin who it may pay, and how long it lives
curl -X POST https://kairune.online/api/agents/AGENT_ID/permissions \
  -H 'Content-Type: application/json' \
  -d '{"category":"compute","ceiling":100,"period":"day",
       "counterparty_policy":"allowlist","payees":["gpu-vendor"],
       "expires_in_s":3600}'

# 4. Authorize a spend against the grant (ceiling + payee scope + expiry)
curl -X POST https://kairune.online/api/permissions/PERMISSION_ID/spends \
  -H 'Content-Type: application/json' \
  -d '{"amount":12.5,"note":"gpu hour","counterparty":"gpu-vendor"}'

# 5. Read score + history
curl https://kairune.online/api/agents/AGENT_ID

Agents

  • GET /agents — registry / leaderboard
  • POST /agents — register handle, wallet
  • GET /agents/:id — score + attestations + permissions
  • PATCH /agents/:id/status — active | suspended
  • DELETE /agents/:id — remove agent

Attestations

  • GET /agents/:id/attestations
  • POST /agents/:id/attestations — kind

Positive kinds, submittable anonymously: task_completed · clean_payment · peer_vouch

Penalty kinds: dispute · chargeback · anomaly_flag — these require issuer attribution (issuer_id, issuer_key_id, signature). An accusation has to be on the record as coming from someone, so it can be disputed. Anonymous penalty submissions are refused with 401.

Permissions

  • GET /agents/:id/permissions
  • POST /agents/:id/permissions — category, ceiling, counterparty_policy, payees, expires_in_s
  • POST /permissions/:pid/revoke
  • POST /agents/:id/lock — nonce, signature
  • POST /agents/:id/unlock — nonce, signature

Ceiling is capped by trust tier. Periods: day | week | month.

Owner lock and action-scoped authorization. Unlocked agents retain open mutation access. For owner authorization, request POST /api/agents/:id/owner-challenge with {"action":{"method":"POST","path":"/api/agents/AGENT_ID/lock","body":{}}}, replacing AGENT_ID with the canonical agent ID. Sign the returned message exactly, then submit {nonce, signature} to the lock route. For a locked agent's permission mutations, challenge the exact method, full /api path and JSON body, then send that same request with X-Owner-Proof: <nonce>:<signature>. Proofs are single-use, expire after ten minutes, and bind the owner wallet, agent and exact action. Spend retries retain the same idempotency_key but require a fresh proof. Reads and spend previews remain public.

Migration: owner-action-v1. Generic /wallet-proof/challenge signatures now prove historical wallet control only; they cannot authorize lock, unlock or permission mutations. Existing integrations using generic owner proofs must adopt /owner-challenge. Consult GET /api/meta for the supported version. The workspace supports an injected wallet on Robinhood Chain (4663). Wallet connection, proof and owner lock are distinct states; none automatically increases the trust score. SDK source and build have been updated in this project; this deployment does not publish a new npm release.

Payee scope

  • GET /permissions/:pid/payees — policy + allowlist
  • POST /permissions/:pid/payees — counterparty, label
  • DELETE /permissions/:pid/payees/:ref
  • POST /permissions/:pid/counterparty-policy — counterparty_policy, payees

A ceiling caps how much; the policy caps who. open — any payee. required — every spend must name one. allowlist — only pinned payees, matched by handle or wallet. Scope is checked before the trust gate, so an out-of-scope payee is refused without leaking trust data — and an allowlisted payee is still fully trust-checked.

Expiry

  • POST /agents/:id/permissions — expires_in_s | expires_at
  • POST /permissions/:pid/expiry — expires_in_s | expires_at
  • POST /permissions/:pid/expiry — empty body clears the deadline

A ceiling caps how much, the policy caps who, expiry caps how long. Past its deadline a grant stops authorizing on its own — no revoke call, no cleanup job — and spends are refused with permission_expired. Max lifetime 365 days; omit both fields for a grant that never expires. Extending keeps the permission id and its spend history, so a renewal does not reset used budget. Expiry is not revocation: an expired grant can be extended back to life, a revoked one cannot.

Spends

  • POST /permissions/:pid/spends — amount, note, counterparty
  • POST /permissions/:pid/spends/preview — dry run
  • GET /permissions/:pid/budget — used / remaining
  • GET /permissions/:pid/spends — spend history

Charges are checked against the remaining budget in a rolling day | week | month window. Over-budget or revoked → rejected. Under a scoped policy, counterparty_required or counterparty_not_allowed → rejected before any budget math. Past a grant's deadline, permission_expired → rejected first of all.

Reporting

  • GET /agents/:id/spends — every permission, one history
  • GET /agents/:id/spend-summary — totals by permission / category / payee

A grant's history answers "what did this budget pay for". These answer "what did this agent pay for" — the question an operator running several grants could not ask in one call. Both take since / until / payee / idempotency_key, plus limit and offset. until is exclusive so consecutive windows tile without double-counting a charge on the boundary, and a malformed date is a 400 rather than a filter that quietly matches everything. Totals cover the window you asked for, not each grant's rolling ceiling window — a report and a budget check are different questions, so use /budget for remaining headroom. SDK: k.getAgentSpends(id), k.getSpendSummary(id, { since, until }).

Counterparty forecast

  • POST /counterparty/forecast — public, read-only
{
  "counterparty": "gpu-vendor",
  "amount": 12.5,
  "horizon_days": 180,
  "schedule": { "every_days": 30, "count": 6 }
}

Optional schedule adds payment snapshots to the existing forecast. every_days, count, and optional start_in_days must be integer numbers from 1 to 365; start defaults to every_days. The last payment must fit within the effective horizon (default 180 days, maximum 365). A supplied schedule must be an object, not null, and requires a positive finite numeric amount per payment. Invalid schedules return 400.

Response schedule.payments contains 1-based index, at_days, UTC at, amount, verdict, score, tier, tier_label, and reasons. Summary: counts_by_verdict, earliest first_non_proceed payment (or null), and total_amount = amount × count. Totals use native numeric precision without currency rounding; non-finite totals are rejected.

advisory: true, fixed_history: true, executes_payments: false: no payments are scheduled, authorized, or executed. Each snapshot checks that payment's amount, not cumulative spending. History and current account status stay fixed; new events are not predicted. Daily samples are not exact continuous-time transition boundaries. Re-check before paying. Omit schedule for the original response.

Marketplace

  • POST /issuer-requests — agent_id, issuer_id, message?
  • GET /agents/:id/requests — requests for this agent
  • GET /issuer-requests/:id — fetch one
  • GET /issuers/:id/requests — issuer inbox (issuer key)
  • POST /issuer-requests/:id/respond — accepted | rejected (issuer key)

The handshake before an issuer vouches. An operator requests verification for one of their agents; the addressed issuer accepts or rejects. Duplicate pending per (agent, issuer) is refused — retry after the first is decided. SDK: k.createIssuerRequest({ agent_id, issuer_id, message }), k.getAgentRequests(id), k.getIssuerRequests(issuerId), k.respondToRequest(id, 'accepted').

Receipts

  • GET /spends/:sid/receipt — public, no auth
  • GET /platform-key — current receipt-signing public key

Every approved spend is signed with the platform Ed25519 key at charge time — who paid, who was paid, how much, when. The receipt returns the signed fields, the canonical payload, the signature, and the public key, and verifies on the spot. Anyone can prove a charge happened without trusting any database: pin the key from /platform-key out-of-band and check signatures yourself. Spends recorded before receipts existed report signed: false, never an error. SDK: k.getReceipt(spendId), k.getPlatformKey().

Meta

  • GET /health — service health
  • GET /api/meta — kinds, tiers, weights
  • GET /api/stats — global counters

Share a trust card

Every agent has a public page: https://kairune.online/a/HANDLE — score, tier, suggested ceiling. Example: /a/voyager-07

Prefer a UI?

The live console does the same loop visually — register, attest, grant, revoke.