Skip to content

Scripting the admin API

The admin API is what the admin UI uses, behind a session and a CSRF token. A script drives it the same way. Keep the credentials in the environment; this signs in as a real administrator and can do everything one can.

Terminal window
BELUGA_URL=https://shop.example.com \
BELUGA_EMAIL=you@example.com BELUGA_PASSWORD='...' \
node publish-all.mjs
const BASE = process.env.BELUGA_URL;
const EMAIL = process.env.BELUGA_EMAIL;
const PASSWORD = process.env.BELUGA_PASSWORD;
let cookie = "";
let csrf = "";
function remember(response) {
const set = response.headers.getSetCookie?.() ?? [];
if (set.length) cookie = set.map((c) => c.split(";")[0]).join("; ");
}
async function api(method, path, body) {
const response = await fetch(`${BASE}/api${path}`, {
method,
headers: {
accept: "application/json",
...(body ? { "content-type": "application/json" } : {}),
...(cookie ? { cookie } : {}),
...(csrf ? { "x-csrf-token": csrf } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
remember(response);
if (!response.ok) {
const text = await response.text();
throw new Error(`${method} ${path} → ${response.status}: ${text}`);
}
return response.json();
}
// 1. A session, and with it the CSRF token every write needs.
const session = await api("GET", "/session");
csrf = session.csrfToken;
// 2. Sign in. The response is identical for a wrong email and a wrong password.
await api("POST", "/session", { email: EMAIL, password: PASSWORD });
csrf = (await api("GET", "/session")).csrfToken;
// 3. Everything, drafts included.
const { products } = await api("GET", "/products/full");
const pending = products.filter(
(p) => p.isLive && p.variants.some((v) => !v.stripePriceId),
);
console.log(`${pending.length} live product(s) not fully published.`);
// 4. Publish each one. This is the only call in this script that writes to Stripe.
for (const product of pending) {
await api("POST", `/products/${product.id}/publish`);
console.log(`published ${product.slug}`);
}
await api("DELETE", "/session");
  • Publish is deliberate. This script’s whole purpose is to write to Stripe, so it does it through the one route that may, and prints each product it touches. A script that saves products should never expect them to reach Stripe.
  • The CSRF token is re-read after login, because the session it belongs to changed.
  • The password is a live credential. The session is destroyed at the end, and nothing is written to disk.
  • The login route is rate-limited. A script that retries in a tight loop locks itself out for a while, which is the feature working.
  • GET /admin/orders?status=paid and PUT /admin/orders/:id with { status, carrier, trackingNumber } to bulk-mark a batch as shipped from a carrier’s manifest.
  • GET /admin/orders.csv?from=&to= nightly into your accounting.
  • POST /admin/products/import/validate with a CSV body to lint a catalogue file in CI before anyone commits it.