> ## 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.

# Retrieve calculations

> Fetch a stored calculation, list with filters, and paginate by cursor

Four read endpoints. All of them are scoped to your `client_id` and all of them serve
stored snapshots — nothing is recalculated on read.

| Method and path                               | Returns                                                             |
| --------------------------------------------- | ------------------------------------------------------------------- |
| `GET /v1/payroll/calculations/{id}`           | One calculation, exactly as it was answered                         |
| `GET /v1/payroll/calculations`                | A filtered, cursor-paginated list                                   |
| `GET /v1/payroll/batches/{id}`                | A batch run: totals, successful rows, failed rows with their errors |
| `GET /v1/employers[/{id}[/employees[/{id}]]]` | Employers and employees, registered implicitly                      |

## Retrieve one calculation

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.clevis.dev/v1/payroll/calculations/01HX9B2KM3V4W5X6Y7Z8A9B0CD \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import httpx

  calculation = httpx.get(
      "https://api.clevis.dev/v1/payroll/calculations/01HX9B2KM3V4W5X6Y7Z8A9B0CD",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
  ).json()

  print(calculation["summary"]["net_salary"])
  ```

  ```javascript Node.js theme={null}
  const calculation = await fetch(
    "https://api.clevis.dev/v1/payroll/calculations/01HX9B2KM3V4W5X6Y7Z8A9B0CD",
    { headers: { Authorization: "Bearer YOUR_API_KEY" } },
  ).then((r) => r.json());

  console.log(calculation.summary.net_salary);
  ```
</CodeGroup>

The body is the **same document** you received when you created it, including `record`,
`warnings` and `period_basis`.

### Including the audit trail

The trail is stored whether or not you asked for it at calculation time. Ask for it on
read with `?include=audit_trail`:

```bash theme={null}
curl "https://api.clevis.dev/v1/payroll/calculations/01HX9B2KM3V4W5X6Y7Z8A9B0CD?include=audit_trail" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

<Note>
  Unknown ids, and ids belonging to another client, both return `404 CALCULATION_NOT_FOUND`.
  The two cases are indistinguishable on purpose.
</Note>

## List calculations

| Filter           | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `employer_id`    | Only this employer's calculations                      |
| `employee_id`    | Only this employee's calculations                      |
| `country`        | `MX`, `CO`, `AR`, `BR`, `CL`, `PE`                     |
| `batch_id`       | Only rows from this batch run                          |
| `period_from`    | Periods starting on or after this date                 |
| `period_to`      | Periods starting on or before this date                |
| `limit`          | Page size                                              |
| `starting_after` | Cursor: the `id` of the last item on the previous page |

<CodeGroup>
  ```bash cURL theme={null}
  curl -G https://api.clevis.dev/v1/payroll/calculations \
    -H "Authorization: Bearer YOUR_API_KEY" \
    --data-urlencode "employer_id=employer_001" \
    --data-urlencode "country=CO" \
    --data-urlencode "period_from=2026-01-01" \
    --data-urlencode "period_to=2026-09-30" \
    --data-urlencode "limit=50"
  ```

  ```python Python theme={null}
  import httpx

  page = httpx.get(
      "https://api.clevis.dev/v1/payroll/calculations",
      headers={"Authorization": "Bearer YOUR_API_KEY"},
      params={
          "employer_id": "employer_001",
          "country": "CO",
          "period_from": "2026-01-01",
          "period_to": "2026-09-30",
          "limit": 50,
      },
  ).json()
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    employer_id: "employer_001",
    country: "CO",
    period_from: "2026-01-01",
    period_to: "2026-09-30",
    limit: "50",
  });

  const page = await fetch(
    `https://api.clevis.dev/v1/payroll/calculations?${params}`,
    { headers: { Authorization: "Bearer YOUR_API_KEY" } },
  ).then((r) => r.json());
  ```
</CodeGroup>

### Cursor pagination

Pass the `id` of the last item you saw as `starting_after`. Because ids are ULIDs, sorting
by `id` sorts by time — the cursor is stable even while new calculations are being written.

<CodeGroup>
  ```python Python theme={null}
  cursor, all_rows = None, []

  while True:
      page = httpx.get(
          "https://api.clevis.dev/v1/payroll/calculations",
          headers={"Authorization": "Bearer YOUR_API_KEY"},
          params={"employer_id": "employer_001", "limit": 100,
                  **({"starting_after": cursor} if cursor else {})},
      ).json()

      all_rows += page["data"]
      if not page["has_more"]:
          break
      cursor = page["data"][-1]["id"]
  ```

  ```javascript Node.js theme={null}
  let cursor = null;
  const allRows = [];

  for (;;) {
    const params = new URLSearchParams({ employer_id: "employer_001", limit: "100" });
    if (cursor) params.set("starting_after", cursor);

    const page = await fetch(
      `https://api.clevis.dev/v1/payroll/calculations?${params}`,
      { headers: { Authorization: "Bearer YOUR_API_KEY" } },
    ).then((r) => r.json());

    allRows.push(...page.data);
    if (!page.has_more) break;
    cursor = page.data[page.data.length - 1].id;
  }
  ```
</CodeGroup>

### Superseded calculations

A calculation that has been replaced by a later one is flagged `is_superseded` in the
listing. It is never removed and never modified. See
[Corrections](/calculations/corrections).

## Retrieve a batch

```bash theme={null}
curl https://api.clevis.dev/v1/payroll/batches/01HX9B2KM3V4W5X6Y7Z8A9B0CD \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Returns the batch totals, every successful row, and the failed rows **with the error that
failed them** — which is what you need to fix a partial run without re-sending the rows
that worked.

## Employers and employees

Both register themselves the first time you send their id. There is no create endpoint.

```bash theme={null}
# every employer you have ever calculated for
curl https://api.clevis.dev/v1/employers \
  -H "Authorization: Bearer YOUR_API_KEY"

# one employer's employees
curl https://api.clevis.dev/v1/employers/employer_001/employees \
  -H "Authorization: Bearer YOUR_API_KEY"

# one employee
curl https://api.clevis.dev/v1/employers/employer_001/employees/emp_001 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Both list endpoints take `limit` and `starting_after` and paginate the same way.

<Note>
  `employee.id` is unique **within its employer**, so `emp_001` must always be addressed
  through its `employer_id`. An unknown employer returns `404 EMPLOYER_NOT_FOUND`; an unknown
  employee under a known employer returns `404 EMPLOYEE_NOT_FOUND`.
</Note>
