Altradits

API reference.

Base URL https://altradits.com/api/dev/v1. JSON in, JSON out. This page restates the OpenAPI 3.1 contract in the repo.

Basics

Authentication

Authorization: Bearer <token>. The token is a dashboard session or an API key. Keys start with ak_live_ or ak_test_. A test key serves only the sandbox. A live key serves only live. Sessions manage the account and read everything, flagged per environment. Invoice creation always needs a key.

Errors and request ids

Every reply carries requestId. Quote it when asking for help. Failures look like this:

{"ok": false, "requestId": "9f2c…", "code": "insufficient_credits",
 "error": "This invoice needs 2 sats of fee credit and the balance holds 0. Top up first."}
CodeStatusMeaning
api_off, sandbox_off501That environment is not switched on.
bad_email, email_typo, bad_code400, 422, 400/429Signup code problems. The typo reply names the likely address.
unauthorized, invalid_key, key_revoked401Sign in again, or the key is unknown or revoked.
suspended, forbidden_scope, bad_scope403, 403, 400Suspended account, missing scope, unknown scope.
unknown_key, unknown_destination, unknown_invoice, unknown_webhook, unknown_topup, not_found404Unknown id. Cross-environment lookups also answer 404.
destination_unverified, challenge_unpaid, duplicate_destination409Finish the ownership proof first, or the address is taken.
challenge_expired410Register again for a fresh challenge.
bad_amount, bad_currency, amount_out_of_range, bad_address, address_mismatch, bad_webhook_url, bad_webhook_events, idempotency_conflict, sandbox_address_only, sandbox_only422The request was understood but refused. The sentence says why.
rate_limited429Past a limit. Wait and retry.
insufficient_credits402The fee balance can't cover this invoice. Top up first.
address_unreachable, invoice_mismatch, invoice_untracked, verify_unreachable502The destination failed or answered wrong. Nothing was paid.
node_offline, rate_unavailable, lockdown_freeze, account_busy503Try again later. Lockdown freezes new invoices and top-ups only.
bad_version404Unknown API version. The current version is v1.

Pagination

List endpoints take limit (1 to 200, default 50) and cursor. The reply carries nextCursor: pass it as cursor for the next page, newest first.

Idempotency

Invoice and top-up creation accept Idempotency-Key. The same key replays the first reply for 24 hours (deduplicated: true). The same key with different details answers 422.

Rate limits

100 invoices an hour per key. 2,000 invoices and 20 top-ups a day per account. 5 signup codes an hour per address. Webhook and settlement polling are capped per invoice and per connection. Past a limit, 429.

Health

GET/health Live

Always answers. Names whether the API and the sandbox are on. No authentication.

{"ok": true, "requestId": "…", "version": "v1", "api": "on", "sandbox": "on"}

Accounts

POST/accounts/request-code Live

Emails a one-time 8 digit code, good for 10 minutes. Body: {"email", "confirmTypo?"}. Never reveals whether an address is registered.

POST/accounts/verify-code Live

Verifies the code. Creates the account on first use. Returns the account and a session token, good for 30 days. Body: {"email", "code"}.

GET/accounts/me Live

The signed-in account. Session only.

POST/accounts/sign-out Live

Ends this session at once. Session only.

Keys

Session only. Scopes: invoices:create, invoices:read, webhooks. Secrets are shown once and never again.

GET/keys Live

Every key, newest first. Public halves only.

POST/keys Live

Body: {"name?", "scopes?", "sandbox?"}. Scopes default to all three. sandbox: true makes a test key.

POST/keys/{id}/rotate Live

Revokes the key and returns its replacement, shown once.

POST/keys/{id}/revoke Live

Revokes the key at once. The record is kept.

Destinations

Session only. Live: your own Lightning address, proven by a 1 sat challenge payment. Sandbox: only the built-in simulator dev@sandbox.altradits.com, verified at once with no payment.

GET/destinations Live

Every registered address on the account.

POST/destinations Live

Body: {"address", "sandbox?"}. Live returns the destination plus the 1 sat challenge invoice. The address must resolve and name itself. Sandbox returns the verified simulator with challenge: null.

POST/destinations/{id}/prove Live

