Plans and Limits
Tracore meters usage with two counters — pages (the sum of every successfully-extracted document’s pageCount) and extractions (the count of successful runs). Both reset on the same anniversary monthly window. Limits are enforced server-side. When you hit one, an in-app paywall modal explains which limit fired and links to the upgrade flow.
What counts as a page?
Each document is measured at upload time and stored as pageCount on the document row.
| File type | Page count |
|---|---|
One page per PDF page (parsed via pdf-lib) | |
| DOCX | The <Pages> value in the document XML |
| Images (JPEG, PNG) | 1 |
| Plain text | 1 |
The number you see in usage.pagesThisWindow (from GET /user/plan) is the sum of pageCount across documents whose extractions have successfully completed within your current monthly window. The matching usage.extractionsThisWindow field counts how many extraction runs have completed successfully in the same window — Free is capped at 50 successful extractions per month, Pro at 1,000.
When are pages charged?
Pages are charged on the first successful extraction of a document.
- A failed extraction does not consume any quota.
- A document whose extraction succeeded counts once per anniversary window — re-extracting it on Pro (with a different schema or model) is free pages-wise.
- While an extraction is in flight, its pages are reserved in
usage.pendingPagesso concurrent uploads can’t overshoot the limit. The reservation is released on success or failure. - If an extraction worker crashes, an hourly reconciliation job heals the reserved value.
The monthly window
Your quota resets on a per-user anniversary window, anchored on your signup day-of-month. The window is always exactly one month long, with last-day-of-month clamping for short months.
Examples for a user who signed up on January 31:
| Today | Window start | Window end |
|---|---|---|
| Feb 15 | Jan 31 | Feb 28 |
| Feb 28 | Feb 28 | Mar 31 |
| Mar 1 | Feb 28 | Mar 31 |
Examples for a user who signed up on March 15:
| Today | Window start | Window end |
|---|---|---|
| Mar 20 | Mar 15 | Apr 15 |
| Apr 14 | Mar 15 | Apr 15 |
| Apr 16 | Apr 15 | May 15 |
The window block in GET /user/plan returns the live start, end, and daysRemaining.
Free plan
| Limit | Value |
|---|---|
| Workspaces | 1 |
| Schemas | 3 |
| Pages per month | 50 |
| Extractions per month | 50 |
| Environments | production only |
| Webhooks | Enabled |
| Re-extraction | Disabled |
- The
staginganddevelopmentenvironments show a lock icon in the env selector. Selecting one redirects you to the/upgradepage. - Re-running extraction on a previously-counted document requires Pro.
- All plan checks return
HTTP 403 plan_limit_exceededwith a discriminator that drives the in-app modal.
Pro plan — $19 / month
| Limit | Value |
|---|---|
| Workspaces | Unlimited |
| Schemas | Unlimited |
| Pages per month | 2,000 |
| Extractions per month | 1,000 |
| Environments | production, staging, development |
| Webhooks | Enabled |
| Re-extraction | Enabled |
Upgrades happen through Stripe Checkout. Returning to the app at /?upgraded=true triggers a short poll on GET /user/plan; the plan pill flips to Pro within 30 seconds without a manual refresh.
When you upgrade mid-window, your existing usage is preserved against the new limit. A user at 100/100 pages on Free who upgrades immediately has 1,900 pages remaining — the window does not reset.
When you hit a limit
The api returns HTTP 403 with a structured body:
{
"error": {
"code": "plan_limit_exceeded",
"limit": "max_pages_per_month",
"plan": "free",
"limitValue": 50,
"currentValue": 50,
"windowStart": "2026-04-01T00:00:00Z",
"windowEnd": "2026-05-01T00:00:00Z",
"message": "Free plan allows 100 pages per month. Upgrade to Pro for 2,000.",
"upgradeUrl": "/upgrade"
}
}
The web app maps the limit discriminator to one of six modal copy variants:
limit | Triggered by |
|---|---|
max_workspaces | Creating a 2nd workspace on Free |
max_schemas | Creating a 4th schema on Free |
max_pages_per_month | An extraction whose pages would push you over the window total |
max_extractions_per_month | An extraction that would exceed your monthly successful-run count |
env_not_allowed | Selecting staging / development on Free |
re_extract_not_allowed | Re-extracting an already-counted document on Free |
Inspecting your plan
import { TracoreClient } from "@tracore/sdk";
const client = new TracoreClient({ apiKey: process.env.TRACORE_API_KEY! });
const plan = await client.user.getPlan();
console.log(plan.plan); // "free" | "pro"
console.log(plan.usage.pagesThisWindow); // 37
console.log(plan.usage.extractionsThisWindow); // 12
console.log(plan.usage.pendingPages); // 0
console.log(plan.window.daysRemaining); // 14
The same data drives the plan pill in the dashboard header. The pill turns amber once you cross 80 % of your page limit.