Reference · Errors
HTTP status codes the Neuroon API can return and the body shape. Most responses come from GlobalExceptionHandler (api/src/main/java/.../rest/GlobalExceptionHandler.java); exceptions are documented below.
Standard envelope
{
"timestamp": "2026-05-06T10:15:00",
"status": 400,
"error": "Bad Request",
"message": "field-level error joined with commas",
"path": "/api/widget/search"
}
Built in
GlobalExceptionHandler.java:147-156.
| Field | Type | Notes |
|---|---|---|
timestamp | string | LocalDateTime.now().toString() (server runs in UTC, no explicit zone). |
status | number | HTTP code. |
error | string | Reason phrase for the status (Bad Request, Unauthorized, …). |
message | string | Detail. On 400 it concatenates validation errors with , . |
path | string | Request path stripped of the uri= prefix WebRequest adds. |
HTTP codes emitted by the global handler
| Status | Java exception that triggers it | Meaning |
|---|---|---|
| 400 Bad Request | MethodArgumentNotValidException, ConstraintViolationException, MissingServletRequestParameterException, IllegalStateException, IllegalArgumentException | Validation failure (@Valid, missing @RequestParam, domain rule). |
| 401 Unauthorized | BadCredentialsException | Invalid credentials. |
| 403 Forbidden | AccessDeniedException | Token not authorized for the resource. |
| 404 Not Found | ResponseStatusException(NOT_FOUND) thrown from a controller | Missing resource (shopId, product). |
| 500 Internal Server Error | Exception (catch-all) | Unhandled exception. Report with timestamp and path. |
| 502 Bad Gateway | PaymentGatewayException | Stripe unreachable. |
| 503 Service Unavailable | CallNotPermittedException (open circuit breaker) | External provider in open state. Retry with backoff. |
Other statuses (
405,409,415, …) are emitted by Spring directly without going through the handler — the body uses Spring's default envelope ({ timestamp, status, error, path }) withoutmessage.
Special cases (different envelope)
429 Too Many Requests — rate limit
Emitted by the filters (WidgetRateLimitFilter, RateLimitFilter), not by the global handler. The envelope is different:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"retryAfter": 60,
"limit": 200,
"remaining": 0,
"timestamp": "2026-05-06T10:15:00Z"
}
Built in
WidgetRateLimitFilter.java:90-93. Associated headers:Retry-After,X-RateLimit-Limit,X-RateLimit-Remaining. See Rate Limits.
401 / 403 from ApiKeyAuthenticationFilter
When the Shop API Key fails validation, the auth filter emits directly without going through the global handler:
{
"error": "Invalid or missing X-Shop-API-Key header",
"status": 401
}
At
ApiKeyAuthenticationFilter.java:131. Same shape withstatus: 403when the origin is not authorized.
Validation errors (400)
MethodArgumentNotValidException and ConstraintViolationException are joined into message with the format field: description:
{
"timestamp": "2026-05-06T10:15:00",
"status": 400,
"error": "Bad Request",
"message": "products[0].externalId: must not be blank, products[0].price: must be positive",
"path": "/api/plugin/shops/123/products/sync"
}
GlobalExceptionHandler.java:22-47.
Best practices
- Don't regex-parse
message— the format is not stable. - Retry only on
429,502,503with exponential backoff + jitter. - Don't retry on
400,401,403,404— those are client failures. - Log
timestamp,pathand, if available,requestIdto correlate with the server side when opening a ticket. - Distinguish the two envelopes — most use
{ timestamp, status, error, message, path }; rate-limit and auth filters use their own formats.