Accounts, tiers and billing

Status as of 27/07/2026: self-service delivered (Epic 4) — signup, email verification, account area, Stripe subscription/cancellation. Per-key dashboard, quota headers and 80% alert delivered (Epic 12, Stories 12.1 to 12.3). Not delivered: automated accounting reconciliation with Stripe (see Dev Notes of Story 4.4).

Full journey: create an account → Découverte key → first call

  1. Register an accountPOST /signup with email + password (8 characters minimum). A Découverte key is created, but inactive. Common disposable email domains are refused; account creation is capped by IP and by period (anti-abuse, FR-9).
  2. Verify the email — click the link received (GET /verify?token=...). The key becomes active immediately.
  3. First call — use the key received in step 1 as Authorization: Bearer fxk_... on any /v1/* endpoint.

No manual action on the operator's side at any step. See the getting started guide for the complete commands.

Tiers

Tier Included quota Price [ASSUMPTION] Overage
Découverte 20 documents/month [ASSUMPTION] Free Blocked (HTTP 429) — never overage on the free tier
Starter 200 documents/month [ASSUMPTION] €29/month [ASSUMPTION] Overage +20% of unit price [ASSUMPTION]
Pro 1,000 documents/month [ASSUMPTION] €79/month [ASSUMPTION] Overage +20% [ASSUMPTION]
Éditeur 5,000 documents/month [ASSUMPTION] €199/month [ASSUMPTION] Overage +20% [ASSUMPTION]

Amounts and quotas marked [ASSUMPTION] are carried over from the PRD as launch hypotheses, not fixed business decisions — to be checked against the market before milestone J3 (see PRD, §4.6/FR-10). On a paid tier, exceeding the quota is never blocking: documents beyond the included quota are billed as overage, never interrupted.

The quota is applied per API KEY, not per account (Story 12.1): an account with several keys sees each one consume its own included volume independently of the others. The dashboard (/account/) therefore shows one usage row per active key, never a total presented as a single account-wide cap.

Subscribe, change tier, cancel

From the account area (/account/, login by email/password):

  • Subscribe or change tier: redirect to Stripe Checkout, hosted by Stripe — your card never transits through Heartwood's infrastructure.
  • Manage card, invoices, cancellation: redirect to the Stripe Customer Portal, exclusively.
  • A tier change (upgrade, downgrade, cancellation) is reflected in the quota in under a minute (processed by a signature-verified Stripe webhook).
  • A cancellation returns the account to the Découverte tier (no abrupt cutoff): the 20 documents/month quota applies again.

Idempotence

Billed endpoints (POST /v1/generate/facturx, POST /v1/validate) accept an optional Idempotency-Key header, tied to the hash of the request body, over a 24h window:

  • Same API key + same Idempotency-Key + same body replayed: the original document is returned as-is (Idempotency-Replayed: true header), counted only once.
  • Same API key + same Idempotency-Key + different body: HTTP 409, nothing is produced or counted.
{
  "type": "idempotency-key-conflict",
  "title": "Idempotency-Key déjà utilisée avec un corps différent",
  "status": 409,
  "detail": "réutilisez une nouvelle Idempotency-Key pour un corps différent (Annexe B PRD)",
  "instance": "01H..."
}

Découverte quota exceeded

{
  "type": "quota-exceeded",
  "title": "Quota mensuel Découverte dépassé",
  "status": 429,
  "detail": "réinitialisation le 2026-08-01 — upgradez sur /account/ pour un palier sans quota fixe",
  "instance": "01H..."
}

All errors on this page follow the RFC 7807 format (application/problem+json).

Quota headers (Story 12.2)

Every authenticated call to a billed endpoint (/v1/generate/facturx, /v1/validate, /v1/generate/ubl, /v1/convert, /v1/extract) carries three response headers, on every response — success as well as failure (400, 409, 413, 422, 429...), not only on a quota refusal:

Header Meaning
X-Quota-Limit Monthly volume included in the tier of the key that authenticated the call.
X-Quota-Remaining Remaining before reaching that volume, never negative.
X-Quota-Reset Quota reset date, in RFC 3339 format (first day of the following calendar month, midnight UTC).

/v1/rulepacks (authenticated but not billed) does not carry these headers — it consumes no document from the quota.

Representation chosen for X-Quota-Remaining on a paid tier (Starter, Pro, Éditeur — never blocking, AD-5): the REAL remainder of the included volume, floored at 0. It is never a fictitious cap nor an "unlimited" value that would misrepresent the volume actually included — it is the same figure shown on the dashboard. A paid tier at X-Quota-Remaining: 0 continues to be served normally (never a 429): the overage is simply billed by usage. On the Découverte tier, by contrast, X-Quota-Remaining: 0 means the next call will be refused (429) until X-Quota-Reset.

The headers are computed on usage logged before the current call: the current request is only counted after its outcome (success or failure), known only after processing — too late to set headers before the first byte of the response.

80% quota alert (Story 12.3)

An account on the Découverte tier that crosses 80% of its monthly quota (16 documents out of 20) receives an email informing them:

  • how much they have already consumed this month;
  • what will happen beyond the included quota (HTTP 429 block, with the reset date);
  • how to change tier from the account area, to never be blocked (paid tiers never are).

At most one email per key and per month. Sending this email never makes the current API call fail: a sending error is logged server-side, never returned to the caller.