Get started: authentication, concepts, errors, rate limits and versioning # Authentication > API keys, per-resource scopes, expiry, legacy keys, organization isolation, and how to rotate a key. Every REST request carries an API key in a header. A key belongs to one organization. A key has one or more scopes, and an optional expiry date. ## API keys [Section titled “API keys”](#api-keys) Create a key in the dashboard. Open the account menu at the bottom of the sidebar and select **Developers**. On the **API keys** tab, select **Create API key**. Pick the access for each resource, pick an expiry, and select **Create key**. LIGR shows the full key once. After that it shows only the first four and the last four characters. Only an organization admin or owner sees **Developers**. If the menu does not show it, ask an admin or owner to create the key. A key starts with `ligr_`. LIGR stores only a hash of the key. If you lose a key, create a new one. ## Scopes [Section titled “Scopes”](#scopes) A scope is `:read` or `:write`. `write` on a resource includes `read` on that resource. The dashboard groups resources by product area. Each resource has a **Read** box and a **Write** box. Select **Write** to also select **Read**. A group header box sets every resource in that group. The key list shows the scopes of each key. | Boxes selected | Scope | | -------------- | ------------------ | | None | none | | Read | `:read` | | Read and Write | `:write` | Each endpoint in the [REST reference](/rest/) names the scope it needs. | Resource | Endpoints | | -------------- | --------------------------------------------------------------------------- | | `matches` | `/v1/matches`, `/v2/matches`, facts, lineups and match summaries | | `competitions` | `/v1/competitions` | | `teams` | `/v1/teams`, team rosters and adding players to a team | | `players` | `/v1/players`, `/v1/officials` | | `venues` | `/v1/venues` | | `overlays` | `/v1/overlays`, `/v2/overlays`, graphics commands and control room graphics | | `streams` | `/v1/streams`, go-live, overlay show and hide | | `data-sources` | `/v2/data-sources` | | `themes` | `/v2/themes`, code graphics, `/v2/schemas`, `/v2/scenarios` | The resource is the first path segment after the version, except `/v1/officials`, which uses `players`. `read` allows every `GET` under that segment. `write` allows every request under that segment. No endpoint requires `players:write` or `data-sources:read` yet. The dashboard disables these two boxes. To create a player, use `POST /v1/teams/{teamId}/players` with `teams:write`. A key that lacks the scope for a route returns **403** with `code: "INSUFFICIENT_SCOPE"`. The message names the missing scope. LIGR checks the scopes of the key, not the role of the user who created it. Grant the smallest set of scopes that works. A score feed needs `matches:write`. A results website needs `matches:read` and `competitions:read`. ## Expiry [Section titled “Expiry”](#expiry) Pick an expiry when you create the key: 7, 30, 60, 90 days, one year, or a custom date. The default is 30 days. An expired key returns **401** with `code: "API_KEY_EXPIRED"`. Create a new key before the old one expires. See [Rotate a key](#rotate-a-key). You can also choose **No expiration**. The dashboard warns you first. A key with no expiry stays valid until you delete it. Choose it only when the integration cannot rotate keys. ## Legacy keys [Section titled “Legacy keys”](#legacy-keys) Keys created before scopes existed are legacy keys. The dashboard labels them **Legacy**. * A legacy `read` key has `read` on every resource. * A legacy `write` key has `write` on every resource. * A legacy key never expires. * A legacy key keeps working. You cannot create a new one. Move each integration to a scoped key, then delete the legacy key. ## Send the key [Section titled “Send the key”](#send-the-key) Use `Authorization: Bearer ` for new integrations. Both scoped and legacy keys work with this header. Terminal ```bash curl https://api.ligr.live/rest/v1/competitions \ -H 'Authorization: Bearer YOUR_API_KEY' ``` Existing integrations can keep using the legacy `x-ligr-api-key` header: ```http x-ligr-api-key: YOUR_API_KEY ``` Both headers use the same key validation, scopes, and expiry checks. Changing the header does not require a new key. Send one authentication header per request. If both headers are present, they must contain the same key. Different keys return **401**. An invalid `Authorization` format also returns **401**, even when `x-ligr-api-key` contains a valid key. REST routes ignore keys in the query string. Caution Send an API key as the Bearer token. REST routes do not accept dashboard or overlay JWTs. ## Who can create keys [Section titled “Who can create keys”](#who-can-create-keys) Organization **owners** and **admins** can create and delete a key. A key acts for the whole organization, within its scopes. It sees every competition and match that the organization owns, and nothing else. A request for a resource in another organization is always refused. The status is not uniform today: theme routes return **404**, and match, team, player, overlay, stream and data source routes return **403**. Do not read ownership from the status code. ## Rotate a key [Section titled “Rotate a key”](#rotate-a-key) There is no REST endpoint for keys today. Follow these steps. 1. Create the new key in the dashboard with the same scopes. 2. Deploy the new key to your service. 3. Delete the old key in the dashboard. Both keys work during the overlap. Deletion takes effect at once. ## Errors [Section titled “Errors”](#errors) | Status | When | Body | | ------ | --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | 401 | The key is missing or unknown, the Authorization format is invalid, or the headers contain different keys | `{ "message": "…", "code": "UNAUTHENTICATED" }` | | 401 | The key has expired | `{ "message": "…", "code": "API_KEY_EXPIRED" }` | | 403 | The key is valid but lacks the scope for the route | `{ "message": "…", "code": "INSUFFICIENT_SCOPE" }` | | 403 | The resource belongs to another organization, on most routes | `{ "message": "…", "code": "FORBIDDEN" }` | | 404 | The resource does not exist, or a theme belongs to another organization | `{ "message": "Not Found" }` | | 429 | The rate limit is exceeded and enforcement is on | `{ "message": "Api key has made too many requests" }` | See [Errors](/get-started/errors/) for the full status map. See [Rate limits](/get-started/rate-limits/) for the headers and the 429 response. # Concepts > The competition tree, the theme tree, how an overlay joins them, and the ids you need. Two object trees explain the whole API. The competition tree is what happens on the pitch. The theme tree is what appears on screen. An overlay joins them for one match. ## The competition tree [Section titled “The competition tree”](#the-competition-tree) REST v1 and v2 read and write this tree. | Object | What it is | | --------------- | ----------------------------------------------------------------------------- | | **Competition** | A league or a tournament. It owns teams, venues, matches and one control room | | **Match** | Sport, date, competitors, periods, clock, `liveStatus` and `finishedStatus` | | **Facts** | Time-stamped events under a match: a goal, a card, a point, a substitution | | **Summary** | The score, periods and statistics that LIGR computes from the facts | | **Lineup** | The match players of one match, as starters and bench | | **Overlay** | A per-match graphics session with a secret key | | **Stream** | Encode settings, inputs, destinations and stream state | A competition holds matches. A match holds facts, a summary and a lineup. A match also holds an overlay and a stream. ## The theme tree [Section titled “The theme tree”](#the-theme-tree) REST v2, the Rive Graphics Builder, and the Graphics SDK read and write this tree. | Object | What it is | | ----------------------------- | -------------------------------------------------------------------------------- | | **Theme** | A sport-scoped set of graphics. Its `activeVersion` is what overlays render | | **Graphic** | One graphic, either Rive or code. `graphicId` is stable. `id` changes on publish | | **Working version** | The mutable draft you push to | | **Published versions** | Immutable snapshots, numbered 1, 2, 3 and up | | **Assets** | The files of a graphic: code file, image, font or Rive file | | **Theme version** | A frozen list of graphic versions | | **Competition theme setting** | Binds a theme to a competition. It holds theme variable values and data sources | A publish picks the latest published version of each graphic, unless you name a version. ## Where the trees meet [Section titled “Where the trees meet”](#where-the-trees-meet) An overlay belongs to one match. It renders the active theme version of the competition of that match. Its `key` forms the browser-source URL. Browser source, 1920×1080 ```http https://overlay.ligr.live/production-3b2b1c0e-… ``` A control room is the button layout that an operator uses for that theme. Each button is a preset. You can fire a preset over REST. See [Overlays & control room](/control-room/). Fire a preset ```http POST /rest/v2/overlays/2300003/control-room/graphics ``` ## Ids that matter [Section titled “Ids that matter”](#ids-that-matter) | Id | Where you get it | Used by | | --------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | | `matchId` | Search matches, create a match, or the dashboard URL | Matches, facts, summary, overlays, webhooks | | `overlayId` | A match with `include=o`, or create an overlay | Graphics commands, control room graphics, stream overlay change | | `streamSettingsId` | A match with `include=s` | Stream state and stream commands | | `competitionThemeSettingId` | `GET /v2/competitions/{competitionId}/theme-profiles`, or the theme settings of the competition | [Data source push](/control-room/data-sources/) | | `themeId` | The dashboard theme URL, or get a theme | [Push and publish](/graphics-sdk/push-and-publish/) | | `graphicId` | A code or Rive creation response, or a theme read | Files, configuration, presets, and versions. It is stable across publishes | | `presetId` | `GET /v2/control-rooms/{roomId}/presets` | [Presets](/control-room/presets/) | # Errors > Every REST status code, the error codes you can branch on, and the response bodies. Every error is JSON with a `message`. Some errors also carry a `code` you can branch on. Some carry `details`. ## Status codes [Section titled “Status codes”](#status-codes) | Status | Meaning | Body | | ------ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | 400 | Validation failed, or a user input error | `{ message: "Validation Failed", details: { "body.date": { message, value } } }` | | 401 | No key, an unknown key, or an expired key | `{ message, code: "UNAUTHENTICATED" }` or `{ message, code: "API_KEY_EXPIRED" }` | | 403 | A valid key without the scope the route needs | `{ message, code: "INSUFFICIENT_SCOPE" }` | | 403 | A resource owned by another organization, on most routes | `{ message, code: "FORBIDDEN" }` | | 404 | Not found, or a theme owned by another organization | `{ message }` | | 409 | A graphic is locked, a publish conflicts, or a stored graphic asset fails verification | `{ message, code }`; `LOCKED` also includes `holder.userName` | | 429 | The rate limit is exceeded while enforcement is on | `{ message }` | | 500 | Unexpected. Retry once, then contact support | `{ message }` | For a 500, send the `traceparent` header value you used to support. It lets LIGR find your request. ## Error codes [Section titled “Error codes”](#error-codes) | `code` | Status | When | Fix | | -------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `API_KEY_EXPIRED` | 401 | The key passed its expiry date | Create a new key in the dashboard | | `INSUFFICIENT_SCOPE` | 403 | The key lacks the scope the route needs. The message names the scope | Create a key with that scope | | `LOCKED` | 409 | Someone has the graphic open in the dashboard editor, or a stale lock has not expired. A lock expires after 60 seconds | Wait, then retry. `holder.userName` names the editor | | `CONFLICT` | 409 | Two publishes raced | Read the theme again, then publish again | | `GRAPHIC_ASSET_MISSING` | 409 | A graphic or theme publish references an S3 asset that is missing | Upload the missing asset, then publish again | | `GRAPHIC_ASSET_INVALID` | 409 | An asset cannot be read, or its stored size or SHA-256 differs from the recorded value | Replace the asset with the intended bytes, then publish again | | `CODE_GRAPHIC_INVALID` | 400 | An unknown control variable type, or a bundle that names a file you did not upload in the session it names and the graphic does not already hold | Read `details[]`. Each line is one problem. Upload the named file, then record the bundle with the `uploadSessionId` of that upload | | `BUNDLE_TOO_LARGE` | 400 | The bundle is over 10 MiB, or one file is over 5 MiB | Read `details[]`. It lists the files that are too large | | `THEME_LIMIT_REACHED` | 409 | Your organization already owns five themes | Delete a theme you no longer use, or ask your LIGR contact | | `THEME_IN_USE` | 409 | A competition still uses the theme you want to delete | Read `details[]`. Remove the theme instance from each competition, then delete again | | `GRAPHIC_TYPE_MISMATCH` | 400 | The id belongs to a Rive graphic, and the route serves code graphics | Use the [Rive graphics](/rive-graphics/) routes for a Rive graphic | | `INVALID_ASSET_PATH` | 400 | A file name holds `..`, an empty segment, a control character, or one of `?`, `#`, `%` | Rename the file | | `RUNTIME_HAS_NO_EDITABLE_SOURCE` | 400 | A raw `.riv` upload requested source retention | Send `retainSource: false`. The runtime revision preserves those bytes | | `INVALID_SELECTION` | 422 | The chosen artboard, state machine, or archive entry is unavailable | Read current import metadata, then send an exact returned index or entry | | `SCRIPT_SIGNING_UNSUPPORTED` | 422 | Source requires script signing, which this converter cannot perform | Export a signed `.riv` externally, then upload the runtime | | `IMPORT_UNAVAILABLE` | 503 | Source import is disabled or not qualified in this environment | Read the capability response and use an available path | ## Validation errors [Section titled “Validation errors”](#validation-errors) A 400 from schema validation names each bad field. 400 Bad Request ```json { "message": "Validation Failed", "details": { "body.date": { "message": "invalid ISO 8601 date", "value": "12-09-2026" } } } ``` ## Retries [Section titled “Retries”](#retries) | Status | Retry? | | ------------------ | ------------------------------------------------------------------------------------------------------------- | | 400, 401, 403, 404 | No. Fix the request | | 409 | For `LOCKED` or `CONFLICT`, retry after resolving the conflict. For `GRAPHIC_ASSET_*`, repair the asset first | | 429 | Yes, after the `RateLimit-Reset` seconds. See [Rate limits](/get-started/rate-limits/) | | 500 | Once. Then contact support | Make write requests safe to repeat. Read the entity back before you write it again. # Your first request > Create a key, list matches with curl, read the response, and choose what to do next. This page takes about five minutes. You need an API key and a terminal. 1. **Create a read key.** Open the LIGR dashboard. Open the account menu at the bottom of the sidebar and select **Developers**. On the **API keys** tab, select **Create API key**. Set **Matches** to **Read**, keep the 30 day expiry, and select **Create key**. Copy the key. LIGR shows it once. 2. **List one match.** Replace `YOUR_READ_KEY` with the key you copied. Terminal ```bash curl 'https://api.ligr.live/rest/v2/matches?limit=1' \ -H 'Authorization: Bearer YOUR_READ_KEY' ``` 3. **Read the response.** 200 OK ```json { "matches": [ { "id": 1188213, "name": "Sydney FC v Melbourne City", "sport": "football", "date": "2026-09-12T09:30:00.000Z", "competitionId": 2311, "competitorsType": "teams", "liveStatus": "pregame", "finishedStatus": "default" } ], "total": 412, "returned": 1 } ``` `total` is the number of matches your organization owns. `returned` is the number in this response. Keep the `id`. Most other endpoints need it. 4. **Fetch one match with its overlay.** Terminal ```bash curl 'https://api.ligr.live/rest/v2/matches/1188213?include=o,cp' \ -H 'Authorization: Bearer YOUR_READ_KEY' ``` `include=o,cp` adds the overlay and the competitors to the response. See [Includes & pagination](/get-started/includes-and-pagination/). ## If the request fails [Section titled “If the request fails”](#if-the-request-fails) | Status | Cause | Fix | | ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | 401 | The key is missing or invalid, or the authentication headers are invalid | Use `Authorization: Bearer `. See [Authentication](/get-started/authentication/#send-the-key) | | 403 | The key lacks the scope for the route | Create a key with the scope the message names | | 404 | The id belongs to another organization | Search the list endpoint first | ## What to do next [Section titled “What to do next”](#what-to-do-next) * Write data: post a fact to a match. See the [Facts](/rest/operations/tags/facts/) reference. * React to changes: subscribe a webhook instead of polling. See [Webhooks overview](/webhooks/overview/). * Understand the object model: read [Concepts](/get-started/concepts/). # Includes & pagination > Fetch related entities with include, and page through lists with limit and offset. ## Includes [Section titled “Includes”](#includes) Many `GET` endpoints accept the `include` query parameter. It populates the response with related entities, so you make fewer requests. | Value | Entity | | ----- | ------------ | | `m` | matches | | `o` | overlays | | `t` | teams | | `c` | competitions | | `v` | venues | | `p` | players | | `s` | streams | | `cp` | competitors | Send a comma-separated list. Each endpoint accepts a subset. The reference page for the endpoint lists the values. The v1 match endpoints accept `t` for teams. The v2 match endpoints accept `cp` for competitors instead. Terminal ```bash curl 'https://api.ligr.live/rest/v1/matches/1188213?include=t,o,c' \ -H 'Authorization: Bearer YOUR_READ_KEY' ``` ### Limits [Section titled “Limits”](#limits) * Includes apply one level deep. An included team does not carry its own includes. * Each endpoint has its own allow-list. The reference page of each endpoint names the values it accepts. * An unknown or unsupported value is ignored. The request succeeds and the response omits that object. The API returns no error. Check the response for the objects you asked for. ## Pagination [Section titled “Pagination”](#pagination) List endpoints take `limit` and `offset`. | Parameter | Meaning | Default | | --------- | ------------------------------------ | ------------------- | | `limit` | The number of items in this response | Set by the endpoint | | `offset` | The number of items to skip | `0` | The maximum `limit` is 100 on match lists. It is 500 on the other lists. ### Response shape [Section titled “Response shape”](#response-shape) Most list endpoints return the item array, a total and a returned count. 200 OK ```json { "matches": [ /* … */ ], "total": 412, "returned": 50 } ``` * `total` is the number of items that match the filter, across all pages. * `returned` is the number of items in this response. Some smaller lists return a `count` instead of `total` and `returned`. The reference page of each endpoint shows the exact shape. ### Page through a list [Section titled “Page through a list”](#page-through-a-list) Increase `offset` by `limit` until `offset` reaches `total`. Page two of fifty ```bash curl 'https://api.ligr.live/rest/v2/matches?limit=50&offset=50' \ -H 'Authorization: Bearer YOUR_READ_KEY' ``` Caution Do not poll a list endpoint to detect changes. Use [webhooks](/webhooks/overview/). Polling counts against your [rate limit](/get-started/rate-limits/). # Overview > What you can build on LIGR, the three developer surfaces, and what you need before your first request. LIGR runs live sports graphics and streams for broadcasters, leagues and federations. This site documents four surfaces you can build on. ## The four surfaces [Section titled “The four surfaces”](#the-four-surfaces) | Surface | What it does | Where to start | | ------------- | ----------------------------------------------------------------------------- | ------------------------------------------------- | | REST API | Read and write matches, facts, teams, overlays, streams and themes | [Your first request](/get-started/first-request/) | | Webhooks | Signed POST requests when a match, fact, team, competition or summary changes | [Webhooks overview](/webhooks/overview/) | | Graphics SDK | Write a graphic in HTML and JavaScript, then publish it into a theme | [Graphics SDK](/graphics-sdk/) | | Rive graphics | Import a native Rive runtime or source project, then bind live data | [Rive graphics](/rive-graphics/) | ## What you can build [Section titled “What you can build”](#what-you-can-build) * A scoring system that creates matches and posts facts as the match runs. * A broadcast automation that fires graphics on an overlay and drives the stream. * A data push from a spreadsheet or a feed into a theme data source. * Your own HTML graphics, published into a theme that operators use. * Native Rive graphics bound to live match data and control-room variables. ## Before you start [Section titled “Before you start”](#before-you-start) 1. Get an organization on LIGR. Ask your LIGR contact if you do not have one. 2. Ask an owner or an admin of that organization to create an API key. 3. Send that key on every request to `https://api.ligr.live/rest`. The examples in these docs use production: `api.ligr.live` for REST and `overlay.ligr.live` for overlays. If LIGR gave you a non-production environment, use its API and overlay origins instead. A key works only in the environment that issued it. ## The rest of this section [Section titled “The rest of this section”](#the-rest-of-this-section) | Page | What it answers | | -------------------------------------------------------------- | ---------------------------------------------------------------- | | [Authentication](/get-started/authentication/) | How keys work, which scopes exist, and how expiry works | | [Your first request](/get-started/first-request/) | A working `curl` call and the response it returns | | [Concepts](/get-started/concepts/) | The competition tree, the theme tree, and the ids that join them | | [Includes & pagination](/get-started/includes-and-pagination/) | How to fetch related entities and page through lists | | [Errors](/get-started/errors/) | Every status code, error code and response body | | [Rate limits](/get-started/rate-limits/) | The `RateLimit` headers, observe mode, and the enforcement dates | | [Versioning](/get-started/versioning/) | Why `/v1` and `/v2` run side by side, and the deprecation policy | # Rate limits > The RateLimit headers, observe mode, the 429 response, and how to stay inside the limits. LIGR applies fair-use limits to the REST API. LIGR does not publish a fixed request budget. Unauthenticated requests are limited per IP address. Read the headers on every response, and slow down before they reach zero. ## Headers [Section titled “Headers”](#headers) Every response carries the IETF draft headers. ```http RateLimit-Limit: 300 RateLimit-Remaining: 287 RateLimit-Reset: 41 RateLimit-Policy: 15;w=1, 300;w=60, 6000;w=3600 ``` | Header | Meaning | | --------------------- | ----------------------------------------------------- | | `RateLimit-Limit` | The number of requests the current window allows | | `RateLimit-Remaining` | The number of requests left in the current window | | `RateLimit-Reset` | The number of seconds until the current window resets | | `RateLimit-Policy` | Every window in force, as `;w=` | More than one window can apply at the same time. `RateLimit-Policy` lists every window in force. ## Observe mode [Section titled “Observe mode”](#observe-mode) LIGR is phasing enforcement in. While enforcement is off, an over-limit request still succeeds. The response carries `x-ligr-rate-limit-observe: 1`. Caution Treat `x-ligr-rate-limit-observe: 1` as a 429 in your client. Back off when you see it. Your requests will fail once enforcement starts. ### Enforcement [Section titled “Enforcement”](#enforcement) Enforcement starts on a date to be announced in the [changelog](/changelog/). Until then, treat `x-ligr-rate-limit-observe: 1` as a 429. ## Over the limit [Section titled “Over the limit”](#over-the-limit) An enforced over-limit request returns **429**. 429 Too Many Requests ```json { "message": "Api key has made too many requests" } ``` Wait `RateLimit-Reset` seconds, then retry. ## Stay inside the limits [Section titled “Stay inside the limits”](#stay-inside-the-limits) * Use [webhooks](/webhooks/overview/) instead of polling. * Batch reads with [`include`](/get-started/includes-and-pagination/) so one request replaces four. * Cache `GET` responses on your side. * Contact support if your integration needs a higher limit. # Versioning > Why v1 and v2 run side by side, how LIGR ships breaking changes, and the deprecation policy. LIGR versions each resource, not the whole API. The path holds the version. ```http GET /rest/v1/matches/{matchId} GET /rest/v2/matches/{matchId} ``` Both versions run at the same time. A resource moves to v2 when its shape changes in a way that would break v1 clients. Resources that have not changed stay on v1 only. A new resource starts at the current version. It has no older path. For example, `PUT /rest/v2/matches/{matchId}/lineup` and every `/rest/v2/themes` route exist only under v2. A v2 path does not mean that a v1 path exists. The [REST reference](/rest/) adds **(v1)** or **(v2)** to a label only when both versions of that operation exist. The two sit next to each other in the sidebar. Use the higher version for new work. A label without a version has one path only. ## What LIGR can change without a new version [Section titled “What LIGR can change without a new version”](#what-ligr-can-change-without-a-new-version) * A new optional field in a response. * A new optional query parameter or body field. * A new endpoint. * A new enum value in a field that already accepts several values. Write your client so that an unknown field does not break it. ## What needs a new path version [Section titled “What needs a new path version”](#what-needs-a-new-path-version) * A field that is removed or renamed. * A field whose type changes. * A required parameter that is added. * A change in the meaning of an existing field. ## Beta endpoints [Section titled “Beta endpoints”](#beta-endpoints) Some endpoints ship as beta before their shape is final. Today the Themes and Code graphics operations under `/v2/themes` are beta. See [Beta status](/graphics-sdk/#beta-status). A beta endpoint can change in ways that the rules above reserve for a new path version. Each change gets a changelog entry, and a removed or renamed field keeps working for at least **30 days** after that entry. A beta endpoint is marked in three places. | Where | Marker | | -------------------- | --------------------------------------------------------- | | The OpenAPI document | `x-beta: "true"` on the operation | | The reference page | A **Beta** line at the top of the operation | | Every response | `X-Ligr-Beta: true` and a `Link` header with `rel="help"` | ## Deprecation policy [Section titled “Deprecation policy”](#deprecation-policy) A deprecated endpoint keeps working for at least **12 months** after its changelog entry. While it is deprecated it returns two headers. | Header | Meaning | | ------------- | --------------------------------------- | | `Deprecation` | The date the endpoint became deprecated | | `Sunset` | The date the endpoint stops working | Watch the [changelog](/changelog/) for the entry, and read the `Sunset` header in your client logs. ## The specification version [Section titled “The specification version”](#the-specification-version) The OpenAPI document carries its own version in `info.version`. It is the version of the newest [changelog](/changelog/) entry, so the reference, the changelog and the API always show the same number. Read it at `https://api.ligr.live/rest/swagger.json`. [/openapi.json](/openapi.json) holds the same operations as that document, regrouped for this site. Generate a client from `swagger.json`. Read `/openapi.json` to see what this site shows. The [REST reference](/rest/) on this site is generated from that document, so it never drifts from what is deployed.