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

Note: the Widget Token (X-Widget-Token, see Widget Token) uses a different validation matrix — it additionally allows trusted domains (neuroon.ai and subdomains, localhost, 127.0.0.1). That trusted list does not apply to the Shop API Key, which is only compared against shop.url.

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: User Secrets in development; Azure Key Vault or appsettings.{Environment}.json mounted as a secret in production. Never in plain web.config or hardcoded.
  • WordPress: define in wp-config.php (which sits outside the docroot) or in encrypted wp_options.
  • General: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GitHub Actions Secrets, Doppler, etc.

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 = builder.Configuration["Neuroon:ApiKey"]
?? throw new InvalidOperationException("Missing Neuroon:ApiKey");
var apiBase = builder.Configuration["Neuroon:ApiBaseUrl"]
?? "https://api.neuroon.ai";

using var client = new HttpClient { BaseAddress = new Uri(apiBase) };
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