{
  "version": "api-versions-2026.1",
  "documentationUrl": "https://apis.allanninal.dev/tariff/versioning.html",
  "current": "v1",
  "note": "The contract version in the URL path changes ONLY on a breaking change. Dataset versions move independently and are never breaking on their own. The full algorithm is in VERSIONING.md; this file is the machine-readable form of it, served at /tariff/api/v1/versions.json and enforced by scripts/check-contract.mjs.",
  "policy": {
    "breakingChangeRequires": "a new path version (/v2). The previous version keeps serving.",
    "minimumSupportWindowMonths": 12,
    "minimumDeprecationNoticeMonths": 6,
    "deprecationSignals": [
      "Deprecation header (RFC 9745) carrying the date the version was deprecated",
      "Sunset header (RFC 8594) carrying the date it stops serving",
      "Link header with rel=\"deprecation\" pointing at the migration notes",
      "a deprecation block in every JSON response body"
    ],
    "breaking": [
      "removing or renaming a response field",
      "changing the type or units of a response field",
      "removing an endpoint, or changing its path",
      "adding a required request parameter",
      "tightening validation so a previously accepted request now fails",
      "changing a default value, page size or sort order",
      "changing the meaning of an existing field without renaming it",
      "changing the shape of a record inside a published dataset"
    ],
    "additive": [
      "adding a new endpoint",
      "adding a new response field",
      "adding a new OPTIONAL request parameter",
      "adding a new enum value to a field documented as open",
      "widening validation so a previously rejected request now succeeds",
      "correcting data values, or refreshing a dataset to a newer upstream edition",
      "a new HTS revision changing published duty rates — the rates are data, not contract"
    ]
  },
  "versions": [
    {
      "version": "v1",
      "status": "current",
      "releasedOn": "2026-09-20",
      "deprecatedOn": null,
      "sunsetOn": null,
      "basePath": "/tariff/v1",
      "changelog": [
        {
          "release": "1.1.0",
          "date": "2026-09-21",
          "kind": "minor",
          "summary": "Scope widened from three wine headings to the complete Harmonized Tariff Schedule — all 98 chapters, 29,860 lines — plus CBP ports of entry. Additive: no v1 field changed type, moved or disappeared.",
          "changes": [
            "GET /v1/tariffs — the whole schedule, not headings 2204/2205/2206. New filter `chapter`; `heading` is now validated by shape (4 digits) instead of membership in a three-item list, which WIDENS what is accepted; `rateKind` gained cross-reference, sliding-scale, note-reference, conditional-free and apportioned.",
            "POST /v1/duty-estimate — accepts `quantity` + `quantityUnit` (and `quantities` for a rate assessed on two units at once) in any unit the schedule uses. `litres` still works and means the same thing. `litres` is NO LONGER REQUIRED, which is a widening: a request that used to 400 now succeeds.",
            "POST /v1/duty-estimate — new response fields `requiredUnit` and `quantities`. A rate that needs a quantity the request does not carry now refuses by naming the unit, where it previously answered `unsupported unit: kg`.",
            "GET /v1/ports, GET /v1/ports/{code} — new. CBP Schedule D customs districts and ports of entry.",
            "GET /api/v1/ports.json, /api/v1/chapters/{NN}.json, /api/v1/chapters/index.json — new static files. The whole schedule is also still published at /api/v1/hts-schedule.json, unchanged in shape.",
            "Tariff-line records gained `parsedOk` and `inheritedColumns`. `parsed` is unchanged and still published everywhere it was.",
            "meta/scope gained `chapters`, `description`, `meaningNote` and `notComputed`. `scope.meaning` keeps its three heading entries and its type."
          ],
          "fixes": [
            "A rate assessed on two or more components — \"3.9% on the apparatus + 5.3% on the battery\" — kept only the LAST percentage and charged it against the whole customs value. These are now `apportioned` and refuse to price at all: apportioning a customs value between movement, case and battery is a classification act, not arithmetic. 188 rate cells.",
            "Rate inheritance skipped any line carrying a rate in ANY column, so a statistical suffix publishing its own column-2 rate was left with no column-1 rate. Inheritance is per column now.",
            "`parsedOk` was computed before inheritance ran, so ~16,000 inherited lines reported that their rate string had defeated the parser when it had not.",
            "Sliding-scale, cross-reference, note-reference and conditional-free rates parsed successfully and then summed to a duty of $0 — on 359 sugar lines among others. They refuse with a reason now; `dutyUsd: 0` only ever means Free.",
            "The Schedule D parser accepted the source file's own footer as a port, publishing a record whose code was \"U.S. Census Bureau\".",
            "api/v1/duty-rates.json published `terms: []` on every row: it read a `parsed` block the build had stopped storing.",
            "The landing page and its structured data linked api/v1/hts-schedule.json while the build was not writing it."
          ]
        },
        {
          "release": "1.0.0",
          "date": "2026-09-20",
          "kind": "initial",
          "summary": "Tariff tier: the Harmonized Tariff Schedule for wine, duty estimation, Federal Register tariff actions, and the revision archive.",
          "changes": [
            "GET /v1/tariffs, /v1/tariffs/{htsno} — headings 2204, 2205, 2206 with rates parsed into computable terms",
            "GET /v1/tariffs/release — the live HTS revision",
            "GET /v1/chapter99 — the additional-duty headings, listed and never auto-applied",
            "POST /v1/duty-estimate — duty on a shipment, with the arithmetic shown",
            "GET /v1/countries, /v1/countries/{code} — Schedule C with trade-programme eligibility",
            "GET /v1/notices — Federal Register tariff actions reaching wine",
            "GET /v1/customs-rules — 19 CFR citations for entry and classification",
            "GET /v1/changes, /v1/history/coverage — the HTS revision archive",
            "GET /v1/sources — every upstream, and what was evaluated and rejected",
            "GET /v1/plans, /v1/usage, /v1/version"
          ]
        }
      ]
    }
  ],
  "datasetVersioning": {
    "scheme": "<source>-<year>.<edition>",
    "example": "usitc-hts-2026.1",
    "rule": "A dataset version increments when the upstream data or the build that shapes it changes. Every response carries datasetVersions so a caller can pin, cache and reproduce. Refreshing a dataset is ADDITIVE: values may change, record shape may not. A change to the SHAPE of a dataset record is a breaking API change and takes a new path version.",
    "htsReleaseIsSeparate": "htsRelease (e.g. 2026HTSRev19) is USITC's own name for the schedule edition, carried verbatim. It is not this service's version number and moves on USITC's schedule, several times a year.",
    "reproducibility": "Any response can be reproduced from its datasetVersions plus the build in this repo at the matching tag."
  }
}
