Skip to content

Account developer guide

How to call the account API from code. Concepts are in the user guide; every endpoint and field is in the API reference.

This API is not the compute API. It does not use SigV4 and it does not use access keys.

https://storefront.job-rss-processor.workers.dev

[!primary]

No custom domain is attached yet, so this hostname will change. Read it from configuration rather than hard-coding it in a client you ship.

LevelWhat it needs
PublicNothing. /v1/catalog, /v1/prices, /v1/prices/comparison
SessionA Bearer JWT
Session + onboardedA Bearer JWT and a completed onboarding

A session call before onboarding does not fail with an auth error — it returns a 400 telling you to become a customer first. Handle that as a distinct state, not as a broken token.

Identity is a hosted auth service, and this API and the site are on different hosts — so the browser’s session cookie is never sent here and there is nothing to forward. Instead the site asks the auth service for a short-lived JWT and sends it on every call:

Authorization: Bearer <jwt>

The token is verified against the issuer’s JWKS on every request. Three claims are read: the user id, the email, and whether the email is verified.

Expiry is 15 minutes by default. There is no refresh endpoint on this API — ask the auth service for a new token. Build the refresh into your client rather than treating a 401 as a dead end.

async function call(path, init = {}) {
const jwt = await getFreshToken(); // yours; refresh when near expiry
const res = await fetch(BASE + path, {
...init,
headers: { ...init.headers, Authorization: `Bearer ${jwt}` },
});
if (!res.ok) throw await res.json();
return res.status === 204 ? null : res.json();
}
await call("/v1/onboard", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: cloudPassword }),
});

Every step is find-before-create and every grant is re-applied until onboarding completes, so calling it again is the recovery path — not an error, and not something to guard against. If a call times out, call it again.

Two things to get right:

  1. The body carries the cloud password. It is used only on first creation; a later call with a different password does nothing. It is also stored in recoverable form so the console can be opened for the customer — see Credentials and trust.
  2. Check completion with GET /v1/me rather than assuming a POST succeeded. Once onboarded it also returns your cloud auth URL and username.
const key = await call("/v1/access-keys", { method: "POST" }); // 201
// { access, secret }
await call("/v1/access-keys"); // list
await call(`/v1/access-keys/${encodeURIComponent(access)}`, { method: "DELETE" }); // 204

Rotate in this order: mint, roll out, confirm the old key is idle, delete. Deleting is immediate with no grace period.

See Access keys for the security property that differs from AWS.

const usage = await call("/v1/usage");

Machines running and the month-to-date cost, in one call. This is the only cost-before-the-invoice surface. The cost half lags by about two hours — an hour is rated roughly two hours after it ends — while the machines half is live. Field by field: Usage and cost.

await call("/v1/console/session", { method: "POST" }); // web console
await call(`/v1/machines/${id}/console`, { method: "POST" }); // one machine

The machine console returns a single-use, time-limited URL and refuses a machine that is not yours. Do not cache it, and do not paste one into a ticket.

The three public endpoints are cached for 60 seconds at the edge, keyed on the URL. Every other route is per-request.

That has one consequence worth coding around: inStock in the catalog can be up to a minute stale. It is a snapshot, not a reservation — another customer can take the capacity, and a launch can still fail with InsufficientInstanceCapacity. Treat the launch as the authoritative answer.

Exactly one origin is allowed with credentials — this site’s own. A page on another origin cannot call the session routes from a browser, by design.

Server-side callers are unaffected.

Errors come back as JSON problem documents, not the EC2 XML shape the compute API uses. Do not share one error handler between the two.

StatusMeansDo
400Not onboarded, or a malformed body, or a card not matching its checkout sessionRead the message; it names the missing precondition
401Missing, expired or unverifiable tokenRefresh and retry once
403Email not verifiedVerify, then retry
404Not yours, or no such thingTreat as not-found
502/503An upstream did not answer — “the cloud did not answer about access keys”, “prices are momentarily unavailable”Retry with backoff. Nothing has been half-done: writes here are find-before-create