Takehome

Errors

Who this is for: Integrators who branch on HTTP status and error.code instead of parsing free-text messages.

When you finish: You know why Takehome rejects bad input, why Quebec never returns a fake zero-tax stub, and why unknown JSON keys fail fast.

A claim code is the TD1 box value that maps personal amounts to federal and provincial credits. Errors never return partial employee amounts.

Every error returns ErrorEnvelope:

`json

{

"error": {

"code": "date_out_of_range",

"message": "as_of 2025-12-31 is before coverage begins on 2026-01-01.",

"docs": "https://takehome.gautamkhosla.com/what-is-the-t4127"

},

"rule_set_version": null,

"engine_version": "0.1.0",

"engine_build_sha256": "b8a89f98fa2fcf25715aa6171bdf0b2d2b00ad2fe595b1187c119bb7cd9c77bd"

}

`

error always has exactly code, message, and docs (HTTPS link). There are no stack traces, SQL fragments, or internal paths in the body.

Design choices

Typed codes, stable HTTP mapping

Each failure maps to one error.code and a documented HTTP status. Handlers use classifyEngineError so engine messages about unknown fields become unknown_field and date coverage gaps become date_out_of_range. Clients should switch on code, not substring match message.

Quebec is unsupported, not zero tax

province: "QC" returns jurisdiction_not_supported (HTTP 422). Takehome does not return federal-only tax with provincial_tax forced to zero. QPIP (Québec Parental Insurance Plan) and QPP are not approximated. Billing does not count a failed QC call as a successful live calculation.

Dates are rejected, not snapped

When as_of is before the first enacted rule set or after coverage ends, the API returns date_out_of_range (HTTP 400). It does not pick the closest January or July edition. The same strict behavior applies to GET /v1/rules/{version} for unknown versions (not_found, HTTP 404).

Unknown fields are rejected, not ignored

Extra JSON keys fail deserialization with unknown_field (HTTP 400). The engine names the typo in message. Silent ignore would let integrators ship wrong withholding for weeks before anyone notices.

Fail closed on metering storage

If usage storage fails, live keys receive store (HTTP 503) and no calculation is served. See ADR-005. Test keys stay unmetered but still need a working engine.

Batch partial success

POST /v1/deductions/batch returns HTTP 200 when the envelope is valid even if some items fail. Each slot is { "ok": true, "response": … } or { "error": { … } }. Whole-batch rejection happens for batch_too_large, plan_limit, or malformed batch shape.

Common codes

Code HTTP Typical cause
unknown_field 400 Typo in JSON property name
invalid_request 400 Illegal pay period, ambiguous claim code plus TC, bad webhook URL
date_out_of_range 400 as_of outside enacted coverage
jurisdiction_not_supported 422 Quebec or other unsupported code
unauthorized 401 Missing or revoked API key
plan_limit 402 Live monthly calculation cap reached
rate_limited 429 Too many HTTP requests
engine 500 Internal engine failure (generic message)
store 503 Storage outage (metering or keys)

Full list with remediation steps: api-reference.md.

Batch item errors reuse the same error object shape but omit top-level identity inside the slot (only error on failure items).

Last reviewed: 2026-09-20

Engine: 0.1.0