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

# CORS

> Cross-origin request support on api.simkl.com.

The Simkl API supports **Cross-Origin Resource Sharing (CORS)** on `api.simkl.com` — you can call it directly from a browser with `fetch` or `XMLHttpRequest`, from any origin.

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
const res = await fetch(
  'https://api.simkl.com/tv/17465?client_id=YOUR_CLIENT_ID&app-name=my-app-name&app-version=1.0',
);
```

<Note>
  **Do not set `User-Agent` from browser code.** It is a [forbidden header name](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header) — the browser refuses to let you set it and sends its own. The `User-Agent` requirement in [Headers and required parameters](/conventions/headers) applies to server-side and native clients; identify your browser app with the `app-name` and `app-version` URL parameters instead.

  Setting `Content-Type: application/json` on a `GET` has a cost too: it turns a simple request into a preflighted one for no benefit. Send it only on requests that actually have a JSON body.
</Note>

## Authenticated calls from a browser

`Authorization` is named explicitly in the allowed request headers, so a bearer token works cross-origin:

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
const res = await fetch(
  'https://api.simkl.com/sync/activities?client_id=YOUR_CLIENT_ID&app-name=my-app-name&app-version=1.0',
  { headers: { Authorization: `Bearer ${accessToken}` } },
);
```

That request is **preflighted** — the browser sends an `OPTIONS` first and Simkl answers it with `204` and an empty body. You do not need to do anything about it; just expect two requests in your network tab rather than one, and note that only the real request counts against your [rate limit](/resources/rate-limits).

The preflight is cached for **24 hours** (`Access-Control-Max-Age: 86400`), so the extra round-trip happens once per browser session per endpoint shape, not on every call.

<Warning>
  **Never put a `client_secret` in browser code.** Anything shipped to a browser can be read. Register a **Mobile, desktop & browser apps** client, which has no secret, and use the [authorization code flow with PKCE](/api-reference/oauth2-authorization-code) — it is designed for exactly this case. If you need a secret, the exchange belongs on your own server.
</Warning>

## Headers you can read

Browsers hide response headers from JavaScript unless the server opts them in. Simkl exposes seven:

| Header | Why you would read it |
| - | - |
| `X-RateLimit-Limit` | That user's daily allowance. [AUTH V2](/api-reference/auth-v2) requests only. |
| `X-RateLimit-Remaining` | Requests left today. AUTH V2 requests only. |
| `Retry-After` | On a daily-quota `429`, the **seconds from now** until the reset — a duration, not a timestamp. |
| `X-Pagination-Page` | The page you are on. |
| `X-Pagination-Limit` | Items per page. |
| `X-Pagination-Page-Count` | How many pages exist. |
| `X-Pagination-Item-Count` | How many items exist in total. |

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
if (res.status === 429) {
  const waitSeconds = Number(res.headers.get('Retry-After'));
  // ...
}

const totalPages = Number(res.headers.get('X-Pagination-Page-Count'));
```

The pagination headers are the reason a browser client does not need to count pages itself — see [Pagination](/conventions/pagination). For what the quota headers mean and when they are absent, see [Rate limits](/resources/rate-limits#headers).

Any header not in that list is invisible to `fetch` from another origin; that is the browser's rule, not Simkl's.

<Note>
  **Errors carry CORS headers too**, including the ones answered at the edge before the request reaches Simkl's application servers. A `401`, `403` or `429` is readable from JavaScript exactly like a `200`, so you can branch on `error` in the body rather than guessing from a network failure.

  An opaque "network error" with no status in a browser is almost always something else — an ad blocker, an extension, or the request never leaving the machine.
</Note>


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