Skip to main content

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.

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 match shop.url. Otherwise → 403 Forbidden with message Origin 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, Python requests, Node axios, 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:

ClientOrigin headerBehavior
Server-to-server (.NET HttpClient, Python requests, cURL, Node axios)AbsentAllowed — X-Shop-API-Key is the only required credential
Browser from shop.urlMatches shop.url (normalized)Allowed
Browser from another domainDiffers from shop.url403 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:

  1. app.widget.allowed-origins — list declared in application.yml:

    Allowed hostUse
    localhostLocal development
    127.0.0.1Local development
    neuroon.aiDashboard, demos and *.neuroon.ai subdomains

    Matching is by host suffix: any subdomain (foo.neuroon.ai) counts as allowed. Source: WidgetTokenValidator.java.

  2. 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:

  1. Shop → API Keys → "Create new key".
  2. Shown only once. Copy it to your secret manager.
  3. 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 plain appsettings.json.
  • WordPress: define in wp-config.php or encrypted wp_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

CodeTypical messageCause
401Invalid or missing API KeyMissing X-Shop-API-Key or wrong format (not sk_*)
403Origin mismatch — API Key cannot be used from this domainBrowser calling with Origin different from shop.url
403Shop ID mismatchThe sk_* does not belong to the shopId in the URL
429rate_limit_exceededPer-endpoint bucket exhausted — see Rate limits

Further reading