Billing
10 Billing routes on the FLAM API: The house's auto top-up rule, ceiling, card and audit trail; Arm, retune or disarm the auto top-up rule (OWNER ONLY).
Base URL https://api.flam.fashion. Send Authorization: Bearer flam_sk_… on every call; a handful of routes are session-only and say so. How keys and roles work.
GET /api/toolkit/ai/billing/autocharge
The house's auto top-up rule, ceiling, card and audit trail
The standing order — "when the balance falls below X, buy pack Y" — plus what it has already spent this calendar month, whether a card is saved, and every automatic charge the provider has confirmed. available is false unless AUTOCHARGE_ENABLED=1 AND a Dodo token is configured; canArm mirrors the server's canSpendMoney (owner only).
Responses
| Status | Meaning |
|---|---|
200 | Settings + ceiling + packs + charges |
401 | No valid session |
200 returns:
{
"available": true,
"canArm": true,
"hasCard": true,
"hasCustomer": true,
"settings": {
"enabled": true,
"thresholdTokens": 0,
"refillCents": 0,
"refillPack": "string",
"monthlyCapCents": 0
},
"thisMonth": {
"period": "2026-07",
"spentCents": 0
},
"lastChargeAt": "2026-07-27T09:00:00.000Z",
"packs": [
{
"slug": "string",
"tokens": 0,
"priceCents": 0
}
],
"limits": {},
"charges": [
{
"at": "2026-07-27T09:00:00.000Z",
"amountCents": 0,
"currency": "string",
"reference": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/ai/billing/autocharge" \
-H "Authorization: Bearer $FLAM_API_KEY"PUT /api/toolkit/ai/billing/autocharge
Arm, retune or disarm the auto top-up rule (OWNER ONLY)
Real money on a standing order, so this is gated by canSpendMoney — owner only, 403 for every other seat. The refill is always a real catalogue pack; the monthly ceiling is a hard stop and may never be below one refill.
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
enabled | boolean | yes | — |
thresholdTokens | integer | yes | — |
pack | "pack_250" | "pack_1000" | "pack_3000" | yes | — |
monthlyCapUsd | number | yes | — |
{
"enabled": true,
"thresholdTokens": 0,
"pack": "pack_250",
"monthlyCapUsd": 0
}Responses
| Status | Meaning |
|---|---|
200 | The saved rule, read back |
400 | BAD_THRESHOLD · BAD_CAP · BAD_PACK · CAP_BELOW_REFILL |
401 | No valid session |
403 | forbidden — role_cannot_spend_money |
409 | AMBIGUOUS_PACK_PRICE |
200 returns:
{
"ok": true,
"settings": {}
}Call it
curl -X PUT "https://api.flam.fashion/api/toolkit/ai/billing/autocharge" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":true,"thresholdTokens":0,"pack":"pack_250","monthlyCapUsd":0}'POST /api/toolkit/ai/billing/autocharge/run
Fire the standing order if the house is genuinely short
A HINT endpoint — every condition is re-validated server-side, so calling it is safe and it answers 200 either way with { charged, reason }. The reservation (enabled · 10-minute debounce · monthly ceiling · live spendable balance) is one atomic UPDATE, so two concurrent calls can never both charge. Nothing here credits a token: payment.succeeded is the sole grant authority. Gated by canSpendTokens (viewers excluded) — it executes an instruction the owner already gave.
Responses
| Status | Meaning |
|---|---|
200 | Charged, or the reason nothing happened |
401 | No valid session |
403 | forbidden — role_cannot_spend_tokens |
502 | charge_failed — the provider refused; the reservation is rolled back |
200 returns:
{
"charged": true,
"reason": "flag_off",
"pack": "string",
"tokens": 0,
"amountCents": 0,
"paymentId": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/ai/billing/autocharge/run" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/ai/billing/balance
Spendable token balance + open holds
Responses
| Status | Meaning |
|---|---|
200 | Derived from the append-only credit_ledger |
401 | No valid session |
200 returns:
{
"balance": 0,
"grant": 0,
"topup": 0,
"held": 0,
"holds": [
{
"id": "string",
"amount": 0,
"generationId": "string",
"createdAt": "string"
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/ai/billing/balance" \
-H "Authorization: Bearer $FLAM_API_KEY"GET /api/toolkit/ai/billing/catalog
Public pricing catalog (tiers + packs)
Responses
| Status | Meaning |
|---|---|
200 | Active subscription tiers and top-up packs |
200 returns:
{
"provider": "dodo",
"tiers": [
{
"slug": "string",
"name": "string",
"priceUsd": 0,
"monthlyTokens": 0,
"rolloverMultiple": 0,
"featured": true
}
],
"packs": [
{
"slug": "string",
"tokens": 0,
"priceUsd": 0
}
]
}Call it
curl -X GET "https://api.flam.fashion/api/toolkit/ai/billing/catalog" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/ai/billing/checkout/custom
Hosted Dodo checkout for a pay-what-you-want top-up
The director names a dollar amount; it becomes tokens at the top-up rate — the highest price per token any active row in topup_packs sells at, so a custom amount is never cheaper per token than a pack, and packs are already pricier than every plan (check:pricing rule 4). The amount is quantised DOWN to whole tokens, so $10.30 buys 257 tokens and charges $10.28. No processing-fee gross-up, so no fee line: the fee is the same one the fixed packs already carry. Owner only — it charges the house.
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
amountUsd | number | yes | Dollars. Minimum is the cheapest active pack's price ($10 today); maximum is $5,000, a typo guard rather than a policy. |
{
"amountUsd": 25
}Responses
| Status | Meaning |
|---|---|
200 | Hosted checkout URL, plus what will be charged and granted |
400 | BAD_AMOUNT or AMOUNT_OUT_OF_RANGE (with minCents/maxCents) |
401 | No valid session |
403 | FORBIDDEN — only the owner spends the house's money |
502 | CHECKOUT_FAILED |
503 | BILLING_NOT_CONFIGURED |
200 returns:
{
"url": "string",
"tokens": 0,
"amountCents": 0
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/ai/billing/checkout/custom" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amountUsd":25}'POST /api/toolkit/ai/billing/checkout/subscription
Hosted Dodo checkout for a subscription tier
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
tier | "studio" | "editorial" | "couture" | yes | — |
{
"tier": "studio"
}Responses
| Status | Meaning |
|---|---|
200 | Hosted checkout URL |
400 | BAD_TIER |
401 | No valid session |
502 | CHECKOUT_FAILED |
503 | BILLING_NOT_CONFIGURED |
200 returns:
{
"url": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/ai/billing/checkout/subscription" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tier":"studio"}'POST /api/toolkit/ai/billing/checkout/topup
Hosted Dodo checkout for a token pack
Request body — application/json (required)
| Field | Type | Required | Notes |
|---|---|---|---|
pack | "pack_250" | "pack_1000" | "pack_3000" | yes | — |
{
"pack": "pack_250"
}Responses
| Status | Meaning |
|---|---|
200 | Hosted checkout URL |
400 | BAD_PACK |
401 | No valid session |
502 | CHECKOUT_FAILED |
503 | BILLING_NOT_CONFIGURED |
200 returns:
{
"url": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/ai/billing/checkout/topup" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pack":"pack_250"}'POST /api/toolkit/ai/billing/portal
Open the merchant of record's hosted portal (OWNER ONLY)
Dodo owns the card, the subscription controls and the tax documents, so this hands off rather than rebuilding them. Owner only (canSpendMoney).
Responses
| Status | Meaning |
|---|---|
200 | The portal session URL |
400 | NO_CUSTOMER — nothing has ever been bought |
401 | No valid session |
403 | forbidden — role_cannot_spend_money |
502 | PORTAL_FAILED · PORTAL_NO_URL |
503 | BILLING_NOT_CONFIGURED |
200 returns:
{
"url": "string"
}Call it
curl -X POST "https://api.flam.fashion/api/toolkit/ai/billing/portal" \
-H "Authorization: Bearer $FLAM_API_KEY"POST /api/toolkit/ai/billing/webhook/dodo
Dodo payment webhook (standard-webhooks signature)
The only unauthenticated billing write — authenticated by the webhook-id / webhook-timestamp / webhook-signature headers, not a session. Duplicate deliveries dedupe via mor_webhook_events.
Request body — application/json (required)
{}Responses
| Status | Meaning |
|---|---|
200 | Applied ({ ok, ... }) or deduped ({ ok, duplicate }) |
400 | UNPARSEABLE |
401 | BAD_SIGNATURE |
500 | APPLY_FAILED |
Call it
curl -X POST "https://api.flam.fashion/api/toolkit/ai/billing/webhook/dodo" \
-H "Authorization: Bearer $FLAM_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'