API reference
The control API lives at https://api.marvel.sh. It is JSON over HTTPS. Authenticated endpoints
take a session token as Authorization: Bearer <token> (see Authentication);
public endpoints are marked public.
Accounts & sessions
Section titled “Accounts & sessions”POST /v1/accounts
Section titled “POST /v1/accounts”public Create an account. Password must be ≥ 8 characters.
POST /v1/accountsContent-Type: application/json
{ "email": "you@example.com", "password": "a-strong-passphrase" }201 Created{ "account_id": 42, "token": "sess_…" }POST /v1/sessions
Section titled “POST /v1/sessions”public Sign in; returns a fresh session token. An unknown email and a
wrong password return the identical 401 (no user enumeration).
POST /v1/sessions{ "email": "you@example.com", "password": "a-strong-passphrase" }200 OK{ "account_id": 42, "token": "sess_…" }DELETE /v1/sessions
Section titled “DELETE /v1/sessions”Invalidate the current session token. Returns 204 No Content.
GET /v1/me
Section titled “GET /v1/me”The authenticated account.
200 OK{ "account_id": 42, "email": "you@example.com", "created_at": "2026-01-04T12:00:00Z" }API keys
Section titled “API keys”POST /v1/keys
Section titled “POST /v1/keys”Mint a key. The raw key is returned once; only its hash is stored.
POST /v1/keys{ "label": "prod-scraper" }201 Created{ "id": 7, "key": "mk_live_xxxxxxxxxxxxxxxx", "prefix": "mk_live_xxxx", "label": "prod-scraper" }GET /v1/keys
Section titled “GET /v1/keys”List your keys — prefixes only, never the raw secret. A revoked key carries a revoked_at.
200 OK[ { "id": 7, "prefix": "mk_live_xxxx", "label": "prod-scraper", "created_at": "2026-01-04T12:00:00Z", "revoked_at": null } ]DELETE /v1/keys/{id}
Section titled “DELETE /v1/keys/{id}”Revoke a key by id. 204 No Content on success, 404 if it isn’t yours or doesn’t exist. Revoked
keys stop authenticating to the proxy immediately.
A node is a vetted residential exit. Issuing one pins a live exit for a sticky session, or samples a rotating exit when no session is given.
POST /v1/nodes
Section titled “POST /v1/nodes”Balance-gated: returns 402 on an empty balance, 503 when the country has no live capacity, and
422 on an invalid country, session id, or min_purity.
POST /v1/nodes{ "country": "US", "session": "cart_19f3", "min_purity": 90 }200 OK{ "session": "cart_19f3", "exit_ip": "76.121.x.x", "country": "US", "asn": 7018, "purity": 99, "grade": "S", "seals": ["authentic", "residential", "clean"], "expires_at": "2026-01-04T12:10:00Z", "dial": "country-US-session-cart_19f3"}session, min_purity are optional. With no session, the response session is null and the
exit is a rotating sample (not listable). The asn field is the re-resolved ASN — evidence, not
a selector you can set.
GET /v1/nodes
Section titled “GET /v1/nodes”Your live sticky nodes. Dead pins are dropped automatically. Empty array when you have none.
200 OK[ { "session": "cart_19f3", "exit_ip": "76.121.x.x", "country": "US", "asn": 7018, "purity": 99, "grade": "S", "seals": ["authentic","residential","clean"], "expires_at": "…", "dial": "country-US-session-cart_19f3" } ]Availability
Section titled “Availability”GET /v1/availability
Section titled “GET /v1/availability”public Every country with live capacity right now, with per-grade counts, mean Purity, and the dominant grade. Countries with zero live nodes are omitted.
200 OK[ { "country": "US", "live_nodes": 184, "s": 121, "a": 47, "b": 16, "mean_purity": 96, "dominant_grade": "S" } ]GET /v1/availability/{country}
Section titled “GET /v1/availability/{country}”public The same shape for a single country. 404 when that country
has no live capacity right now.
curl https://api.marvel.sh/v1/availability/USUsage & ledger
Section titled “Usage & ledger”GET /v1/usage
Section titled “GET /v1/usage”Your balance and consumption, in bytes.
200 OK{ "balance_bytes": 7000000000, "used_bytes_total": 3000000000, "used_bytes_30d": 3000000000 }GET /v1/usage/series
Section titled “GET /v1/usage/series”Daily bytes consumed, zero-filled into a contiguous series for charting. Query days defaults to
14, max 90.
curl "https://api.marvel.sh/v1/usage/series?days=30" -H "Authorization: Bearer $MARVEL_TOKEN"200 OK[ { "day": "2026-01-03", "used_bytes": 0 }, { "day": "2026-01-04", "used_bytes": 524288000 } ]GET /v1/ledger
Section titled “GET /v1/ledger”Your ledger, newest first. Each row carries the running balance after it applied; amount_cents is
the money side of a purchase and null for a usage debit.
200 OK[ { "created_at": "…", "kind": "usage", "delta_bytes": -3000000000, "amount_cents": null, "balance_after_bytes": 7000000000, "reference": "win_…" }, { "created_at": "…", "kind": "purchase", "delta_bytes": 10000000000, "amount_cents": 5000, "balance_after_bytes": 10000000000, "reference": "cs_…" } ]Billing
Section titled “Billing”GET /v1/bundles
Section titled “GET /v1/bundles”public The prepaid volume bundles — the source of truth for
pricing. bytes and the cent fields are integers.
200 OK[ { "id": "gb10", "gb": 10, "bytes": 10000000000, "total_cents": 5000, "per_gb_cents": 500 }, { "id": "tb1", "gb": 1000, "bytes": 1000000000000, "total_cents": 200000, "per_gb_cents": 200 } ]POST /v1/checkout
Section titled “POST /v1/checkout”Create a Stripe Checkout Session for a bundle and return its URL. Send the customer there to pay by card.
POST /v1/checkout{ "bundle_id": "gb100" }200 OK{ "checkout_url": "https://checkout.stripe.com/c/pay/…" }POST /v1/webhooks/stripe
Section titled “POST /v1/webhooks/stripe”Stripe-to-Marvel only — you never call this. It is the single credit path: a bundle is credited
to your balance only when a signature-verified checkout.session.completed event with
payment_status: "paid" arrives. A redirect back to the dashboard is not payment; the webhook is.
Redelivery is idempotent — a bundle is never double-credited.