Shop API Key
La Shop API Key autentica las llamadas server-to-server contra /api/plugin/shops/*. Sirve para sincronizar el catálogo, registrar conversiones, verificar el dominio y consultar metadatos de la tienda.
Cabecera
X-Shop-API-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Formato
sk_ seguido de 32 caracteres hexadecimales. Longitud total ~35 caracteres. Es permanente hasta rotación: no caduca por tiempo.
Validación de Origin
El filtro valida la cabecera Origin (o Referer como fallback) contra el dominio registrado en la tienda:
- Si el cliente es un navegador y envía
Origin, debe coincidir conshop.url. En caso contrario →403 Forbiddencon mensajeOrigin mismatch — API Key cannot be used from this domain. - Si el cliente no envía Origin ni Referer (cualquier cliente server-to-server:
HttpClientde .NET,requestsde Python,axiosde Node,cURL, librería HTTP de Java, etc.) → la petición se permite. Es el comportamiento intencionado para integración server-to-server.
Origin validation y allowed-origins
La política de Origin de la Shop API Key (/api/plugin/shops/*) no consulta la lista global app.widget.allowed-origins. Sólo compara Origin/Referer contra shop.url registrado para esa tienda. Esto es deliberado: la clave nace ligada a un dominio.
Hay tres modos de petición:
| Cliente | Header Origin | Comportamiento |
|---|---|---|
Server-to-server (HttpClient .NET, requests Python, cURL, axios Node) | Ausente | Se permite — X-Shop-API-Key es la única credencial requerida |
Navegador desde shop.url | Coincide con shop.url (normalizado) | Se permite |
| Navegador desde otro dominio | Distinto a shop.url | 403 Origin mismatch — API Key cannot be used from this domain |
La normalización de URL elimina protocolo, www. y barras finales, así que https://example.com, http://www.example.com/ y example.com se consideran equivalentes.
Diferencia con el widget token
El widget token (X-Widget-Token, ver Widget Token) sí consulta dos fuentes para validar Origin:
-
app.widget.allowed-origins— lista declarada enapplication.yml:Host permitido Uso localhostDesarrollo local 127.0.0.1Desarrollo local neuroon.aiDashboard, demos y subdominios *.neuroon.aiLa coincidencia es por suffix sobre el host: cualquier subdominio (
foo.neuroon.ai) cuenta como permitido. Fuente: WidgetTokenValidator.java. -
shop.url— si el Origin no está en la lista anterior, debe coincidir con la URL registrada en la tienda.
Por tanto, allowed-origins no aplica a la Shop API Key. Para añadir un dominio adicional al bypass del widget token, contacta soporte con el dominio y caso de uso (no se autoservicio aún).
Diferencia con CORS de Spring
Existe una tercera lista, app.cors.allowed-origins (variable de entorno ALLOWED_ORIGINS, default http://localhost:3000,http://localhost:3001 para dev). Esto sólo afecta a la negociación CORS preflight a nivel de backend auth; no relaja ni la validación de la Shop API Key ni la del widget token.
Generación y rotación
Desde el Dashboard:
- Tienda → API Keys → "Crear nueva clave".
- Se muestra una sola vez. Cópiala a tu secret manager.
- Para rotar: "Rotar clave" → la antigua queda invalidada al instante.
Almacenamiento
Sitios donde sí debes guardarla:
- .NET: usa
IConfiguration(ASP.NET Core) o un secret manager (Azure Key Vault, AWS Secrets Manager). Nunca enappsettings.jsonplano comprometido. - WordPress: define en
wp-config.phpowp_optionscifrado. - Generales: AWS Secrets Manager, Azure Key Vault, HashiCorp Vault, GitHub Actions Secrets.
Sitios donde nunca debes guardarla:
- Repositorios Git (público o privado).
- Logs, traces exportadas, mensajes de error visibles al usuario.
- Atributos del HTML (
data-api-key="..."). - Variables JavaScript del navegador.
Ejemplos por 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# (cualquier .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()
Errores
| Código | Mensaje típico | Causa |
|---|---|---|
401 | Invalid or missing API Key | Falta X-Shop-API-Key o el formato no es sk_* |
403 | Origin mismatch — API Key cannot be used from this domain | El navegador llama con Origin distinto a shop.url |
403 | Shop ID mismatch | La sk_* no pertenece al shopId de la URL |
429 | rate_limit_exceeded | Bucket por endpoint agotado — ver Rate Limits |
Próximas lecturas
- Widget Token — autenticación frontend.
- Rate Limits — cuotas por plan y backoff.
- Errores — estructura completa.