> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clevis.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Pay periods

> Fortnightly payroll in all six countries, and why prorating is not dividing

All six countries settle a fortnight. Send `period.type: "biweekly"` and two fortnights
add up to the month exactly, line by line.

<Tip>
  **Prorating is not dividing.** The engine settles the full month and splits the payment.
  It does not re-run the scheme over half a salary.
</Tip>

That distinction sounds semantic. It is the difference between a correct number and one
that is wrong by up to 21%.

## Why the obvious shortcut is wrong

"Send half the salary and multiply by two" gets the base salary right and gets **everything
with a floor, a ceiling or a bracket** wrong. Measured against the real engine, country by
country:

| Country | Error in net pay       | The extreme, and who it lands on                                        |
| ------- | ---------------------- | ----------------------------------------------------------------------- |
| **CO**  | +3.03% to +7.59%       | At 16 SMMLV withholding drops a marginal bracket: **−47%**              |
| **MX**  | +5.81% to +8.74%       | ISR under-withheld by 23–42%, and the subsidy is paid twice             |
| **CL**  | **−14.11%** to +10.80% | The negative lands on the **minimum wage**: the worker is paid 14% less |
| **AR**  | **−20.48%** to +3.16%  | The worst number in the series, also on the **minimum wage**            |
| **BR**  | 0.00% to **+21.13%**   | At R$9,000 the employee stops withholding R$1,294.55 **every month**    |
| **PE**  | −14.73% to +7.30%      | The annual taxable base drops **82.7%** at S/6,000                      |

<Warning>
  **The two most dangerous cases are the ones that come out right.** In Brazil the shortcut
  is exact to the centavo at the minimum wage — it is the only country in the series with no
  contribution floor. In Argentina it is exact to the peso at `2,000,000`.

  The error shows up at the edges of the payroll: **a minority of headcount, a majority of
  the money.** The natural smoke test — settle a minimum wage in two fortnights and check
  they add up — passes.
</Warning>

Peru gives the cleanest argument, and it is not about brackets. The IR de 5ta categoria is
an **annual** tax, projected and divided by twelve. Recalculating over half a salary does
not say "this worker is paid twice a month". It says **"this worker earns half as much per
year"**, which is simply false. In CO, CL or BR the shortcut misapplies a table. In PE it
asserts a false fact about annual income, and SUNAT sees it at the annual reconciliation.

## The model: settle the month, split the payment

The dependency graph runs **unchanged over the monthly figures**, and the split happens
afterwards. Everything non-linear is evaluated exactly where the law defines it: over the
month. **Not one formula in the rule files changed.**

| Model           | What it does                                                                              | Countries          |
| --------------- | ----------------------------------------------------------------------------------------- | ------------------ |
| `split_monthly` | The graph runs monthly and the **result** is split                                        | CO, CL, AR, BR, PE |
| `native`        | The formulas read `period_days` and prorate themselves, scaling their own caps and floors | MX                 |

**Mexico is the only `native` scheme**, because its formulas genuinely prorate
(`salario_base = daily_salary × period_days`) and the IMSS charges by real days. But the
ISR tariff and the subsidy are **tables, not formulas**, and a table cannot read
`period_days` — they were being applied to a base that had already been halved. The fix
**annualises**: apply the monthly table to the equivalent monthly base, then scale the
result back to the period. At `period_days = 30` the expression reduces to the original, so
the monthly case is identical **by construction**, not by test.

## The balancing rule — two fortnights add up to the month

CO, CL and AR round to whole units, so `2 × round(month/2)` is not `month`. Every period
but the last takes the rounded share, and the last takes what is left:

```
Q1 = round_half_up(month × 1/2)
Q2 = month − Q1
```

`Q1 + Q2 == month` holds **by construction**, and both are pure functions of the monthly
figure — the engine does not need to remember Q1 to compute Q2.

It applies **per line**, and the totals are derived by summing lines that have already
been split. A payslip cannot fail to reconcile with itself.

This is not accounting aesthetics. PILA, the F.931, Previred, eSocial and the PLAME are all
filed **monthly**. If the two fortnights do not add up to the month's contribution, you
have a per-employee, per-month difference with the authority.

### Subtotals are derived, not split

A concept that is the sum of others cannot be split on its own — it has to add up to what
its parts already received.

Without that rule, **21% of Chilean fortnights** produced a payslip whose "Total
Cotizaciones Previsionales" differed by one peso from the three lines above it. In
Argentina it would have been **63.6%**, because its three rates (11%, 3%, 3%) apply to the
same base.

