Automate your reseller account from your own backend. All amounts are in USD.
Create a key in Resell → API & Webhooks. It starts with qbx_rs_. Keep it on your server only — it can manage all your customers.
BASE=https://api.qubax.ai/reseller/v1
curl $BASE/me -H "Authorization: Bearer qbx_rs_..."sk-…) make AI requests on your domain only.| GET | /me | Your status, credit, margin and domains |
| PATCH | /pricing | Set defaultMarginPct (≥ 0) and lowBalancePct (1–90) |
| GET | /models | All models with your cost, margin and customer price |
| PATCH | /models/{modelId} | Per-model: enabled (bool), marginPct (number or null = default) |
| POST | /customers | Create customer: name, email?, externalRef?, billingMode (prepaid | limit), initialCreditUsd? / spendLimitUsd + limitPeriod (monthly | total) |
| GET | /customers | List. ?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}/credit | Add (or remove, negative) balance: amountUsd, note?. Send Idempotency-Key |
| GET | /customers/{id}/ledger | Balance history |
| POST | /customers/{id}/keys | Create key (returned once): name? |
| GET | /customers/{id}/keys | List keys (prefix only) |
| DELETE | /customers/{id}/keys/{keyId} | Revoke key |
| GET | /usage | Totals. ?from=&to=&groupBy=day|model|customer&customerId= |
| GET | /usage/export.csv | CSV per day × customer × model. ?from=&to= |
| GET | /requests | Latest requests (metadata only). ?customerId=&limit=&cursor= |
| GET | /domains | Your domains + CNAME target |
| POST | /domains | Add domain: hostname |
| GET | /domains/{id} | Domain with live DNS/SSL check |
| DELETE | /domains/{id} | Remove domain |
| GET | /webhooks | List webhooks + event names |
| POST | /webhooks | Add: url (https), events? (default all). Secret returned once |
| DELETE | /webhooks/{id} | Delete webhook |
| POST | /webhooks/{id}/test | Send a test event |
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 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).
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.
{
"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:
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));
}