Rate limits and billing
Who this is for: Teams running live pay batches who need to predict monthly cost and HTTP headers on every calculation.
When you finish: You know what increments usage, how batch size counts, and what happens at the plan cap.
Takehome meters calculations, not HTTP requests. Pricing is CAD and published in pricing.md.
What counts as one calculation
| Call | Count (live keys) |
|---|---|
POST /v1/deductions |
1 per successful 200 response |
POST /v1/deductions/batch |
N = length of the requests array, whenever the call returns 200, including items that come back with an error |
POST /v1/deductions/year |
pay_period calculations per successful projection (26 for biweekly, 12 for monthly; at most 27) |
Examples:
- A batch of 20 employees in one HTTP call counts 20 calculations.
-
A batch of 25 counts 25 (the maximum array size, set by the Workers Free CPU limit; a larger batch is HTTP 400
batch_too_large). -
A batch of 10 where 2 items fail validation or are refused by the engine (for example a Quebec item) still returns
200and counts 10: every item is counted, successful or not. Validate items before sending them if this matters to you. -
A biweekly year projection (
pay_period26) counts 26 calculations; a monthly one counts 12. A year projection runs at most 27 pay periods: a weekly, daily or hourly year is HTTP 400year_too_largeand counts nothing. -
A single call that fails validation, a
401, a402, and a year projection the engine refuses do not increment usage. A batch rejected as a whole (wrong shape, more than 25 items) does not either. -
If a batch or a year projection would exceed the remaining live quota, the whole call returns
plan_limit(402) and nothing is counted.
Quebec (QC) jurisdiction_not_supported responses on POST /v1/deductions are not successful calculations and are not counted. A Quebec item inside a batch is counted like any other batch item.
Usage headers
Successful deduction, batch and year responses include:
| Header | Meaning |
|---|---|
X-Usage-Limit |
Monthly live calculation cap for the plan, or unlimited for np_test_ keys |
X-Usage-Remaining |
Calculations left in the current UTC month |
X-Usage-Reset |
ISO timestamp when the UTC month meter resets |
Every API path also has Cloudflare's per-IP rule in front of it: at most 10 requests per 10 seconds from one IP address, counted before our code runs. Above it, Cloudflare answers HTTP 429 with its own page for 10 seconds: not JSON, so there is no error.code; wait 10 seconds and retry. One IP address gets at most 60 API requests a minute in all, so the per-minute limits below are reached only from more than one address.
The rule counts by IP address alone, whatever the key or account. Test-key and live-key calls from one address share its 10 requests per 10 seconds: a CI job that sends test calls from the same address as your production server can make that server's live calls wait up to 10 seconds. Send test traffic from another address if that matters. Callers behind one shared outbound address share it too: an office network, a CI service, or a serverless platform. Requests from Cloudflare Workers to our API may share Cloudflare's outbound addresses with other Workers customers (not verified); if your payroll code runs on Workers, the takehome-ca WASM package (in the repository, not yet published on npm) runs the same engine inside your Worker with no API call (see offline and self-host). The free plan's daily limits (Worker requests and database rows) are shared too, by every caller of the service; see the API Terms, section 7.
Test keys return unlimited headers: they are not metered, but an account's test keys and its live keys are each limited to 120 requests a minute (HTTP 429 rate_limited; counted per server instance, so approximate), so test traffic never uses the live keys' own per-minute limit; from one IP address, Cloudflare's per-IP rule above counts test and live calls together. Test keys may also run at most 5,000 calculations a minute per account (one per single calculation, the item count of a batch, the pay periods of a year projection; HTTP 429 rate_limited; a request answered with an error counts nothing, and a batch answered with HTTP 200 counts all its items, including items that failed). Key rotations are limited to 10 a UTC day made with the test key and, separately, 10 a UTC day made with the live key; revoking a key and deleting a webhook endpoint are never refused by our own limits or budgets, and neither is a deletion of the account with the live key and the matching email (only refused deletion attempts count, about 5 a minute per IP address); Cloudflare's per-IP rule above still applies to all three. Live keys may also make at most 3,600 calls a day (UTC) per account, counting each request once (a batch or a year projection is one call), which still allows the whole monthly quota one call at a time; above it they get HTTP 429 rate_limited and nothing is counted. Live keys return integers derived from the plan row in pricing.md.
The live quota is counted in one conditional database statement, so requests running at the same time cannot together pass it: a request that would pass it gets 402 and is not counted.
Hard stop at the limit
Live keys (np_live_) stop with HTTP 402 and error.code plan_limit when the UTC-month tally would exceed the plan. There is no overage billing and no silent throttle to cheaper math.
Paid tiers are published on pricing.md but not offered: billing is deferred (ADR-006), and POST /v1/billing/checkout returns billing_unavailable (501). Nothing is charged.
Developer tier: $0 with 100,000 live calculations per UTC month (data/plans.json). Every live account is on it during early access.
Test keys
np_test_ keys run the same engine and return the same JSON as live keys for identical inputs. They are free and unmetered. Use them for CI, staging, and the five-minute onboarding test.
HTTP rate limits (rate_limited, 429) still apply separately from calculation metering. Back off on 429 regardless of key kind; a 429 whose body is not JSON is Cloudflare's per-IP rule (wait 10 seconds), which test and live calls from one IP address share.
Metering outages
If usage storage fails, live calculation routes return store (503) and do not perform unmetered live math. See errors.md and ADR-005.
Last reviewed: 2026-10-30
Engine: 0.1.0