Build on the Nebula API.
Give every agent a card, a budget and an audit trail in a few API calls. Everything the dashboard does, the API does first.
On this pageIntroduction
Getting started
Introduction#
The Nebula API is organised around REST. It accepts JSON request bodies, returns JSON responses, and uses standard HTTP verbs and status codes. Every object has a stable, prefixed ID — acct_, agt_, card_, pol_, evt_ — so you can tell what something is from its ID alone.
| Base URL | https://api.nebulabank.xyz/v1 |
| Sandbox | https://sandbox.api.nebulabank.xyz/v1 |
| Current version | 2026-10-01 |
| Format | JSON, UTF-8, amounts in integer minor units |
Amounts are always integers in the currency’s minor unit, with an explicit currency: { "amount": 125000, "currency": "usd" } is $1,250.00. Sub-cent inference costs are reported in micros on ledger events.
Quickstart#
In five minutes you will create an agent, give it a card bound to a $500 monthly ceiling, and watch its first authorisation arrive.
- 1Get a sandbox key. Every approved account starts with a sandbox. Create a key under Settings → API keys; sandbox keys start with
nb_test_. - 2Create an agent. An agent is the thing that spends — a production workflow, an eval runner, a customer’s assistant.
- 3Attach a policy and issue a card. The card can only ever spend what the policy allows.
- 4Simulate a purchase. The sandbox lets you trigger authorisations against any card.
# 1. create an agent
curl https://api.nebulabank.xyz/v1/agents \
-H "Authorization: Bearer $NEBULA_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "eval-runner", "environment": "production" }'
# 2. issue a card with a $500 monthly ceiling
curl https://api.nebulabank.xyz/v1/cards \
-H "Authorization: Bearer $NEBULA_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent": "agt_8KpZ2xQ4",
"policy": {
"limit": { "amount": 50000, "currency": "usd", "interval": "month" },
"merchants": ["openai.com", "anthropic.com", "modal.com"]
}
}'Then simulate a $12.40 charge from the sandbox and look for it in the live ledger:
curl https://sandbox.api.nebulabank.xyz/v1/test/authorizations \
-H "Authorization: Bearer $NEBULA_KEY" \
-d card=card_3fQm9wLk -d amount=1240 -d merchant=openai.comAuthentication#
Authenticate with a secret key in the Authorization header as a bearer token. Keys are scoped to one account and one environment, and can be restricted to specific resources and actions when you create them.
| Prefix | Environment | Use |
|---|---|---|
nb_test_ | Sandbox | Development and CI. No real money moves. |
nb_live_ | Production | Server-side only. Never ship to a browser or an agent. |
nb_rk_ | Restricted | Least-privilege keys, e.g. read-only ledger access for BI. |
Agents should never hold a secret key. Give an agent a card credential, which can only spend within its policy, and keep the key that created it on your server.
Sandbox and production#
The sandbox is a full copy of the API with simulated rails. Use it to test policies, decline paths and webhooks. Objects never cross environments: a card created with a test key cannot be used with a live one.
- Test cards authorise instantly; use amount
9999to force a decline forinsufficient_funds. - Simulated payouts settle after 10 seconds instead of hours.
- Webhooks are delivered exactly as in production, including retries and signatures.
Core concepts
Accounts and balances#
An account holds money in one currency at a partner bank. A business can have several — USD for operations, EUR for European vendors — and move money between them at the quoted FX rate. Balances are reported as available (spendable now) and pending (authorised but not yet settled).
{
"id": "acct_1Hq7vN",
"object": "account",
"currency": "usd",
"balance": { "available": 48210055, "pending": 1320400 },
"routing": { "aba": "084106768", "account_number": "•••• 6620" }
}Agents and cards#
An agent is a named spender with an environment and optional metadata (owner team, customer ID, model). A card is a virtual payment credential that belongs to exactly one agent and is bound to exactly one policy.
Cards can be single-use, recurring, or locked to one merchant. Freezing a card is reversible; revoking it is not, and takes effect at the network within a second.
# freeze every card an agent holds, in one call
curl -X POST https://api.nebulabank.xyz/v1/agents/agt_8KpZ2xQ4/freeze \
-H "Authorization: Bearer $NEBULA_KEY"Policies#
A policy is evaluated on every authorisation, before money moves. If any rule fails, the authorisation is declined and the decline records which rule fired. Rules compose:
| Rule | Example | Effect |
|---|---|---|
limit | { amount: 50000, interval: "month" } | Rolling ceiling per interval |
per_transaction | { amount: 2000 } | Maximum single charge |
merchants | ["openai.com", "mcc:7372"] | Allowlist by domain or MCC |
hours | { tz: "UTC", from: 6, to: 22 } | Spend window |
approval | { above: 100000, by: "role:finance" } | Hold for a human above a threshold |
expires_at | "2026-12-31T00:00:00Z" | Card stops working after this time |
Policies are versioned. Updating one creates a new version that applies to the next authorisation; the ledger records which version approved each charge.
Ledger events#
Every money movement and every attributed vendor usage line becomes an immutable ledger event. Events carry the dimensions that matter to an AI company — agent, model, environment, customer — plus any tags you add. Corrections are new events that reference the original; nothing is ever edited in place.
{
"id": "evt_0Zr4Kc",
"type": "usage.attributed",
"vendor": "anthropic",
"amount_micros": 18420,
"currency": "usd",
"dimensions": {
"agent": "agt_8KpZ2xQ4",
"model": "claude-opus-5-5",
"environment": "production",
"customer": "cus_acme"
},
"occurred_at": "2026-10-22T09:14:03Z"
}Payouts and FX#
Payouts send money to a recipient in one of 31 corridors over local rails. Request a quote first to lock the FX rate, then create the payout against the quote. Quotes are valid until expires_at.
curl https://api.nebulabank.xyz/v1/quotes \
-H "Authorization: Bearer $NEBULA_KEY" \
-d source_currency=usd -d target_currency=eur -d target_amount=250000
curl https://api.nebulabank.xyz/v1/payouts \
-H "Authorization: Bearer $NEBULA_KEY" \
-H "Idempotency-Key: 6c1f0e7a-payout-oct" \
-d quote=quo_7Yt2 -d recipient=rcp_tideline_42API reference
Endpoints#
| Method | Path | Description |
|---|---|---|
| GET | /accounts | List accounts and balances |
| POST | /agents | Create an agent |
| POST | /agents/{id}/freeze | Freeze all of an agent’s cards |
| POST | /cards | Issue a card bound to a policy |
| DELETE | /cards/{id} | Revoke a card permanently |
| POST | /policies | Create a reusable policy |
| GET | /authorizations | List approvals and declines |
| GET | /ledger/events | Query ledger events by dimension |
| POST | /quotes | Lock an FX rate |
| POST | /payouts | Send an international payout |
| POST | /webhook_endpoints | Register a webhook URL |
List endpoints are cursor-paginated: pass limit (max 100) and starting_after with the last ID you received. Responses include has_more.
Webhooks#
Nebula sends a signed POST to your endpoint for every event you subscribe to. Respond with any 2xx within 10 seconds; otherwise we retry with exponential backoff for up to three days.
| Event | When |
|---|---|
authorization.approved | A card charge passed its policy |
authorization.declined | A charge was declined — includes the rule that fired |
approval.requested | A charge is waiting for a human |
payout.settled | Funds arrived with the recipient |
budget.threshold | An agent crossed 50%, 80% or 100% of its limit |
anomaly.detected | Vendor spend broke from its usual slope |
Verify every delivery with the Nebula-Signature header before trusting it:
import Nebula from '@nebula/sdk'
export async function POST(req: Request) {
const event = Nebula.webhooks.verify(
await req.text(),
req.headers.get('nebula-signature')!,
process.env.NEBULA_WEBHOOK_SECRET!,
)
if (event.type === 'authorization.declined') {
await page(`${event.data.agent} declined by ${event.data.rule}`)
}
return new Response(null, { status: 204 })
}Errors#
Errors return a non-2xx status and a body with a machine-readable code, a human-readable message, and a request_id to quote to support.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed |
| 401 | unauthenticated | No key, or the key is revoked |
| 403 | forbidden | The key is restricted from this action |
| 404 | not_found | No object with that ID in this environment |
| 409 | idempotency_conflict | Same key reused with different parameters |
| 422 | policy_violation | The request would break a policy |
| 429 | rate_limited | Slow down — see Retry-After |
| 5xx | api_error | Our fault. Safe to retry with the same idempotency key |
Idempotency and rate limits#
Send an Idempotency-Key header on every POST that moves money. Retrying with the same key returns the original result instead of paying twice. Keys are remembered for 24 hours.
| Plan | Requests / second | Burst |
|---|---|---|
| Growth | 25 | 50 |
| Scale | 100 | 200 |
| Enterprise | Custom | Custom |
Authorisation decisions are never rate-limited: a card charge is evaluated regardless of how busy your API key is.
Stop paying for machine time
on a human bank.
Open an account in an afternoon. Connect your vendors, issue your first agent card, and see where the money actually goes.
Open an accountNo platform fee · Sandbox on approval · Cancel anytime