Headers, CORS & policy

CORS and browser clients

Which origins are allowed, which headers are readable, and why you should proxy anyway.

The short version

Call the API from your backend, not from your users' browsers. Not primarily because of CORS, but because a key in browser JavaScript is a key you have published.

What's allowed

The API permits a small, fixed set of origins — this documentation site among them, which is what makes the Send button on each reference page work. Your own site's origin is not on that list, so a direct browser call from your app will fail its preflight.

That's deliberate. An allowlist that anyone could join would encourage exactly the pattern that leaks keys.

The right shape

Browser ──▶ your backend ──▶ api.esportsodds.gg
             (holds the key)

Your backend holds the key, calls the API, and returns only what your UI needs. You get caching, a place to enforce your own limits, and a key that never leaves your infrastructure.

For live data in a browser

The WebSocket is designed for exactly this. Your backend mints a short-lived ticket; the browser connects with the ticket, never the key:

// Browser — never sees the API key
const { data } = await fetch('/api/ws-token', { method: 'POST' }).then((r) => r.json())
const ws = new WebSocket(`wss://api.esportsodds.gg/v1/ws?token=${data.token}`)
// Your backend
app.post('/api/ws-token', async (req, res) => {
  const r = await fetch('https://api.esportsodds.gg/v1/cs2/ws-token', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.ESPORTSODDS_API_KEY}` },
  })
  res.json(await r.json())
})

The ticket is valid for 60 seconds and grants only a WebSocket connection. Authorization: Bearer isn't accepted on the WS handshake at all — browsers can't set headers on a WebSocket, which is why tickets exist.

Where CORS does apply

For allowed origins the API responds to a preflight OPTIONS with:

  • Access-Control-Allow-Origin — the echoed origin
  • Access-Control-Allow-Methods: GET, POST, OPTIONS
  • Access-Control-Allow-Headers: Authorization, Content-Type
  • Access-Control-Expose-HeadersX-Request-Id plus the rate-limit and quota headers, so a browser client can read its remaining allowance, and can still quote a request id on a 401 where the quota headers are absent

Without that last one a cross-origin fetch can read the body but none of the X-Quota-* values — a subtle failure worth knowing about if you build a browser client against any API.

Credentials

No cookies, no sessions, no Access-Control-Allow-Credentials. Authentication is a bearer token on each request, so there is no ambient credential to leak cross-site.

On this page