Credits & billing
How credits are calculated per page, what is not charged, the free plan of 150 credits a month, packs in EUR and USD, expiry, balance, alerts and 402 errors.
Constaia is pay-as-you-go with credits. There is no subscription: you buy packs when you need them and, meanwhile, you get 150 free credits every month. Prices and comparison at /en/pricing.
What each operation costs
| Operation | Credits |
|---|---|
Analysis (POST /v1/analyze) of a document of up to 2 pages | 1 |
| Every 2 additional pages | +1 |
Classification (POST /v1/classify) | 0.2 |
| Exports (JSON, CSV, XLSX, XML, vCard, PDF) | Included |
The rule is credits = ceil(pages / 2), with a minimum of 1. A Spanish DNI with both sides in one file or in a 2-page PDF
costs 1 credit.
| Document | Pages | Credits |
|---|---|---|
| DNI, front and back in one file | 2 | 1 |
| One-page medical certificate | 1 | 1 |
| 3-page invoice | 3 | 2 |
| 10-page contract | 10 | 5 |
| 5 classifications | — | 1 |
| Batch of 100 two-page documents | 200 | 100 |
In a batch, each document is counted separately with the same rule. Every analysis shows what it cost in usage:
"usage": { "credits": 1, "pages": 2 }What is not charged
- Test mode.
ck_test_keys don't use credits (usage.credits: 0). - Failures on our side. An analysis that ends with
status: "failed"(for exampleprocessing_failed) is not charged. - Quality rejections before OCR. If the image is rejected before it is read, it is not charged.
- Retries with the same
Idempotency-Keywithin 24 h: they return the stored response without charging twice. - Exports. Already included.
An analysis that ends as invalid or review is charged: Constaia did the work and gave you an answer.
Reservation and capture
When an analysis starts, Constaia reserves the credits it will cost. When it finishes:
- if it completes, the reservation is captured;
- if it fails on our side or is rejected for quality before OCR, the reservation is released and returns to your balance.
While analyses are in progress you will see credits in credits_reserved. If you don't have enough balance to reserve,
the request fails with a 402 insufficient_credits and nothing is analysed.
In live mode, a batch checks when it is created that you have at least 1 credit per document; if not, the whole batch is rejected with 402. Each document then costs what its pages require.
Free plan
- 150 credits a month, renewed on the 1st of each month (UTC). Unused credits don't roll over.
- Spending them in live mode requires a verified email.
free_tier_resets_atin the balance tells you when they renew.
Packs
Packs are one-off payments, no subscription. Prices exclude VAT:
| Pack | EUR | ≈ €/credit | USD | ≈ $/credit |
|---|---|---|---|---|
| 1,000 credits | €49 | 0.049 | $55 | 0.055 |
| 5,000 credits | €199 | 0.040 | $219 | 0.044 |
| 25,000 credits | €790 | 0.032 | $869 | 0.035 |
| 100,000 credits | €2,490 | 0.025 | $2,740 | 0.027 |
| Enterprise | Custom | — | Custom | — |
- Packs expire 12 months after purchase.
- Consumption order: first this month's free plan, then packs from oldest to newest (FIFO).
- Enterprise: contract, DPA, SLA and your own limits. Details at /en/pricing.
Buy packs under Billing in app.constaia.com. Payment goes through Stripe Checkout and the credits are added to your balance as soon as it is confirmed.
VAT and invoices
- Prices exclude VAT. Stripe Tax calculates it at checkout based on your tax details.
- Every purchase generates an invoice, which you will find under Billing in the dashboard.
- Fill in your company's tax details before your first purchase so they appear on the invoice.
Checking your balance
GET /v1/balance returns:
{
"object": "balance",
"credits_available": 4210,
"credits_reserved": 3,
"free_tier_remaining": 12,
"credits_spendable": 4222,
"free_tier_resets_at": "2026-10-01T00:00:00.000Z"
}| Field | Meaning |
|---|---|
credits_available | Pack credits available (free plan not included, minus reservations drawn from packs). |
credits_reserved | Credits reserved by analyses in progress. |
free_tier_remaining | Free credits left this month (minus their reservations). |
credits_spendable | What you can spend right now: credits_available + free_tier_remaining. |
free_tier_resets_at | When the free plan renews. |
What you can spend right now is credits_spendable (credits_available + free_tier_remaining).
curl https://api.constaia.com/v1/balance \
-H "Authorization: Bearer $CONSTAIA_API_KEY"Checking usage
GET /v1/usage?from=YYYY-MM-DD&to=YYYY-MM-DD aggregates usage by day and document type (by default, the last 30 days of
the key's mode):
curl "https://api.constaia.com/v1/usage?from=2026-09-01&to=2026-09-30" \
-H "Authorization: Bearer $CONSTAIA_API_KEY"{
"object": "usage",
"from": "2026-09-01",
"to": "2026-09-30",
"livemode": true,
"data": [
{ "date": "2026-09-29", "type": "es_dni", "kind": "analysis", "livemode": true, "count": 42, "pages": 84, "credits": 42 }
],
"totals": { "count": 42, "pages": 84, "credits": 42 }
}To split costs between customers or departments, send metadata with each analysis (for example
{ "club_id": "42" }) and filter with GET /v1/analyses?metadata[club_id]=42. More in
Balance & usage.
Low-credit alert
In the dashboard settings you set an alert threshold (50 credits by default). When, after a charged live analysis, your spendable balance drops below it, Constaia:
- sends the
credits.lowwebhook to subscribed endpoints, and - emails the account's owners and admins.
{
"type": "credits.low",
"created_at": "2026-09-29T10:00:00Z",
"data": { "object": "balance", "credits_available": 0, "free_tier_remaining": 48, "credits_spendable": 48, "threshold": 50 }
}The fields mean the same as in GET /v1/balance: credits_available is pack credits only and credits_spendable is
what you can spend (free plan plus packs); the alert fires when credits_spendable drops below the threshold. It is sent
once until you top up with a pack or the free plan renews. Subscribe from Webhooks.
Handling 402 errors
Without enough balance, the API responds:
{
"error": {
"type": "insufficient_credits",
"code": "insufficient_credits",
"message": "…",
"request_id": "req_…"
}
}Don't retry it in a loop: it won't fix itself. Queue the work, alert your team and resume when there is balance.
import { Constaia, InsufficientCreditsError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia();
try {
const analysis = await constaia.analyze(await fromPath("./dni.jpg"), { expect: "es_dni" });
console.log(analysis.verdict?.status);
} catch (err) {
if (err instanceof InsufficientCreditsError) {
// Keep the document for later and alert whoever buys credits.
console.error(`Out of credits (request ${err.requestId})`);
} else {
throw err;
}
}Monthly spend cap
You can set a monthly credit cap for the account: if a live-mode analysis would exceed it, it is rejected with
402 monthly_cap_reached and not charged, and owners and admins get an email at 80 % and 100 %. Details in
Rate limits.
Coming soon: managed human review
Human review of documents by the Constaia team: +€0.40 per document. Not available yet; meanwhile, build your own queue with the human review guide. Watch the changelog.
Next steps
Exports
Download Constaia results as JSON, CSV, Excel, XML, vCard or PDF, per analysis or combined per batch, via 24-hour signed links or on demand.
Versioning and changelog
How Constaia versions its API (v1 in the path) and SDKs, which compatible changes may ship without notice and how to write code that doesn't break.