The month always reconciled (`89,250 + 89,250 = 178,500`), which is exactly why no monthly
reconciliation ever caught it. What did not reconcile was **each individual payslip**.

### Mexico has no balancing rule, and should not

Under `native`, each period is settled on its own days: a 14-day period and a 16-day period
are not two halves of anything. They land within **one cent per line** of the month (three
on aggregates) — a measured, declared tolerance.

## The response contract: `period_basis`

Additive, and **emitted on monthly calculations too**, so your client never has to branch
on periodicity.

```json theme={null}
"period_basis": {
  "type": "biweekly",
  "sequence": 1,
  "periods_in_month": 2,
  "factor": "0.5",
  "proration": "split_monthly",
  "absorbs_residual": false,
  "monthly_equivalent": {
    "total_perceptions": "2000000",
    "total_deductions": "140072",
    "net_salary": "1859928",
    "employer_contributions_total": "545000",
    "employer_total_cost": "2545000"
  }
}
```

| Field                | Meaning                                                           |
| -------------------- | ----------------------------------------------------------------- |
| `type`               | The frequency these figures were settled for                      |
| `sequence`           | Which sub-period of the month this is, 1-based                    |
| `periods_in_month`   | `1` for monthly, `2` for biweekly                                 |
| `factor`             | This period's nominal share of the month                          |
| `proration`          | `native` or `split_monthly`. Null when the scheme is monthly-only |
| `absorbs_residual`   | `true` on the period carrying the rounding remainder              |
| `monthly_equivalent` | What the whole month came to                                      |

A monthly calculation reports `sequence: 1`, `periods_in_month: 1`, `factor: "1"` and a
`monthly_equivalent` equal to its own summary.

`monthly_equivalent` is what you need to **reconcile against the monthly filing**. Without
it, a fortnightly response cannot be audited without recalculating — or worse, by adding up
two fortnights and hoping the rounding agreed.

## The sequence is derived, not requested

`period.start_date` on day 1–15 is the first fortnight; day 16 onward is the second. **No
new request field**: the data was already travelling. A company that pays on the 20th and
the 5th is classified correctly with nothing to configure.

<Warning>
  **`period.days` is inert under `split_monthly`.** The factor comes from `period.type`,
  because on a 30-day commercial month a fortnight is half a month whether the stretch is 14,
  15 or 16 days.

  If `days` is not 15, the response carries a `PERIOD_DAYS_IGNORED`
  [warning](/guides/warnings) rather than staying silent. Outside Mexico, `period.days` does
  not prorate anything — claiming otherwise repeats exactly the misunderstanding this
  behaviour exists to close.
</Warning>

## What a fortnight is not

This is the part that gets misread, and misreading it costs real money.

| Country | What the engine does | What it is **not**                                                                                                                                        |
| ------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AR      | Half a settlement    | The **anticipo of LCT Art. 130** (an amount the employer sets, with no contributions, deducted at month end), nor the *jornalizada* fortnight of Art. 126 |
| BR      | Half a settlement    | The ***adiantamento quinzenal*** — the "vale", \~40% of gross, with no INSS and no IRRF                                                                   |
| CL      | Half a settlement    | The salary advance of CT Art. 55                                                                                                                          |
| PE      | Half a settlement    | An advance                                                                                                                                                |

If you pay **advances**, keep settling `monthly` and treat the advance as a deduction in
your own system. Its amount is set by the employer and its balance carries state between
periods, which is exactly what a stateless engine cannot hold.

## Weekly is rejected, in all six countries

The argument is **arithmetic, not legal**: a month does not contain a whole number of
weeks, so the balancing rule has nothing to stand on, and four weeks do not add up to a
month against the monthly filing. Argentine and Brazilian law do recognise weekly pay. It
makes no difference here.

A `period.type` the scheme does not support returns `422 PERIOD_TYPE_NOT_SUPPORTED`, with
`country`, `scheme`, `year`, `period_type` and `supported_period_types` in `details`.

**A batch fails whole**, not row by row: a period no row can settle is not one row's error.

## Example

```bash theme={null}
curl -X POST https://api.clevis.dev/v1/payroll/calculate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "CO",
    "scheme": "ordinario",
    "year": 2026,
    "period": {
      "type": "biweekly",
      "start_date": "2026-09-01",
      "end_date": "2026-09-15",
      "days": 15
    },
    "employee": {
      "id": "emp_001",
      "daily_salary": 66667,
      "monthly_salary": 2000000,
      "risk_class_rate": 0.00522
    },
    "employer": { "id": "employer_001" }
  }'
```

Discover what a scheme supports with `GET /v1/payroll/schemes/{country}`, which reports
`supported_period_types` per scheme.
