Layers
Partner APIOperational

Versioning

What /v1/ promises, what counts as a breaking change, and our deprecation policy.

View as Markdown

We version in the URL. Everything lives under /v1/. Within a major, we commit to not breaking the contract — you can upgrade your library without changing your code.

This page is the shortest real commitment we're willing to make. Read it once and assume it holds until we say otherwise, with notice.

What /v1/ promises

For the lifetime of /v1/:

  • Field contracts are stable. Names don't change. Types don't change. Required fields stay required; optional fields stay optional.
  • Error codes are stable. The string codes in the errors catalog don't get renamed. APPROVAL_REQUIRED will always be APPROVAL_REQUIRED.
  • URLs are stable. /v1/projects/:id/ingest/github doesn't move to /v1/ingestions/github/:id without a new major version.
  • Semantics are stable. POST /v1/content/:id/approve will always flip approval state to approved. It won't secretly start meaning "submit for approval."

If we need to break any of those, we ship /v2/ alongside /v1/. No silent breaks, no "surprise" behavior changes.

Preview status

Pages tagged phase=1 are in the preview release. The /v1/ contract above applies to them from day one — "preview" is an ambient label indicating we're soaking a new surface with pilot partners and holding a tighter support SLA than GA. The API contract itself is held to the same /v1/ rules.

If we find a bug that requires a breaking fix during preview, we treat it like any other incident — reach out, roll the fix with you, document it. That is the one exception, and it's a lever we don't plan to pull.

Additive changes are non-breaking

We'll keep shipping additive changes inside /v1/. These don't bump the major:

  • New optional request fields. You can ignore them; older clients stay working.
  • New response fields. Log-but-ignore them in your deserializer. Don't write code that fails on unknown fields.
  • New endpoints. Entirely new routes under /v1/.
  • New error codes. We'll call them out in the Changelog; your switch on error.code should have a default branch so unknown codes degrade gracefully.
  • New details fields on existing error codes. Treat details as an open dictionary.
  • Relaxed validation. If we widen a field's accepted values (e.g., a new enum variant), the old values keep working.

Write your deserializer to ignore unknown fields. Strict mode breaks the first time we add a field. Every mature API adds fields; every brittle client tries to outlaw them.

Breaking changes ship a new major

Anything that isn't additive is a breaking change. Renaming a field. Changing a field's type. Retiring an endpoint. Tightening validation. Changing the meaning of a code.

When we need to break, we ship /v2/ as a separate surface. /v1/ stays live for the deprecation tail.

Deprecation policy

We follow a predictable path whenever we deprecate something in /v1/:

  1. Announce in the Changelog with a new entry under "Deprecated." The entry names the thing, the reason, and the sunset date.
  2. Add a Deprecation header on every response from the deprecated route. The value is an HTTP date — the earliest we'd consider removing it.
  3. Publish a migration guide that maps the deprecated shape to its replacement, line by line.
  4. Minimum 90 days between announcement and sunset. In practice, most deprecations run longer.
  5. Keep responding until sunset. Deprecation doesn't change behavior — 200 still returns, the old fields still populate. You have time.
HTTP/1.1 200 OK
Deprecation: Sat, 01 Aug 2026 00:00:00 GMT
Link: </docs/api/reference/content/generate-v2>; rel="successor-version"
Sunset: Sat, 01 Aug 2026 00:00:00 GMT

After sunset, the route returns 410 Gone with a pointer to the replacement. It doesn't silently start 404-ing.

Versioning in error codes

Error codes don't carry a version suffix. APPROVAL_REQUIRED isn't APPROVAL_REQUIRED_V1. If we ever need to change what a code means, we retire the old code (following the deprecation policy above) and ship a new name.

The same goes for the codes table as a whole — when we add new ones, it's additive. Your switch (error.code) should have a default: branch that handles the unknown case (log and surface to your caller as a generic error). That's belt-and-braces against a pre-release code landing in prod before your client knows about it.

Versioning in URL paths

The major version is always the first path segment after the host: /v1/. We don't version at the query-string level (?version=1), we don't version via Accept headers, and we don't have flags that toggle v1 vs v2 shapes on the same URL. One path, one shape.

When we ship /v2/, it'll be at api.layers.com/v2/..., running in parallel with /v1/. You opt in by changing the base URL in your client.

The X-Layers-Api-Version header

Every response carries X-Layers-Api-Version: v1 — the same major that appears in the URL. It's informational and matches the /v1/ path segment. It is not a version selector — you never send it on requests, and there is no header-based opt-in for v2 shapes. When we ship /v2/, responses from that base will emit X-Layers-Api-Version: v2.

What you can rely on, and what you can't

You can rely on:

  • The shape of every documented endpoint at the time you read the page.
  • Error codes not changing names.
  • New optional fields appearing without warning.
  • 90+ days of deprecation notice before anything documented stops working.
  • The Changelog being the single source of truth on what changed and when.

You can't rely on:

  • Undocumented fields. If it's in the wire payload but not in this reference, assume we'll remove it tomorrow. Don't branch on it.
  • Private platform codes inside PLATFORM_ERROR.details.platformCode. These are pass-throughs from TikTok, Meta, Apple — whatever they ship, we ship.
  • Internal request ids, workflow ids, or other *_id values that aren't part of the documented resource set.

See also

On this page