Skip to content

Versioning and deprecation

/v1 is the public contract of the HodeiShield API and it is frozen: what is published under /v1 does not change in an incompatible way. This page summarises the policy as the OpenAPI document itself publishes it (GET /v1/openapi.json), which prevails if they differ.

How the version is identified

  • The path carries the major version: /v1/….
  • info.version of the OpenAPI document is the contract revision, shaped like a date. The current one is 2026-10-29. It moves when the published contract gains an addition or a correction.
  • The Contract changes section of the OpenAPI document lists every addition and correction, newest first, with the revision it arrived in. To know whether a capability is present, check whether the operation exists (for example GET /v1 lists listPortfolioTenants when the portfolio contract is available) instead of comparing version strings.

Recent contract changes

  • 2026-10-29, addition: a warning when a filter value is not a published one. severity on GET /v1/alerts, domain and status on GET /v1/risks and status on GET /v1/compliance/controls match exactly, so a mistyped value (severity=critcal) used to return an empty list that looked like "no results". The request is still served as sent, with 200 and the same items: no parameter accepts less than before. When the value is outside the parameter's x-hs-known-values, the response also carries the optional key meta.warnings[] (param, value, known) and the X-HS-Unknown-Filter header, which names the parameter. With no unknown value, neither appears. category on GET /v1/vendors is free text and is not checked.
  • 2026-10-27, correction: a scope needs its module active. A tenant API key or a portfolio key used to be served on its scope alone, even when the tenant no longer had that scope's module active. Every credential now gets 403 with error.details.code MODULE_NOT_ENTITLED in that case, and a compliance.<framework> scope also needs the framework activated for the tenant. A request that used to answer 200 now answers 403. The same revision adds GET /v1/changes (scope changes:read, read-only).
  • 2026-10-26, change: user.email needs the email scope. GET /v1/me returns user.email only to an OAuth access token that carries the new email scope, and omits it otherwise; the call is never refused for lacking it. Treat user.email as optional.

Compatible changes (happen inside /v1, without prior notice)

  • a new operation;
  • a new optional query parameter;
  • a new field in a response object;
  • a new value in a field documented as an open vocabulary (for example status, severity, evidence_type);
  • a new error.code, always with the HTTP status its siblings already use;
  • anything under error.details, which is diagnostic and not part of the contract, except error.details.code and the keys the error table of each operation names.

Write your client so these do not break it: ignore unknown fields, do not switch exhaustively on an open vocabulary, and treat an unknown error.code as "an error of this HTTP status".

Incompatible changes (do not happen in /v1)

They go to a new version (/v2), and /v1 keeps being served:

  • removing or renaming an operation, a parameter or a field;
  • changing the type, the unit or the meaning of a field;
  • making an optional parameter required, or accepting less than it accepted;
  • changing which scope an operation requires;
  • changing the HTTP status an existing error.code is sent with.

Unknown filter values

A filter with a published vocabulary (x-hs-known-values) does not refuse an unknown value: the request answers 200 with whatever matches, which is almost always nothing. So that this is not mistaken for "no results", the response carries meta.warnings[] with one { "param", "value", "known" } element per unknown filter, and the X-HS-Unknown-Filter header with the parameter names separated by commas. It applies to alert severity, risk domain and status, and compliance control status; the retired Spanish severity values raise no warning. The obsolete Warning header is not used.

Retired values

A value retired from a parameter is listed in the OpenAPI document with the extension x-hs-deprecated-values, and the date until which the server is documented to accept it in x-hs-deprecated-values-sunset. There is one case today: the Spanish values of the alert severity filter (critico, alto, medio, bajo) are documented as accepted until 2027-01-14; move to the English values (critical, high, medium, low, info) before that date.

What is not part of the frozen contract

  • Rate limits may be raised without notice. See Rate limits.
  • Webhook signature headers have their own lifecycle: the legacy X-HodeiShield-Signature will be retired. See Webhooks.

What this policy does not establish

The contract does not state how long /v1 will keep being served once a /v2 exists, nor a notice period for retiring it. Do not read this page as a commitment on either.