Shop API Key
The Shop API Key authenticates server-to-server calls against /api/plugin/shops/*. It is used to sync the catalog, record conversions, verify the domain and read shop metadata.
Header
X-Shop-API-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Format
sk_ followed by 32 hexadecimal characters. Total length ~35 characters. It is permanent until rotated: it does not expire by time.
Origin validation
The filter validates the Origin header (or Referer as fallback) against the domain registered for the shop:
- If the client is a browser and sends
Origin, it must matchshop.url. Otherwise →403 Forbiddenwith messageOrigin mismatch — API Key cannot be used from this domain. - If the client does not send Origin or Referer (any server-to-server client: .NET
HttpClient, Pythonrequests, Nodeaxios, cURL, Java HTTP libraries, etc.) → the request is allowed. This is the intended behavior for server-to-server integrations.
Origin validation and allowed-origins
The Shop API Key Origin policy (/api/plugin/shops/*) does not consult the global app.widget.allowed-origins list. It only compares Origin/Referer to the registered shop.url. This is deliberate: the key is bound to a single domain on creation.
There are three request modes:
| Client | Origin header | Behavior |
|---|---|---|
Server-to-server (.NET HttpClient, Python requests, cURL, Node axios) | Absent | Allowed — X-Shop-API-Key is the only required credential |
Browser from shop.url | Matches shop.url (normalized) | Allowed |
| Browser from another domain | Differs from shop.url | 403 Origin mismatch — API Key cannot be used from this domain |
URL normalization strips protocol, www. and trailing slashes, so https://example.com, http://www.example.com/ and example.com are considered equivalent.
Difference vs the widget token
The widget token (X-Widget-Token, see Widget Token) does consult two sources to validate Origin:
-
app.widget.allowed-origins— list declared inapplication.yml:Allowed host Use localhostLocal development 127.0.0.1Local development neuroon.aiDashboard, demos and *.neuroon.aisubdomainsMatching is by host suffix: any subdomain (
foo.neuroon.ai) counts as allowed. Source: WidgetTokenValidator.java. -
shop.url— if the Origin is not in the list above, it must match the URL registered for the shop.
So, allowed-origins does not apply to the Shop API Key. To add an extra domain to the widget-token bypass list, contact support with the domain and use case (no self-service yet).
Difference vs Spring CORS
A third list, app.cors.allowed-origins (env var ALLOWED_ORIGINS, default http://localhost:3000,http://localhost:3001 for dev), only affects the backend auth CORS preflight negotiation. It does not relax either the Shop API Key validation or the widget token validation.
Generation and rotation
From the Dashboard:
- Shop → API Keys → "Create new key".
- Shown only once. Copy it to your secret manager.
- To rotate: "Rotate key" → the previous one is invalidated immediately.
Storage
Where you should store it:
- .NET: use
IConfiguration(ASP.NET Core) or a secrets manager (Azure Key Vault, AWS Secrets Manager). Never commit it in plainappsettings.json. - WordPress: define in
wp-config.phpor encryptedwp_options. - General: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GitHub Actions Secrets.
Where you must never store it:
- Git repositories (public or private).
- Logs, exported traces, error messages visible to the user.
- HTML attributes (
data-api-key="..."). - Browser-side JavaScript variables.
Examples by stack
cURL
curl -X POST "https://api.neuroon.ai/api/plugin/shops/$SHOP_ID/products/sync" \
-H "X-Shop-API-Key: $NEUROON_API_KEY" \
-H "Content-Type: application/json" \
-d @body.json
Node.js / TypeScript
const res = await fetch(
`${process.env.NEUROON_API_URL}/api/plugin/shops/${SHOP_ID}/products/sync`,
{
method: 'POST',
headers: {
'X-Shop-API-Key': process.env.NEUROON_API_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
},
);
.NET / C# (any .NET 6+)
var apiKey = HostController.Instance.GetEncryptedString(
"Neuroon.ApiKey", Config.GetDecryptionkey());
using var client = new HttpClient { BaseAddress = new Uri("https://api.neuroon.ai") };
client.DefaultRequestHeaders.Add("X-Shop-API-Key", apiKey);
var response = await client.PostAsJsonAsync(
$"/api/plugin/shops/{shopId}/products/sync", payload);
response.EnsureSuccessStatusCode();
Python
import os, requests
response = requests.post(
f"{os.environ['NEUROON_API_URL']}/api/plugin/shops/{SHOP_ID}/products/sync",
headers={
"X-Shop-API-Key": os.environ["NEUROON_API_KEY"],
"Content-Type": "application/json",
},
json=payload,
timeout=30,
)
response.raise_for_status()
Errors
| Code | Typical message | Cause |
|---|---|---|
401 | Invalid or missing API Key | Missing X-Shop-API-Key or wrong format (not sk_*) |
403 | Origin mismatch — API Key cannot be used from this domain | Browser calling with Origin different from shop.url |
403 | Shop ID mismatch | The sk_* does not belong to the shopId in the URL |
429 | rate_limit_exceeded | Per-endpoint bucket exhausted — see Rate limits |
Further reading
- Widget Token — frontend authentication.
- Rate limits — per-endpoint quotas and backoff.
- Errors — full envelope.