Year projection
Who this is for: Developers calling POST /v1/deductions/year.
When you finish: You know how YTD carry-forward binds CPP and EI caps, when CPP2 (second additional CPP contribution) starts after YMPE (Year's Maximum Pensionable Earnings), and why the federal tax sum can differ from annual T1 by at most P cents.
POST /v1/deductions/year runs every pay period in the year, carrying YTD CPP, CPP2, EI, and pensionable / insurable earnings forward. Each cheque is still T4127 (Payroll Deductions Formulas) Option 1 on that period’s gross. The YTD fields are what make the annual CPP and EI maxima bind, and what delay CPP2 until pensionable earnings have crossed YMPE (spec §21.5).
Fields
The body is first read exactly like POST /v1/deductions: an unknown field is unknown_field, an invalid value is the same HTTP 400 naming it. Then:
-
Applied to every period exactly as
POST /v1/deductionsapplies them to one cheque:province,gross_pay,pensionable_earnings,insurable_earnings,federal_claim_code,provincial_claim_code,federal_tc,provincial_tcp,cpp_months,cpp_exempt,k2_method,rounding_compat,taxable_benefits,rpp,union_dues,alimony,child_care_expenses,prescribed_zone_deduction,additional_tax_requested,lcf_purchase,lcp_purchase,dependants_under_19,dependants_disabled, andcalculation_optionoption1. Without a claim code (the TD1 box value) or a claim amount, the claim code is 1. -
Refused with HTTP 400
invalid_requestnaming the field and the reason: the year-to-date totals andpay_periods_elapsed(the projection starts them at zero and carries them itself), the bonus and retroactive pay fields and abonus_methodother thanregular(paid once, not every period), the commission fields, the Quebec and change-of-province fields,date_of_birthandcpp_election_after_65(the engine reads them nowhere, so every route refuses them), andcalculation_optionoption2(it needs the tax withheld so far). For an employee already paid this year, callPOST /v1/deductionsfor each remaining cheque with the year-to-date amounts.
Pay dates
as_of is the first pay date; without it the year starts on 1 January of the current UTC year. pay_period is one of 1, 2, 4, 10, 12, 13, 22, 24, 26 or 27: a projection runs at most 27 pay periods. The hosted API runs on Cloudflare Workers Free, which allows 10 ms of CPU a request, so a weekly, daily or hourly year (52, 53, 240 or 2000) is HTTP 400 year_too_large and nothing is calculated or counted; send those pay periods to POST /v1/deductions/batch instead, at most 25 a call, carrying the year-to-date totals yourself. Monthly keeps the day of the month (the last day of a shorter month); the rest step a fixed number of days (14, 15, 16, 28, 36, 91 or 182). Every pay date must fall in the first pay date's calendar year, because the CPP and EI maximums restart on 1 January; a year that would run past 31 December is HTTP 400 naming the latest first pay date that fits.
A projection runs at most 27 periods (28 engine calculations with the check of the body). A live key is billed one calculation per period; a test key may run at most 5,000 calculations a minute per account.
Federal tax sum versus annual T1
The year response includes annual_t1 (the first period’s Option 1 annual_projection.federal_tax, T4127 T1) and totals.federal_tax (the sum of the per-period federal tax lines).
Those two figures are not required to be equal. Each period’s federal tax is round_tax_to_cent((T1)/P): T4127 Chapter 2 / Step 6, per-period rounding. Adding P independently rounded cheques back up is a different operation from T1.
The documented tolerance is one cent per pay period (P cents; 0.26 on a 26-biweekly year). That is federal_tax_sum_tolerance in the JSON. The gap exists because of per-period rounding, not because the engine used a different T1.
Caps and crossings
-
cpp_cap_period/ei_cap_period: 1-based period where the annual maximum is reached. Later periods withhold0.00.nullif the year never reaches the maximum. -
ympe_cross_period: 1-based period where year-to-date pensionable earnings first exceed YMPE. -
cpp2_start_period: first period with non-zero C2. It is never beforeympe_cross_period.
53-week and 27-biweekly years
P = 53 and P = 27 are legal (spec §21.10). The CPP basic exemption is truncate_exemption_to_cent(3500 / P) from T4127 Chapter 6: $66.03 weekly-in-a-53-week-year, $129.62 on a 27-pay biweekly year. A 27-pay biweekly year runs as a projection; a 53-week year is over the 27-period cap (year_too_large), so calculate its cheques with POST /v1/deductions or a batch.
Last reviewed: 2026-10-24
Engine: 0.1.0