CORS
Why the browser blocks your call, and the exact allowlist that fixes it.
The Sellvik API treats CORS as an auth boundary, not a cosmetic convenience. The rules below exist because letting them slip is a tenant-data leak risk; we will not loosen them.
Quick rules
- Admin routes (
/v1/admin/*) never set CORS headers. There is no browser-safe way to call them. If you need browser CRUD, build a thin proxy on your server. - Store routes (
/v1/store/*) set CORS only when the requestOriginexactly matches an entry in the shop'sallowedOriginslist. No wildcards, no protocol coercion, no port stripping.
Configuring allowed origins
Panel → Settings → API keys → Allowed Origins.
Each entry is the literal scheme://host[:port] value the browser sends:
https://shop.example.com
https://www.shop.example.com
https://staging.shop.example.com
http://localhost:3000Things that don't work:
shop.example.com— missing scheme.https://*.example.com— no wildcards.https://shop.example.com/— trailing slash.https://shop.example.com:443— explicit port for the default port.
Preflight (OPTIONS)
For any non-simple request (anything sending JSON, custom headers, or using
PATCH/DELETE), the browser sends a preflight OPTIONS first.
- If the
Originis in your shop's allowlist:204 No Contentwith the full CORS headers. - If not:
403 origin_not_allowedwith a body explaining where to add the origin.
The 403 is intentional. A silent CORS drop is hard to debug; the explicit error surfaces a readable network response.
What headers are allowed
Allowed request headers on /v1/store/*:
Authorization(customer JWT)Content-TypeX-Sellvik-KeyX-Sellvik-Cart
Allowed methods: GET, POST, PATCH, PUT, DELETE, OPTIONS.
Access-Control-Allow-Credentials: true is always set when the origin
matches — so fetch(url, { credentials: "include" }) works for sites that
need cookies.
Common failure modes
| Symptom | Cause |
|---|---|
CORS error: No 'Access-Control-Allow-Origin' | Your origin isn't in the allowlist, or has a typo. |
CORS error: credentials mode but origin is * | Should be impossible — we never echo *. File a bug. |
Request works in curl, fails in browser | This is CORS. Add the origin. |
| Adding origin doesn't take effect | Cached preflight (Access-Control-Max-Age: 600). Hard-reload or wait. |
403 origin_not_allowed on preflight | Origin missing from allowlist; copy the exact string the browser shows. |
Why no wildcards
Wildcards turn the allowlist from an auth control into a sanity check. A
single CDN-hosted XSS on *.example.com would compromise every Sellvik
shop that trusts that wildcard. Exact-match keeps the blast radius scoped to
the explicit origins the merchant approved.
If you operate many subdomains and need to add them, list each one. If you have a lot, mail us; bulk-add tooling exists for the panel.