Documentation

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.

https://api.nebulabank.xyz/v1Version 2026-10-01
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 URLhttps://api.nebulabank.xyz/v1
Sandboxhttps://sandbox.api.nebulabank.xyz/v1
Current version2026-10-01
FormatJSON, 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.

  1. 1Get a sandbox key. Every approved account starts with a sandbox. Create a key under Settings → API keys; sandbox keys start with nb_test_.
  2. 2Create an agent. An agent is the thing that spends — a production workflow, an eval runner, a customer’s assistant.
  3. 3Attach a policy and issue a card. The card can only ever spend what the policy allows.
  4. 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.com

Authentication#

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.

PrefixEnvironmentUse
nb_test_SandboxDevelopment and CI. No real money moves.
nb_live_ProductionServer-side only. Never ship to a browser or an agent.
nb_rk_RestrictedLeast-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 9999 to force a decline for insufficient_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:

RuleExampleEffect
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_42

API reference

Endpoints#

MethodPathDescription
GET/accountsList accounts and balances
POST/agentsCreate an agent
POST/agents/{id}/freezeFreeze all of an agent’s cards
POST/cardsIssue a card bound to a policy
DELETE/cards/{id}Revoke a card permanently
POST/policiesCreate a reusable policy
GET/authorizationsList approvals and declines
GET/ledger/eventsQuery ledger events by dimension
POST/quotesLock an FX rate
POST/payoutsSend an international payout
POST/webhook_endpointsRegister 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.

EventWhen
authorization.approvedA card charge passed its policy
authorization.declinedA charge was declined — includes the rule that fired
approval.requestedA charge is waiting for a human
payout.settledFunds arrived with the recipient
budget.thresholdAn agent crossed 50%, 80% or 100% of its limit
anomaly.detectedVendor 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.

StatusCodeMeaning
400invalid_requestA parameter is missing or malformed
401unauthenticatedNo key, or the key is revoked
403forbiddenThe key is restricted from this action
404not_foundNo object with that ID in this environment
409idempotency_conflictSame key reused with different parameters
422policy_violationThe request would break a policy
429rate_limitedSlow down — see Retry-After
5xxapi_errorOur 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.

PlanRequests / secondBurst
Growth2550
Scale100200
EnterpriseCustomCustom

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