After the challenge is paid. Checks settlement and re-binds the address, then marks it verified.

Invoices

Creation needs a key with invoices:create. Reads take a key with invoices:read or a session. A key sees only its own environment.

POST/invoices Live

Fetches your destination's own invoice for exactly the amount, checks it, and tracks it. Body: {"destinationId", "sats?" | "fiat?", "memo?"}. Fiat: {"amount", "currency"}, USD or KES, converted at the live rate and recorded. At most 100,000,000 sats. Frozen in a lockdown (503). Needs its fee covered (402). Accepts Idempotency-Key.

POST /api/dev/v1/invoices
{"destinationId": "K7…", "sats": 5000, "memo": "order 123"}

201, or 200 with "deduplicated": true on an Idempotency-Key replay
{"ok": true, "requestId": "…", "invoice": {
  "id": "…", "address": "you@wallet.com", "sats": 5000,
  "bolt11": "lnbc50u1…", "paymentHash": "…", "status": "pending",
  "verifySupported": true, "createdAt": "…", "settledAt": null }}
GET/invoices?limit&cursor Live

Your invoices, newest first. Paginated.

GET/invoices/{id} Live

The invoice, with a live settlement check when the destination supports verify. Without verify support, the invoice stays pending and the reply carries a note saying to confirm in your wallet.

GET/verify/{hash} Live

Settlement by payment hash (64 hex characters). Same live check as invoice status.

Webhooks

Key with the webhooks scope, or a session. A key sees only its own environment. Endpoints are never deleted, only disabled. The delivery log is kept.

POST/webhooks Live

Body: {"url", "events?", "sandbox?"}. The URL must be https on a public host. Events default to ["invoice.settled"]. Returns the endpoint plus the secret, shown once.

GET/webhooks Live

Every endpoint on the account. Secrets are never listed.

POST/webhooks/{id}/disable Live

Stops new deliveries at once.

GET/webhooks/{id}/deliveries?limit&cursor Live

Every delivery, newest first, with each attempt. Paginated.

POST to your URL on every settlement
{"event": "invoice.settled", "invoice": {…}, "deliveredAt": "…"}

Top-ups, balance, statement

Session, or a key (invoices:create to top up, invoices:read to read). Top-ups are live only, 100 sats minimum, invoiced from Stanley's node. Fees deduct 0.5% rounded up, minimum 1 sat, when an invoice settles. Credits are non-refundable, except on account close: unused live balance pays out to a verified destination within 7 days.

POST/topups Live

Body: {"sats"}. Returns the node invoice. Pay it, then poll the top-up. Frozen in a lockdown (503). Accepts Idempotency-Key.

GET/topups?limit&cursor Live

Your top-ups, newest first. Paginated.

GET/topups/{id} Live

The top-up, with a live check that credits the balance when paid. Crediting stays on during a lockdown.

GET/balance Live

The prepaid balance: {"live", "test"} in sats.

GET/statement Live

Balance, per-environment totals (live and test: toppedUp, feesPaid, settledVolume, settledCount, shortfall), and the ledger: every top-up, fee and grant, newest first. Paginated. Only live fees are real sats.

Sandbox

Test keys and the simulator. Free. Works while mainnet is off. The sandbox never touches the network or the node.

POST/sandbox/settle Sandbox only

Body: {"invoiceId"}. Plays the customer paying a sandbox invoice. Needs a test key with invoices:create, or a session. Live invoices are refused. Simulator invoices use signet encoding (lntb…), so a real wallet refuses them.

Verifying webhooks

Every delivery carries X-Dev-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256(secret, "<t>.<raw body>"). Check the timestamp is within 5 minutes. Compare in constant time. Failures retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. Then the delivery is marked dead.

Node.js verification
import crypto from "node:crypto";
const [t, v1] = sig.split(",").map((p) => p.split("=")[1]);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) throw new Error("stale");
const expect = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expect))) throw new Error("bad signature");

Changelog

VersionAdded
1.0.0Accounts, keys, destinations.
1.1.0Invoices, verify by hash, webhooks.
1.2.0Top-ups, balance, statement. Fee gate on new invoices.
1.3.0Sandbox simulator.
1.4.0Sessions may read invoices.