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

# Overview

The **Fetch API** is the public HTTP surface every Frontic project exposes. It's what the [Client SDK](https://docs.frontic.com/reference/client-sdk.md) and [Nuxt Module](https://docs.frontic.com/reference/nuxt-module.md) call under the hood - documented here for use cases that don't fit a JavaScript SDK: server-side integrations in other languages, raw curl debugging, building proxies or middleware, or understanding exactly what the SDK does.

For a frontend app, prefer the SDK or Nuxt module - they handle types, auth, and context management for you. For everything else, the endpoints below are the canonical surface.

## Base URL

Each Frontic project gets its own Fetch subdomain that embeds the project's identifier. The URL pattern is:

```
https://fetch-{project-token}.frontic.com
```

Find the project token in the admin app under **Settings → General**. The same token appears in the URL of generated SDK files (`.frontic/generated-client.ts`).

A complete request URL is the base plus the route - e.g. `https://fetch-{project-token}.frontic.com/listing/<listing-slug>`.

## Available Endpoints

> [card] [Fetch a Block](https://docs.frontic.com/reference/fetch-api/block.md)
>
> **POST /block/{slug}/{key}** - single record by key

> [card] [Fetch a Listing](https://docs.frontic.com/reference/fetch-api/listing.md)
>
> **POST /listing/{slug}** - search, filter, sort, paginate a collection

> [card] [Fetch a Page](https://docs.frontic.com/reference/fetch-api/page.md)
>
> **GET /page/{url}** - page by full URL

> [card] [Fetch a Tree](https://docs.frontic.com/reference/fetch-api/menu-tree.md)
>
> **POST /tree/{slug}** - hierarchical records (Preview)

> [card] [Get Context](https://docs.frontic.com/reference/fetch-api/get-context.md)
>
> **GET /context/token** - resolve a contextKey

> [card] [List Contexts](https://docs.frontic.com/reference/fetch-api/list-contexts.md)
>
> **GET /context** - available context variations for the project

> [card] [Update Context](https://docs.frontic.com/reference/fetch-api/update-context.md)
>
> **PATCH /context** - change the active region/locale on a contextKey

## Authentication

The Fetch API does **not** use the standard `Authorization` header. Auth is split across two concerns:

* **Context resolution** - every request needs to resolve to a (scope, region, locale) triple. Send either `fs-context` (a `contextKey`) or `fs-domain` (a configured project domain). If neither is sent, the project's default region and locale apply.
* **Secret-based access control** - the `fs-secret` header gates the API when [fetch secrets are configured](#fetch-api-secrets) for the project.

> [!NOTE]
>
> The "Authorization required" label that appears in the playground above is inherited from the docs site's global API config - it doesn't apply to the Fetch API. Use the `fs-*` headers below.

### Fetch API secrets

Fetch secrets turn your project's Fetch API from **open** (anyone with the URL can call it) into **secret-protected** (every request must include `fs-secret`). Whether you need them depends on how your frontend reaches the API:

* **Server-side or proxied calls** - keep the Fetch API closed and require a secret. Your server holds it; it never reaches the browser.
* **Direct browser calls** - the open default is fine if your frontend talks to the Fetch API straight from the browser. There's nothing for the browser to keep secret anyway. If that's not acceptable for your project, route those calls through a server-side proxy and apply a secret there instead.

**Manage secrets in the admin app** under your project's Settings. Adding at least one secret flips the Fetch API into secret-required mode; removing every secret flips it back. Secrets belong to an **API version**: the admin app edits the workspace, so a new secret protects the develop version at once and reaches the preview and public versions only when you [preview and publish a version](https://docs.frontic.com/releases/release-flow.md) that carries it. A storefront reading the public version keeps answering unprotected until that version is published. The value travels unchanged, so one secret then works against every API version.

The same **Add API Key** dialog also creates **Ingest API** keys and the project's [Bypass Token](https://docs.frontic.com/project-settings/domains.md#bypass-token), which identifies Frontic to your own storefront rather than protecting the Fetch API.

#### Using the secret

How you attach `fs-secret` depends on what's calling the API:

| Caller | How |
| - | - |
| Raw HTTP / curl | Set the header on every request: `fs-secret: <your-secret>`. |
| [Client SDK](https://docs.frontic.com/reference/client-sdk.md), server-side | Pass `createClient({ secret })` from a server-only env var, or `secret` per request. See [Server-side with a secret](https://docs.frontic.com/reference/client-sdk.md#server-side-with-a-secret). |
| [Client SDK](https://docs.frontic.com/reference/client-sdk.md), in the browser | Use `createClient({ proxyUrl: "/api/frontic" })` without a secret, and add `fs-secret` in your server-side proxy. The browser never sees the secret. |
| [Nuxt module](https://docs.frontic.com/reference/nuxt-module.md) | Set [`fetchApiSecret`](https://docs.frontic.com/reference/nuxt-module#configuration) once, from a server-only env var. The module attaches the header server-side on every path, and no call site passes a secret. |

To rotate, add the new secret first, redeploy your servers/proxies with the new value, then remove the old one - keep both active for the cut-over.

## Headers

Most endpoints support the same set; per-endpoint pages list which are honoured.

None of the context-resolution headers are required - without them, Frontic falls back to the project's default region and locale. In production you'll almost always want to send one of `fs-context` or `fs-domain` so the response is shaped for the right visitor.

| Header | Required | Purpose |
| - | - | - |
| `fs-context` | No (recommended) | The visitor's context token (`contextKey`). Resolves to the stored region/locale/scope. Takes precedence over `fs-domain`. |
| `fs-domain` | No (recommended) | A configured project domain (e.g. `demo-shop.com/de`). Used to resolve the context when no `fs-context` is set. |
| `fs-version` | No | The [version key](https://docs.frontic.com/releases/release-flow.md#version-keys) of the API version to read: the develop version or a preview version. The public version's key is empty, so leave the header out to read it. Without it, or with a key that no longer exists, you get the public version. Without Release Control, or before the first publish, the public version is served straight from your workspace. |
| `fs-secret` | Conditional | Required when the project has [fetch secrets](#fetch-api-secrets) configured. Otherwise unused. |
| `fs-use-semantic-search` | No | `true` or `1` adds semantic matching to a [Search Listing](https://docs.frontic.com/api-builder/listings.md)'s `search` term, so results match by meaning as well as by keywords. Early access: it only has an effect for Teams with semantic search enabled, on listings with searchable fields, and on the preview and public versions. Otherwise the search stays keyword-only. |

## Response headers

Frontic adds the following on every successful response:

| Header | Value |
| - | - |
| `fs-context` | The active `contextKey`. If the request didn't send one, Frontic generated and stored a fresh context - read this header to keep the same context across subsequent calls. If the request sent a token that didn't yet exist, it's persisted and echoed back. |

## Context resolution

Every request resolves to a single (scope, region, locale) triple before any data is read:

1. If `fs-context` is set and points to an existing context, the stored values are used.
2. Else if `fs-domain` is set, the configured domain mapping resolves scope / region / locale.
3. Otherwise, the project's default region and locale apply.

When `fs-context` is sent but doesn't match an existing context (and is a valid 36–50-character token), Frontic creates one on the fly using the resolved (scope, region, locale) and stores it under that token. Subsequent requests with the same token re-use the stored context.

See [Request Context](https://docs.frontic.com/api-builder/overview.md#request-context) for the full lifecycle.

## Response shape

Block, listing, and tree endpoints return JSON shaped by your project's [Detail Block](https://docs.frontic.com/api-builder/blocks.md) or [Search Listing](https://docs.frontic.com/api-builder/listings.md) configuration. The exact shape is project-specific - generate the [Client SDK](https://docs.frontic.com/reference/client-sdk.md) to get typed responses, or keep the [OpenAPI spec](#openapi-spec) it is built from to inspect it raw.

The Page endpoint and the context endpoints return fixed shapes documented on each page.

## Errors

Errors return a JSON body in one of two shapes. A deliberate error - a missing record, a failed validation - carries a `code`:

```json theme={"theme":"css-variables"}
{
  "error": "Key not found",
  "code": "KEY_NOT_FOUND",
  "details": [],
  "correlationId": "…"
}
```

* `error` - a human-readable message.
* `code` - a machine-readable error code. Branch on this, not on `error`.
* `details` - structured context for the error. Its shape varies by code, and it is empty for most.
* `correlationId` - identifies the request. Include it when you contact support. Empty when the request failed before it could be identified, for example with an unknown project or an invalid token.

An unexpected server error (`500`) carries only `error` and `correlationId` - no `code`, no `details`. The presence of `code` is how you tell the two apart.

Both shapes are described in your project's [OpenAPI spec](#openapi-spec), so the generated [Client SDK](https://docs.frontic.com/reference/client-sdk.md#error-handling) and the [Nuxt module](https://docs.frontic.com/reference/nuxt-module.md#error-handling) type them for you.

Common HTTP statuses you'll see:

| HTTP | Codes (examples) | Meaning |
| - | - | - |
| `401` | `AUTHENTICATION_FAILED` | `fs-secret` missing or wrong on a key-protected project. |
| `404` | `KEY_NOT_FOUND`, `BLOCK_NOT_EXISTS`, `URL_NOT_FOUND`, `ROUTE_NOT_FOUND`, `PAGE_NOT_EXISTS`, `TOKEN_NOT_FOUND`, `SITEMAP_NOT_FOUND`, `NO_UPDATES_PROVIDED_EXCEPTION` | The record, slug, url or token didn't resolve, or an [Update Context](https://docs.frontic.com/reference/fetch-api/update-context.md) body carried neither `region` nor `locale`. **Note: the Page endpoint never returns 404 directly** - it returns `200` with the not-found state encoded in the body's `route.code`. See [Fetch a Page](https://docs.frontic.com/reference/fetch-api/page.md). |
| `405` | `METHOD_NOT_ALLOWED` | Wrong HTTP verb for the endpoint. |
| `422` | `INVALID_PARAMETERS`, `INVALID_POST_BODY`, `MISSING_PARAMETER`, `WRONG_PARAMETER_TYPE`, `INVALID_FILTER_TYPE`, `UNKNOWN_FILTER_FIELD`, `INVALID_TOKEN`, `DATA_VIOLATION`, `FETCH_API_LIMIT_EXCEEDED` | The request was understood but rejected: validation failed, a filter or sort names something the listing doesn't expose, or a [plan limit](https://docs.frontic.com/admin/limits.md) was hit. `details` lists what's wrong and where. |
| `500` | (none) | Unexpected error. The body carries `error` and `correlationId` only. |

Validation failures answer `422`, not `400` - including a malformed body. Plan limits answer `422` as well, not `403`.

## OpenAPI spec

Your project's OpenAPI spec enumerates every block, listing, tree, and page endpoint defined in the project, with its response shape, plus the shared context endpoints and the error bodies. [`frontic generate`](https://docs.frontic.com/reference/frontic-cli#frontic-generate) fetches it from Frontic's API, scoped to the selected project, and builds the typed Client SDK from it. To keep a copy, run it with `--persist-oas`: it writes `fetch-api.spec.yaml` next to the generated files, which you can inspect or feed to OpenAPI generators in other languages.

The Fetch subdomain also serves an OpenAPI document at `https://fetch-{project-token}.frontic.com/api/doc.json`. That one is generic: it describes the Fetch API's routes with the block or listing name as a path parameter, not your project's endpoints or their response shapes, and the Client SDK isn't built from it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.