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

# Pinning a Ruleset Version

> Reproduce a tax calculation against the exact rules that produced it

export const ApiExample = ({method = "GET", path, params, body, reference, tenantKey}) => {
  const baseUrl = "https://api.cadanapay.com";
  const query = params ? "?" + Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&") : "";
  const url = `${baseUrl}${path}${query}`;
  const isPlatformPath = (/^\/(v1\/)?platform(\/|$)/).test(path || "");
  const includeTenantKey = tenantKey === undefined ? !isPlatformPath : tenantKey;
  let curl = `curl -X ${method} '${url}' \\\n  -H 'Authorization: Bearer YOUR_API_KEY'`;
  if (includeTenantKey) {
    curl += ` \\\n  -H 'X-MultiTenantKey: YOUR_BUSINESS_TENANT_KEY'`;
  }
  if (body) {
    const formatted = JSON.stringify(body, null, 2);
    curl += ` \\\n  -H 'Content-Type: application/json' \\\n  -d '${formatted}'`;
  }
  return <div>
      <CodeBlock language="bash" filename="bash" wrap>
        {curl}
      </CodeBlock>
      {reference && <div style={{
    marginTop: "-0.5rem",
    marginBottom: "1rem"
  }}>
          <a href={reference} style={{
    fontSize: "0.875rem"
  }}>
            Try it in the playground →
          </a>
        </div>}
    </div>;
};

Tax rules change. A calculation you ran last quarter will not necessarily reproduce today, because the country's ruleset has moved on. Pinning lets you name the exact ruleset a calculation should use, so a figure you showed a customer, filed, or stored can be recomputed and audited later.

By default every calculation uses the current ruleset. That is the right choice for new work — pin only when you need a result to stay stable.

***

## Find a version to pin

Ask which rulesets a country has:

<ApiExample method="GET" path="/v1/tax/versions" params={{ country: "MX" }} reference="/api-reference/tax/tax-calculator/list-tax-ruleset-versions" />

**Response:**

```json theme={null}
{
  "data": [
    { "country": "mx", "version": "v2.1.0", "effectiveFrom": "2026-01-01", "isLatest": true, "pinnable": true },
    { "country": "mx", "version": "v2.0.3", "updatedAt": "2026-08-10T06:42:54Z", "pinnable": false }
  ]
}
```

`isLatest` marks the ruleset a request uses when you do not pin. `pinnable` tells you whether a version can be requested by name.

<Warning>
  Only pin a version with `pinnable: true`. Rulesets published before versioning was introduced cannot be reproduced — the rules they depended on were never captured — so requesting one returns a 400 rather than a result computed from the wrong rules.
</Warning>

***

## Pin a calculation

Pass `version` alongside your usual request body:

<ApiExample method="POST" path="/v1/tax/calculate" body={{ payeeId: "123e4567-e89b-12d3-a456-426614174000", salary: { amount: 1000000, currency: "MXN" }, version: "v2.1.0" }} reference="/api-reference/tax/tax-calculator/calculate-taxes" />

The same field works on [Estimate Gross or Net Salary](/api-reference/tax/tax-calculator/estimate-gross-or-net-salary) and [Validate Tax Fields](/api-reference/tax/tax-calculator/validate-tax-fields), and as a `version` query parameter on [List Required Additional Tax Fields by Country](/api-reference/tax/tax-calculator/list-required-additional-tax-fields-by-country).

A pinned response echoes the version it used:

```json theme={null}
{
  "grossAmount": { "amount": 1000000, "currency": "MXN" },
  "netAmount": { "amount": 955743, "currency": "MXN" },
  "rulesetVersion": "v2.1.0"
}
```

<Note>
  `rulesetVersion` appears only when you pin. Its absence means the current ruleset was used.
</Note>

***

## Pin the fields too

The inputs a country requires change between versions, so a stored payload can stop matching the ruleset it was collected for. Pass the same `version` to the fields endpoint to see the inputs as they were:

<ApiExample method="GET" path="/v1/tax/fields" params={{ country: "MX", version: "v2.1.0" }} reference="/api-reference/tax/tax-calculator/list-required-additional-tax-fields-by-country" />

The response carries `version`, `effectiveFrom` and `pinnable` alongside the field list, so you can record which contract a stored payload was built against.

***

## When a pin is rejected

Pinning never falls back to current rules — a request that cannot be honoured fails instead of quietly returning a different answer. Each case returns a 400:

| Response                                                                                            | Meaning                                                                                                                           |
| :-------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
| `ruleset version "v9.9.9" is not available for MX`                                                  | No such version for this country. Check [List Tax Ruleset Versions](/api-reference/tax/tax-calculator/list-tax-ruleset-versions). |
| `ruleset version "v1.0.0" cannot be pinned for CA: it was published before rulesets were versioned` | The version exists but predates versioning, so it is reported as `pinnable: false`.                                               |
| `ruleset version pinning is not supported for GH`                                                   | This country's calculation does not read versioned rulesets, so a pin could not be applied.                                       |
| `version "latest" is not a valid ruleset version`                                                   | Malformed. Use a semver such as `v2.1.0` or `2.1.0`.                                                                              |

<Tip>
  Read `pinnable` from the versions response before pinning and you will avoid the first three entirely.
</Tip>

***

## Staying current

Pinning holds a calculation still; it does not keep you up to date. To see what changed since the version you are on, and whether the required fields moved:

<ApiExample method="GET" path="/v1/tax/changelog" params={{ country: "MX", fromVersion: "v2.0.0" }} reference="/api-reference/tax/tax-calculator/get-tax-ruleset-changelog" />

`actionRequired` tells you whether the tax field requirements changed, which is the case that needs code on your side rather than just a version bump.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Get Started" icon="play" href="/tax/getting-started">
    Run your first tax calculation
  </Card>

  <Card title="Tax API Reference" icon="code" href="/api-reference/tax/tax-calculator/list-tax-ruleset-versions">
    Full endpoint reference
  </Card>
</CardGroup>
