Stores and API keys
How the API knows which store you mean, the public store key and server secret, registered websites, and how to rotate credentials safely.
Every request to the Elara One API is made on behalf of one store. This page covers how a store is identified, which credentials to send from a browser and from a server, and how to change them without taking your shop offline.
A store
A store is one shop or business on Elara One, such as Hearth & Oak. Each store has its own database: its catalogue, customers, orders, CRM records and settings live there and are never shared with another store. Search indexes, cache entries and media paths are kept apart per store too.
| What | Where |
|---|---|
| API base URL | https://api.elaraone.app/api/v1 |
| Store admin | {slug}.admin.elaraone.app |
| Keys, secret and websites | Store admin, Settings → Storefront API |
The public store key
X-Tenant-Key identifies the store, and it is the only thing that does. Send it on every call.
- Live store keys start with
pk_live_. A staging store, where your plan includes one, has its own key starting withpk_test_. - The key is public in the same way a Stripe publishable key is: it can appear in browser code. On its own it only works from your registered websites (browser calls) or together with the server secret (server calls).
- Find it under Store key in Settings → Storefront API.
A missing key returns 400 TENANT_KEY_MISSING. An unknown key, a suspended store and a rotated key whose overlap has ended all return the same 404 TENANT_NOT_FOUND. The API never echoes the key back.
The server secret
X-Tenant-Secret proves a call comes from your server. Send it on every server-to-server call, together with the store key.
Never put the secret in browser code
Anyone who visits the site can read browser code and call the API as your server. Keep the secret in a server-only environment variable, never under a prefix your framework copies into the browser bundle: NEXT_PUBLIC_* (Next.js), NUXT_PUBLIC_* and VITE_* (Nuxt and Vite), PUBLIC_* (Astro, SvelteKit), REACT_APP_* (Create React App).
The secret is shown in Settings → Storefront API. Revealing or rotating it asks for your admin password and authenticator code.
Browser calls and server calls
The API tells the two kinds of caller apart by the Origin header, which browsers add to cross-site requests.
| Browser call | Server call | |
|---|---|---|
| How the API tells | The request has an Origin header | The request has no Origin header |
| Who makes it | JavaScript on a web page | Your storefront server, a script, a job, an integration |
| Must carry | X-Tenant-Key, from a registered website | X-Tenant-Key and X-Tenant-Secret |
| Gets | The public tier only | Everything the route allows, including per-shopper rate limits through X-Client-IP |
The Origin check is not authentication. It stops other websites' JavaScript from using your key, but a program that is not a browser can send any Origin it likes. That is why a browser call only gets the public tier: X-Client-IP is ignored, and routes that need the secret (such as consent) refuse it whatever Origin it claims.
We recommend a backend-for-frontend: your storefront's own server calls the API, and the browser only talks to your server. All the starter storefronts work this way.
curl https://api.elaraone.app/api/v1/products \
-H "X-Tenant-Key: $ELARAONE_TENANT_KEY" \
-H "X-Tenant-Secret: $ELARAONE_TENANT_SECRET" \
-H "X-Client-IP: 203.0.113.7"Server-side fetch in Node.js does not add an Origin header, so your server's calls are server calls. Do not copy the incoming browser request's headers onto the API call wholesale: the browser's Origin would turn a server call into a browser call.
Register your websites
Browser calls are accepted only from the websites you register in Settings → Storefront API → Your websites. Each entry is an origin: scheme, host and, when it is not the default, port. No path.
| You enter | Stored as |
|---|---|
https://Shop.Example.com/ | https://shop.example.com |
https://shop.example.com:443 | https://shop.example.com |
https://shop.example.com:8443 | https://shop.example.com:8443 |
https://shop.example.com/products | Refused: no path, query string or fragment |
http://shop.example.com | Refused: https:// only |
https://*.example.com | A wildcard, on plans with wildcard website domains |
- A wildcard matches one or more labels below its base, never the bare domain itself. Register
https://example.comseparately if the shop runs there too. - How many websites you can register depends on your plan. The admin shows how many you have used.
Origin: null(a sandboxed iframe, a page opened from a file) counts as an unregistered website. So does a page served fromlocalhoston a live store: develop against the API from your own server instead. Staging stores, where your plan includes them, accepthttp://localhostandhttp://127.0.0.1on any port.- Registering a website never identifies a store; the key does. Two stores can register the same website.
CORS
- Preflight (
OPTIONS) requests are answered for any origin, because the store cannot be identified before the real request. They are cached for 600 seconds and never allow credentials (cookies). A successful preflight does not mean the call will be accepted. - The real response carries
Access-Control-Allow-Originset to your origin, never*, and every response carriesVary: Origin. - Your page's JavaScript can read these response headers:
Idempotent-Replayed,Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id,X-Search-Id,X-Search-TotalandX-Tenant-Key-Deprecated.
If your storefront calls the API only from its server, switch Browser calls off in Settings → Storefront API. Every call without the secret is then refused, with or without an Origin.
Report-only, then enforcing
A new store starts in report-only mode. Nothing is refused, but every call the lock would refuse is counted and shown in Settings → Storefront API → API lock, per website, with the first example of each kind (path, method, time and browser family). No header values or IP addresses are kept.
After 14 days the lock turns on by itself. The admin shows the date and counts down. You can choose Turn on the lock now earlier, and until the original date you can go Back to report-only.
Once the lock is on, the API answers:
| Status | error.code | When | Fix |
|---|---|---|---|
| 403 | ORIGIN_NOT_ALLOWED | A browser call from a website you have not registered | Register the website, or call from your server |
| 403 | BROWSER_CALLS_DISABLED | A browser call while browser calls are switched off | Call from your server with the secret, or switch browser calls back on |
| 403 | TENANT_SECRET_REQUIRED | A server call without X-Tenant-Secret | Send the secret on every server call |
| 403 | TENANT_SECRET_INVALID | A server call with the wrong secret, or a secret-only route called without it | Copy the current secret from Settings → Storefront API |
None of these is worth retrying: the same call gets the same answer until your integration or the store's settings change.
Before the lock date, check that:
- Every website the shop runs on is registered, including
www.and any separate checkout or landing-page host. - Every server that calls the API sends
X-Tenant-Secret: the storefront server and any integration, scheduled job or feed generator. - The report shows no calls without the secret and none from unregistered sites for the last few days.
- If the report shows no browser calls at all, switch Browser calls off.
Rotate the public key
Rotate the store key when it has been copied onto a site you do not control, or when an agency hands over. Go to Settings → Storefront API → Danger zone → Rotate store key. Rotating asks for your admin password and authenticator code.
- The new key keeps the old one's prefix (
pk_live_orpk_test_) and is shown straight after the rotation. - The old key keeps working for 7 days. Responses to calls made with it carry
X-Tenant-Key-Deprecated: true. Log this header in your integration so a forgotten deployment shows up. - End overlap now stops the old key at once, for a key that has leaked.
- After the overlap, calls with the old key get
404 TENANT_NOT_FOUND. - Only one rotation runs at a time. Deploy the new key everywhere during the overlap.
Rotate the server secret
Rotate the secret when it may have leaked: it was seen in a browser, committed to a repository, or someone who knew it has left.
The change is immediate, with no overlap. Once the store is enforcing, calls with the old secret get 403 TENANT_SECRET_INVALID. To keep a locked shop working, rotate just before you deploy the new value to every server.
If a call ever carries both an Origin header and the secret, the secret has leaked to every visitor of that website. The API still serves the call, but the admin shows an urgent notice. Rotate the secret and move those calls to your server.