API reference
Who this is for: Developers integrating the hosted Takehome API who need every route, field, and error code in one place.
When you finish: You can call any documented route with the correct method, body shape, and typed error handling.
Responses follow T4127 (Payroll Deductions Formulas). Year projection responses may cite YMPE (Year's Maximum Pensionable Earnings) and CPP2 (second additional CPP) cap fields.
Generated from services/api/openapi.json (OpenAPI 3.0.3). Regenerate with node scripts/generate-api-reference.mjs.
Base URL: https://takehome.gautamkhosla.com
API version: 0.1.0
Authenticated routes expect Authorization: Bearer np_test_… or np_live_… unless noted.
GET /health
Liveness only. No versions, dependencies, or environment.
Authentication: none.
Responses
#### HTTP 200
OK
Body schema: HealthResponse
status("ok", required)
#### HTTP 503
Engine failed the probe
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 503.
GET /openapi.json
OpenAPI document generated from the same schemas as this service.
Authentication: none.
Responses
#### HTTP 200
OpenAPI envelope
Body schema: OpenApiResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)document(object, required)
POST /v1/billing/checkout
Stripe Checkout session in CAD for a published self-serve tier.
Request body
plan("starter" | "growth" | "business", required)success_url(string, optional)cancel_url(string, optional)
Responses
#### HTTP 200
Checkout URL
Body schema: CheckoutResponse
url(string, required)currency("cad", required)plan(string, required)
#### HTTP 400
Unknown plan
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401, 429.
POST /v1/billing/webhook
Stripe webhook. Upgrades the plan. Never invents overage.
Authentication: none.
Responses
#### HTTP 200
Received
Body schema: WebhookResponse
received(boolean, required)
#### HTTP 400
Invalid event
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400.
GET /v1/changes
Machine-readable change log.
Authentication: none.
Responses
#### HTTP 200
Changes
Body schema: ChangesResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)feed_title(string, optional)feed_link(string, optional)feed_description(string, optional)items(array, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 429.
GET /v1/changes.rss
RSS 2.0 change log.
Authentication: none.
Responses
#### HTTP 200
RSS
Body: RSS 2.0 XML string.
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 429.
GET /v1/conformance
PDOC conformance record, including every disagreement.
Authentication: none.
Responses
#### HTTP 200
Conformance
Body schema: ConformanceResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)record(object, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 429.
POST /v1/deductions
Calculate T4127 payroll deductions for one pay period.
Requires a Bearer np_test_ or np_live_ key. Metering counts calculations, not HTTP requests. A batch of 1000 on POST /v1/deductions/batch counts 1000. Test keys are free and unmetered. Live keys hard-stop at the plan limit; Takehome does not surprise-bill.
Request body
T4127 request. Unknown fields are rejected by name. as_of is required by core; if omitted here the Worker sets the UTC request date and does not nearest-match a rule set.
as_of(string, optional)province(string, required)pay_period(integer, required)gross_pay(string, required) Decimal money as a lexical string. Never a JSON number.calculation_option(string, optional)pensionable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.insurable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.federal_claim_code(object, optional)provincial_claim_code(object, optional)federal_tc(string, optional) Decimal money as a lexical string. Never a JSON number.provincial_tcp(string, optional) Decimal money as a lexical string. Never a JSON number.cpp_months(integer, optional)k2_method(string, optional)rounding_compat(string, optional)ytd_pensionable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_insurable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_cpp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_cpp2(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_ei(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_federal_tax(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_provincial_tax(string, optional) Decimal money as a lexical string. Never a JSON number.pay_periods_elapsed(integer, optional)bonus(string, optional) Decimal money as a lexical string. Never a JSON number.retroactive_pay(string, optional) Decimal money as a lexical string. Never a JSON number.bonus_method(string, optional)ytd_bonus(string, optional) Decimal money as a lexical string. Never a JSON number.f5b_ytd(string, optional) Decimal money as a lexical string. Never a JSON number.most_recent_i(string, optional) Decimal money as a lexical string. Never a JSON number.rpp(string, optional) Decimal money as a lexical string. Never a JSON number.bonus_rrsp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_bonus_rrsp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_income(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_rpp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_union_dues(string, optional) Decimal money as a lexical string. Never a JSON number.f5a_ytd(string, optional) Decimal money as a lexical string. Never a JSON number.commission_income(string, optional) Decimal money as a lexical string. Never a JSON number.commission_expenses(string, optional) Decimal money as a lexical string. Never a JSON number.estimated_annual_expenses(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_qpp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_qpp2(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_qpip(string, optional) Decimal money as a lexical string. Never a JSON number.prior_province(string, optional)date_of_birth(string, optional)cpp_exempt(boolean, optional)cpp_election_after_65(boolean, optional)additional_tax_requested(string, optional) Decimal money as a lexical string. Never a JSON number.taxable_benefits(string, optional) Decimal money as a lexical string. Never a JSON number.union_dues(string, optional) Decimal money as a lexical string. Never a JSON number.alimony(string, optional) Decimal money as a lexical string. Never a JSON number.child_care_expenses(string, optional) Decimal money as a lexical string. Never a JSON number.prescribed_zone_deduction(string, optional) Decimal money as a lexical string. Never a JSON number.lcf_purchase(string, optional) Decimal money as a lexical string. Never a JSON number.lcp_purchase(string, optional) Decimal money as a lexical string. Never a JSON number.dependants_under_19(integer, optional)dependants_disabled(integer, optional)
Responses
#### HTTP 200
Deduction
Body schema: DeductionResponse
rule_set_version(string, required)engine_version(string, required)engine_build_sha256(string, required)prorated_rules_applied(boolean, optional)employee(object, required)employer(object, required)annual_projection(object, optional)breakdown(object, optional)citations(array, optional)warnings(array, optional)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 402
Plan calculation limit reached
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 422
Jurisdiction not supported
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401, 402, 422, 429.
POST /v1/deductions/batch
Calculate T4127 payroll deductions for a pay run of up to 1000 employees.
Requires a Bearer np_test_ or np_live_ key. One HTTP call, up to 1000 calculations, metered as N not 1. Each result is {ok, response} or {error}; one bad employee record does not abort the others. If the live plan cannot cover the whole batch, the call is 402 and usage is unchanged.
Request body
Up to 1000 T4127 calculations. Partial success: each result is {ok, response} or {error}. Item failures do not abort the pay run.
requests(array, required)
Responses
#### HTTP 200
Pay run results, in input order
Body schema: BatchResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)results(array, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 400
Rejected request, including batches over 1000
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 402
Plan calculation limit reached; nothing billed
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401, 402, 429.
POST /v1/deductions/year
Project every pay period in a year with YTD CPP, CPP2, and EI carried forward.
Requires a Bearer np_test_ or np_live_ key. Metered as P calculations, not 1. Identifies the CPP cap period, EI cap period, YMPE crossing, and CPP2 start (spec §21.5). Sum of per-period federal tax versus annual T1 is allowed to differ by one cent per pay period because of per-period rounding.
Request body
T4127 request. Unknown fields are rejected by name. as_of is required by core; if omitted here the Worker sets the UTC request date and does not nearest-match a rule set.
as_of(string, optional)province(string, required)pay_period(integer, required)gross_pay(string, required) Decimal money as a lexical string. Never a JSON number.calculation_option(string, optional)pensionable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.insurable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.federal_claim_code(object, optional)provincial_claim_code(object, optional)federal_tc(string, optional) Decimal money as a lexical string. Never a JSON number.provincial_tcp(string, optional) Decimal money as a lexical string. Never a JSON number.cpp_months(integer, optional)k2_method(string, optional)rounding_compat(string, optional)ytd_pensionable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_insurable_earnings(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_cpp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_cpp2(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_ei(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_federal_tax(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_provincial_tax(string, optional) Decimal money as a lexical string. Never a JSON number.pay_periods_elapsed(integer, optional)bonus(string, optional) Decimal money as a lexical string. Never a JSON number.retroactive_pay(string, optional) Decimal money as a lexical string. Never a JSON number.bonus_method(string, optional)ytd_bonus(string, optional) Decimal money as a lexical string. Never a JSON number.f5b_ytd(string, optional) Decimal money as a lexical string. Never a JSON number.most_recent_i(string, optional) Decimal money as a lexical string. Never a JSON number.rpp(string, optional) Decimal money as a lexical string. Never a JSON number.bonus_rrsp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_bonus_rrsp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_income(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_rpp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_union_dues(string, optional) Decimal money as a lexical string. Never a JSON number.f5a_ytd(string, optional) Decimal money as a lexical string. Never a JSON number.commission_income(string, optional) Decimal money as a lexical string. Never a JSON number.commission_expenses(string, optional) Decimal money as a lexical string. Never a JSON number.estimated_annual_expenses(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_qpp(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_qpp2(string, optional) Decimal money as a lexical string. Never a JSON number.ytd_qpip(string, optional) Decimal money as a lexical string. Never a JSON number.prior_province(string, optional)date_of_birth(string, optional)cpp_exempt(boolean, optional)cpp_election_after_65(boolean, optional)additional_tax_requested(string, optional) Decimal money as a lexical string. Never a JSON number.taxable_benefits(string, optional) Decimal money as a lexical string. Never a JSON number.union_dues(string, optional) Decimal money as a lexical string. Never a JSON number.alimony(string, optional) Decimal money as a lexical string. Never a JSON number.child_care_expenses(string, optional) Decimal money as a lexical string. Never a JSON number.prescribed_zone_deduction(string, optional) Decimal money as a lexical string. Never a JSON number.lcf_purchase(string, optional) Decimal money as a lexical string. Never a JSON number.lcp_purchase(string, optional) Decimal money as a lexical string. Never a JSON number.dependants_under_19(integer, optional)dependants_disabled(integer, optional)
Responses
#### HTTP 200
Year projection
Body schema: YearResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)periods(array, required)totals(YearTotals, required)annual_t1(string, required) Decimal money as a lexical string. Never a JSON number.federal_tax_sum_tolerance(string, required) Decimal money as a lexical string. Never a JSON number.cpp_cap_period(integer | null, optional)ei_cap_period(integer | null, optional)cpp2_start_period(integer | null, optional)ympe_cross_period(integer | null, optional)ympe(string, optional) Decimal money as a lexical string. Never a JSON number.
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 402
Plan calculation limit reached; nothing billed
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 422
Jurisdiction not supported
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Header X-Usage-Limit: Monthly live calculation cap, or unlimited for np_test_ keys.
Header X-Usage-Remaining: Calculations remaining this UTC month, or unlimited.
Header X-Usage-Reset: Next UTC month start, when the live meter resets.
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401, 402, 422, 429.
GET /v1/jurisdictions
Provinces and territories. Quebec is listed as unsupported.
Authentication: none.
Responses
#### HTTP 200
Jurisdictions
Body schema: JurisdictionsResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)jurisdictions(array, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 429.
GET /v1/keys
Active key prefixes for this account. Secrets are never returned.
Responses
#### HTTP 200
Prefixes only
Body schema: KeyListResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)keys(array, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 401.
POST /v1/keys/revoke
Revoke keys of the given kind immediately. A lost key is regenerated, never retrieved.
Request body
kind("test" | "live", required)
Responses
#### HTTP 200
Revoked
Body schema: KeyRevokeResponse
kind("test" | "live", required)revoked(true, required)
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401.
POST /v1/keys/rotate
Issue a new key of the given kind. The previous key is revoked immediately. The new secret is shown once.
Request body
kind("test" | "live", required)
Responses
#### HTTP 200
New secret, shown once
Body schema: KeyRotateResponse
kind("test" | "live", required)key(string, required)message(string, required)
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401.
GET /v1/rules
Embedded T4127 rule-set editions.
Authentication: none.
Responses
#### HTTP 200
Rule set list
Body schema: RulesListResponse
rule_set_version(string, required)engine_version(string, required)engine_build_sha256(string, required)rule_set_versions(array, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 429.
GET /v1/rules/diff
Field-by-field comparison of two embedded T4127 editions.
Authentication: none.
Parameters
from(query, required): stringto(query, required): string
Responses
#### HTTP 200
Rule-set diff
Body schema: RulesDiffResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)from(string, required)to(string, required)changed(array, required)fields(object, required)cpp(array, required)ei(array, required)qpip(array, required)
#### HTTP 400
Missing versions
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 404
Unknown version
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 404, 429.
GET /v1/rules/{version}
One embedded rule-set edition. Unknown versions 404; never nearest-match.
Authentication: none.
Parameters
version(path, required): string
Responses
#### HTTP 200
Rule set
Body schema: RuleVersionResponse
rule_set_version(string, required)engine_version(string, required)engine_build_sha256(string, required)effective_from(string, required)effective_to(string | null, optional)status("enacted" | "proposed", required)
#### HTTP 404
Unknown version
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 404, 429.
POST /v1/signup
Start signup. Email, verify, keys. No sales call.
Authentication: none.
Request body
email(string, required)
Responses
#### HTTP 200
Verification email sent
Body schema: SignupResponse
message(string, required)
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 429.
GET /v1/signup/verify
Verify email and issue np_test_ plus np_live_ keys.
Authentication: none.
Parameters
token(query, required): string
Responses
#### HTTP 200
Keys issued once
Body schema: VerifyResponse
test_key(string, required)live_key(string, required)message(string, required)
#### HTTP 400
Invalid token
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 429
Rate limited
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 429.
GET /v1/webhooks
Webhook endpoints for this account.
Responses
#### HTTP 200
Endpoints
Body schema: WebhookListResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)webhooks(array, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 401.
POST /v1/webhooks
Register an HTTPS endpoint. Signing secret is shown once.
The whsec_ secret is returned only on create. Deliveries are HMAC-SHA256 over t + "." + body as takehome-signature: t=<unix>,v1=<hex>. Receivers must reject |now - t| > 300 seconds (5 minutes).
Request body
url(string, required)
Responses
#### HTTP 200
Created
Body schema: WebhookCreateResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)id(string, required)url(string, required)secret(string, required)created_at(string, required)
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401.
GET /v1/webhooks/deliveries
Delivery log for this account.
Responses
#### HTTP 200
Log
Body schema: WebhookDeliveriesResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)deliveries(array, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 401.
POST /v1/webhooks/deliveries/{id}/replay
Re-send one signed payload.
Parameters
id(path, required): string
Responses
#### HTTP 200
Replayed
Body schema: WebhookReplayResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)id(string, required)endpoint_id(string, optional)event(string, optional)status(string, required)attempts(integer, required)last_error(string | null, optional)replay_of(string | null, optional)created_at(string, optional)delivered_at(string | null, optional)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 404
Unknown delivery
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 401, 404.
POST /v1/webhooks/dispatch
Fan out a rule_set.changed event to every endpoint on this account.
Request body
from(string, required)to(string, required)
Responses
#### HTTP 200
Deliveries
Body schema: WebhookDispatchResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)from(string, required)to(string, required)changed(array, required)deliveries(array, required)
#### HTTP 400
Rejected request
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 404
Unknown rule set
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 400, 401, 404.
DELETE /v1/webhooks/{id}
Delete a webhook endpoint on this account. Unknown or foreign IDs are 404.
Parameters
id(path, required): string
Responses
#### HTTP 200
Deleted
Body schema: WebhookDeleteResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)deleted(boolean, required)id(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 404
Unknown webhook
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 401, 404.
GET /v1/webhooks/{id}
One webhook endpoint on this account. Unknown or foreign IDs are 404.
Parameters
id(path, required): string
Responses
#### HTTP 200
Endpoint
Body schema: WebhookGetResponse
rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)id(string, required)url(string, required)created_at(string, required)
#### HTTP 401
Missing or unknown key
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
#### HTTP 404
Unknown webhook
Body schema: ErrorEnvelope
error(ErrorBody, required)rule_set_version(string | null, required)engine_version(string, required)engine_build_sha256(string, required)
Error responses on this route
HTTP statuses that return ErrorEnvelope: 401, 404.
Error codes
Every error JSON matches ErrorEnvelope: error (code, message, docs) plus rule_set_version, engine_version, and engine_build_sha256.
batch_too_large
HTTP 400.
Cause: POST /v1/deductions/batch carried more than 1000 requests.
What to do: Split the pay run into chunks of at most 1000 calculations.
date_out_of_range
HTTP 400.
Cause: as_of falls before the first enacted rule set or after the last covered date.
What to do: Pick a supported calendar date. The API does not snap to the nearest rule set.
engine
HTTP 500.
Cause: An internal engine or health probe failure occurred.
What to do: Retry once. If it persists, contact support with rule_set_version and engine_build_sha256 from the envelope.
invalid_request
HTTP 400.
Cause: The JSON parsed but violates request rules (shape, enums, or business constraints).
What to do: Read error.message. Fix the named constraint and retry.
jurisdiction_not_supported
HTTP 422.
Cause: The province code is not calculated (for example QC).
What to do: Use GET /v1/jurisdictions. Do not treat a partial total as Quebec payroll tax.
malformed_json
HTTP 400.
Cause: The body is not valid JSON or exceeds depth or size limits.
What to do: Fix syntax. Send application/json. Keep payloads within documented limits.
method_not_allowed
HTTP 405.
Cause: The HTTP method does not match the route contract.
What to do: Use the method shown in this reference for the path.
not_found
HTTP 404.
Cause: The path, rule version, webhook id, or delivery id does not exist for this account.
What to do: Fix the URL or id. Cross-account webhook access returns 404, not 403.
payload_too_large
HTTP 413.
Cause: The request body exceeded the Worker byte limit.
What to do: Shrink the batch or payload. Prefer batch for many employees.
plan_limit
HTTP 402.
Cause: The live key reached its UTC-month calculation cap.
What to do: Upgrade via POST /v1/billing/checkout or wait until X-Usage-Reset. Failed calls are not billed.
rate_limited
HTTP 429.
Cause: Too many HTTP requests in the rate-limit window for this route or IP.
What to do: Backoff and retry. Metering limits are separate; see rate-limits-and-billing.md.
rule
HTTP 400.
Cause: The engine rejected rule lookup or diff input.
What to do: Confirm rule set versions with GET /v1/rules. Use enacted dates only unless testing proposed sets.
store
HTTP 503.
Cause: Account, usage, or key storage failed (fail closed).
What to do: Retry later. Live metering outages do not serve unmetered calculations.
unauthorized
HTTP 401.
Cause: Missing Bearer key, wrong prefix, or revoked secret.
What to do: Send Authorization: Bearer np_test_… or np_live_…. Rotate at POST /v1/keys/rotate if lost.
unknown_field
HTTP 400.
Cause: The JSON body includes a property name the engine does not recognize.
What to do: Remove or rename the field. See request-fields.md for the allowed set.
unknown_plan
HTTP 400.
Cause: Checkout plan is not starter, growth, or business.
What to do: Send a published plan name from docs/pricing.md.
unsupported_media_type
HTTP 415.
Cause: Content-Type was not application/json where required.
What to do: Set content-type: application/json on POST bodies.
Last reviewed: 2026-09-20
Engine: 0.1.0