FLAM

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

StatusMeaning
200Settings + ceiling + packs + charges
401No 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 bodyapplication/json (required)

FieldTypeRequiredNotes
enabledbooleanyes
thresholdTokensintegeryes
pack"pack_250" | "pack_1000" | "pack_3000"yes
monthlyCapUsdnumberyes
{
  "enabled": true,
  "thresholdTokens": 0,
  "pack": "pack_250",
  "monthlyCapUsd": 0
}

Responses

StatusMeaning
200The saved rule, read back
400BAD_THRESHOLD · BAD_CAP · BAD_PACK · CAP_BELOW_REFILL
401No valid session
403forbidden — role_cannot_spend_money
409AMBIGUOUS_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

StatusMeaning
200Charged, or the reason nothing happened
401No valid session
403forbidden — role_cannot_spend_tokens
502charge_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

StatusMeaning
200Derived from the append-only credit_ledger
401No 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

StatusMeaning
200Active 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 bodyapplication/json (required)

FieldTypeRequiredNotes
amountUsdnumberyesDollars. Minimum is the cheapest active pack's price ($10 today); maximum is $5,000, a typo guard rather than a policy.
{
  "amountUsd": 25
}

Responses

StatusMeaning
200Hosted checkout URL, plus what will be charged and granted
400BAD_AMOUNT or AMOUNT_OUT_OF_RANGE (with minCents/maxCents)
401No valid session
403FORBIDDEN — only the owner spends the house's money
502CHECKOUT_FAILED
503BILLING_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 bodyapplication/json (required)

FieldTypeRequiredNotes
tier"studio" | "editorial" | "couture"yes
{
  "tier": "studio"
}

Responses

StatusMeaning
200Hosted checkout URL
400BAD_TIER
401No valid session
502CHECKOUT_FAILED
503BILLING_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 bodyapplication/json (required)

FieldTypeRequiredNotes
pack"pack_250" | "pack_1000" | "pack_3000"yes
{
  "pack": "pack_250"
}

Responses

StatusMeaning
200Hosted checkout URL
400BAD_PACK
401No valid session
502CHECKOUT_FAILED
503BILLING_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

StatusMeaning
200The portal session URL
400NO_CUSTOMER — nothing has ever been bought
401No valid session
403forbidden — role_cannot_spend_money
502PORTAL_FAILED · PORTAL_NO_URL
503BILLING_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 bodyapplication/json (required)

{}

Responses

StatusMeaning
200Applied ({ ok, ... }) or deduped ({ ok, duplicate })
400UNPARSEABLE
401BAD_SIGNATURE
500APPLY_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 '{}'