Skip to main content

API conventions

This page summarizes the rules common to every endpoint. If your integration doesn't follow them, you will get 400 Bad Request, 401 Unauthorized, or the typical {"timestamp":..., "status":..., "error":..., "message":..., "path":...}.

Base URLs

EnvironmentBase URLWhen
Productionhttps://api.neuroon.aiReal traffic, persistent data, conversions that count
Developmenthttps://dev-api.neuroon.aiIntegration testing, error payloads, edge cases without risk

No URL versioning today — there is no /v1/ or /v2/. Breaking changes are announced in advance in the Changelog and applied to Development first. URL versioning will land later as an additive change (current routes will keep working through a grace window).

sk_* keys and Widget Tokens issued in Development do not work against Production and vice versa. Use separate environment variables:

NEUROON_API_URL=https://dev-api.neuroon.ai
NEUROON_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEUROON_SHOP_ID=shop_dev_xxxxxxxx

See Switching environments for per-stack configuration.

Format

  • Request body: JSON with Content-Type: application/json; charset=utf-8. camelCase on every field.
  • Response body: JSON camelCase. Keys are always strings; numbers may be int, long, decimal (prices). Dates are ISO 8601 (2026-05-06T10:15:00Z, or without Z if local — the backend returns UTC without offset).
  • Charset: UTF-8. Spring decodes the body as UTF-8 — bytes that are not valid UTF-8 break the JSON parser with 400.

Mandatory headers

HeaderApplies toValue
Content-Typerequests with bodyapplication/json
Acceptrecommendedapplication/json
Authorization or X-Widget-Token or X-Shop-API-Keydepending on endpointSee Authentication · Overview

Useful optional headers:

  • Accept-Language: es-ES, en-US, fr-FR, etc. — the backend uses this value for generated text (guided filters, AI assistant) when it matches one of the 9 supported locales; otherwise it falls back to es.
  • User-Agent: YourApp/1.2.3 — for your own log analysis (recommended).

Idempotency

  • POST /api/plugin/shops/{shopId}/products/sync is idempotent by externalId: sending the same externalId twice updates the product, it does not duplicate. This includes INCREMENTAL mode (default). FULL mode wipes the full catalog from the index before reinserting — use it only for initial migrations.
  • POST /api/plugin/shops/{shopId}/track/conversion accepts a unique orderId per shop; a second POST with the same orderId is silently ignored (it does not duplicate metrics).
  • POST /api/widget/track/click and POST /api/widget/track/conversion are identified by searchLogId + productId + timestamp; retries are safe.
  • The remaining endpoints are GET (no side effects) or stateless.

Latency and consistency

  • Product indexing: 2-5 seconds after 200 OK on products/sync. The product is not searchable immediately. If your integration test does GET /products/{externalId} right after the sync, wait ~5 s or poll with backoff.
  • Quotas: productsCount and searchesThisMonth in GET /shops/me are cached ~5 min by some consumers (the WordPress plugin, for example). The backend always returns the up-to-date value.
  • Tokens: the Widget Token has a 24 h TTL (86400 s). Cache it on your server with a 5-minute safety margin before expiration.

Error shape

The standard envelope is emitted by GlobalExceptionHandler for almost every error:

{
"timestamp": "2026-05-06T10:15:00",
"status": 400,
"error": "Bad Request",
"message": "externalId: must not be blank, price: must be greater than or equal to 0.0",
"path": "/api/plugin/shops/shop_abc/products/sync"
}
  • status: numeric HTTP code.
  • error: canonical status name.
  • message: field-level errors comma-joined; human-readable.
  • path: request path (without host).

Special cases with a different envelope:

  • 429 (rate limit) — WidgetRateLimitFilter emits { error, message, retryAfter, limit, remaining, timestamp }. No status, no path.
  • 401 / 403 from auth filtersApiKeyAuthenticationFilter emits { error, status } without going through the global handler.

Detail per code at Reference · Errors.

Rate limits

Throttling per endpoint, not per subscription plan:

EndpointLimit
widget-search200 req/min
widget-suggestions300 req/min
widget-analytics500 req/min
widget-compare10 req/min
sync (plugin)100 req/min
shop-info60 req/min
verification20 req/min

Response headers: X-RateLimit-Limit, X-RateLimit-Remaining. On 429, also Retry-After (seconds).

Detail and exponential backoff at Rate limits.

Pagination

GET /api/plugin/shops/{shopId}/products and derived endpoints accept:

  • page (0-indexed, default 0).
  • size (default 20, maximum 100).
  • sort (format field,direction, e.g. name,asc).
  • modifiedAfter (ISO 8601, for incremental delta sync).

Paginated response:

{
"content": [...],
"page": 0,
"size": 20,
"totalElements": 1247,
"totalPages": 63,
"first": true,
"last": false
}

CORS

  • /api/widget/*: CORS open to any origin (it is JS from customer browsers); credentials via the X-Widget-Token header whose Origin is validated against app.widget.allowed-origins and shop.url.
  • /api/plugin/*: CORS disabled by design — these are server-to-server calls. If you call from a browser and your Origin doesn't match shop.url, you get 403.
  • /api/search/*: CORS configured for the dashboard and *.neuroon.ai subdomains.

If your server-to-server integration goes through a proxy that automatically adds Origin and breaks validation, configure the proxy not to send the header.

Further reading