Errors & retries¶
HTTP status codes¶
| Status | Meaning | What to do |
|---|---|---|
200 OK / 201 Created / 204 No Content |
Success | Use the response body. |
400 Bad Request |
Validation failed (missing required field, malformed JSON, unknown enum value). | Fix the request; do not retry as-is. |
401 Unauthorized |
JWT expired, missing, or invalid. | Re-authenticate via /api/Login/Authenticate and replay the request. |
403 Forbidden |
The authenticated tenant lacks access to the requested resource. | Confirm the resource belongs to your tenant; do not retry. |
404 Not Found |
Resource (batch, work item) does not exist within your tenant. | Verify the id; check whether the resource was created with a different tenant's credentials. |
409 Conflict |
Optimistic-concurrency clash on RowVersion (someone else updated the resource since you read it). |
Re-fetch the resource and re-apply your change. |
422 Unprocessable Entity |
Domain-level validation failure — returns a list of error messages. | Fix the offending fields; do not retry as-is. |
500 Internal Server Error |
Server-side failure. | Safe to retry once after a short backoff; if it persists, contact support. |
Idempotency¶
AddWorkItem*endpoints assign a server-generatedworkItemId(andworkBatchId). Retrying anAddWorkItem*without remembering the returned ids creates a duplicate work item. If a submit fails mid-flight (network error, timeout) and you have no ids, fetch bycustomerReferenceviaGET /api/VlmMad/GetResultByReferencebefore resubmitting.GetResult/GetResultByReference/ status-poll endpoints are idempotent — safe to retry.- All actions are tenant-scoped through the JWT, so retries cannot leak into another tenant.
Rate limits¶
No HTTP-level rate limit is enforced today. Treat this as an implementation detail that may change; if you see 429 Too Many Requests in the future, retry with exponential backoff starting at 1s.
Webhook delivery¶
Result webhooks are sent by WebhookService, which runs as a cron job every 3 minutes. Each work item with a configured webhookUrl has a WebhookCallCount budget (default from AppSettings.WebhookRetryCount); failures decrement that counter and schedule the next attempt with polynomial backoff — the delay is (maxRetryCount - remainingCalls)^2 * 30 seconds, so retries spread out further over time. Both transient network failures and non-2xx responses (4xx and 5xx) are retried until the counter reaches zero. Your webhook receiver must therefore:
- Accept the same
workItemIdmore than once and treat duplicates as no-ops. - Respond
200quickly (within a few seconds); do the heavy work asynchronously. - Use the payload itself as the source of truth — do not assume webhook delivery order.
If your endpoint exhausts the retry budget, the server gives up and the result is still queryable via the normal status-poll endpoints (GetResult / GetResultByReference).