Reseller API Reference

Automate your reseller account from your own backend. All amounts are in USD.

Authentication

Create a key in Resell → API & Webhooks. It starts with qbx_rs_. Keep it on your server only — it can manage all your customers.

Shell
BASE=https://api.qubax.ai/reseller/v1
curl $BASE/me -H "Authorization: Bearer qbx_rs_..."
⚠️
This key manages your account. It cannot make AI requests. Customer keys (sk-…) make AI requests on your domain only.

Endpoints

GET/meYour status, credit, margin and domains
PATCH/pricingSet defaultMarginPct (≥ 0) and lowBalancePct (1–90)
GET/modelsAll models with your cost, margin and customer price
PATCH/models/{modelId}Per-model: enabled (bool), marginPct (number or null = default)
POST/customersCreate customer: name, email?, externalRef?, billingMode (prepaid | limit), initialCreditUsd? / spendLimitUsd + limitPeriod (monthly | total)
GET/customersList. ?search=&limit=&cursor=
GET/customers/{id}One customer
PATCH/customers/{id}Update name, email, externalRef, status (active | paused), spendLimitUsd, limitPeriod
DELETE/customers/{id}Delete customer and revoke all their keys
POST/customers/{id}/creditAdd (or remove, negative) balance: amountUsd, note?. Send Idempotency-Key
GET/customers/{id}/ledgerBalance history
POST/customers/{id}/keysCreate key (returned once): name?
GET/customers/{id}/keysList keys (prefix only)
DELETE/customers/{id}/keys/{keyId}Revoke key
GET/usageTotals. ?from=&to=&groupBy=day|model|customer&customerId=
GET/usage/export.csvCSV per day × customer × model. ?from=&to=
GET/requestsLatest requests (metadata only). ?customerId=&limit=&cursor=
GET/domainsYour domains + CNAME target
POST/domainsAdd domain: hostname
GET/domains/{id}Domain with live DNS/SSL check
DELETE/domains/{id}Remove domain
GET/webhooksList webhooks + event names
POST/webhooksAdd: url (https), events? (default all). Secret returned once
DELETE/webhooks/{id}Delete webhook
POST/webhooks/{id}/testSend a test event

Example: new customer after checkout

JavaScript
const BASE = "https://api.qubax.ai/reseller/v1";
const H = { Authorization: `Bearer ${process.env.QUBAX_RESELLER_KEY}`, "Content-Type": "application/json" };

// 1. create the customer
const c = await fetch(`${BASE}/customers`, { method: "POST", headers: H,
  body: JSON.stringify({ name: "Acme Ltd", externalRef: stripeCustomer.id }) }).then(r => r.json());

// 2. give them a key (show it to them once)
const k = await fetch(`${BASE}/customers/${c.id}/keys`, { method: "POST", headers: H, body: "{}" }).then(r => r.json());

// 3. after every payment, add balance (safe to retry)
await fetch(`${BASE}/customers/${c.id}/credit`, { method: "POST",
  headers: { ...H, "Idempotency-Key": payment.id },
  body: JSON.stringify({ amountUsd: 20, note: "Stripe " + payment.id }) });

Errors

Errors use { "error": { "message": "..." } } with status 400 (bad input), 401 (bad key), 403 (not approved / suspended), 404 (not found) or 429 (too many requests).

On your domain, customer requests fail with 402 when their balance or spend limit is used up, 403 when the customer is paused, and 503 when your credit is empty (so your customers never see a billing message about you).

Webhooks

We POST JSON to your URL. Events: customer.balance_low, customer.balance_depleted, customer.spend_limit_reached, reseller.credit_low, reseller.credit_depleted, domain.active, domain.failed. Failed deliveries are retried a few times.

JSON
{
  "id": "evt_3f9c…",
  "type": "customer.balance_low",
  "created": 1790510400,
  "data": {
    "customer": { "id": "…", "external_ref": "cus_123", "name": "Acme Ltd", "billing_mode": "prepaid",
      "balance_usd": 0.84, "spent_this_period_usd": 0, "spend_limit_usd": null }
  }
}

Verify every webhook. The signature is HMAC-SHA256 of `${timestamp}.${rawBody}` with your signing secret:

JavaScript
import crypto from "node:crypto";

function verify(req, rawBody, secret) {
  const ts = req.headers["x-webhook-timestamp"];
  const sig = req.headers["x-webhook-signature"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 min
  const expected = crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
Reseller API Reference · Qubax AI