Errors
One envelope, one spelling, and diagnostics that name the admissible set.
Every failure — including mistyped paths and unsupported methods — answers inside the same envelope:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "trigger config: \"61 * * * *\" is not a valid cron expression"
}
}code is a stable SCREAMING_SNAKE identifier to branch on; message is for humans and logs. One spelling, enforced by GoRunner's own build — you will never meet forbidden on one route and FORBIDDEN on another.
Status mapping
| Status | Codes you'll meet | Notes |
|---|---|---|
400 | BAD_REQUEST, VALIDATION_ERROR | Malformed JSON says malformed; an oversize body says too large — they are not conflated. |
401 | UNAUTHORIZED | Only ever on credential problems — never on 400s or 403s. |
403 | FORBIDDEN | Names the missing permission. |
404 | NOT_FOUND | Includes cross-tenant resources (non-disclosure). |
405 | METHOD_NOT_ALLOWED | Wrong method on a real path — still inside the JSON envelope. |
409 | CONFLICT | Stale expectedRev, a flow busy under another write, replayed-but-unreadable idempotent responses. |
422 | VALIDATION_ERROR | Semantically invalid: unknown piece, bad operator, unpublishable definition. |
429 | RATE_LIMITED, CONCURRENCY_LIMIT | Carries Retry-After and X-RateLimit-*. |
503 | QUEUE_UNAVAILABLE, WORKER_UNAVAILABLE, STORAGE_UNAVAILABLE | A dependency is down: dispatch, the worker fleet (a synchronous webhook with no worker is refused rather than silently queued), or the object store. Carries Retry-After where retrying helps. |
5xx | INTERNAL | Carries X-Request-ID for correlation. |
Diagnostics that help you fix it
Validation failures don't just refuse — they name the location, the observed value, and the legal alternatives, with the nearest miss first:
{
"ok": false,
"diagnostics": [{
"severity": "error",
"path": "trigger→check_total",
"message": "condition operator \"greaterThan\" is not one the engine implements",
"alternatives": ["num_gt", "num_gte", "text_contains", "..."]
}]
}This shape is deliberate: feedback naming the admissible set is what lets a retry loop — scripted or model-driven — converge instead of guessing. POST /api/workflows/validate gives you the full diagnostic report without writing anything; the publish gate refuses on the first blocking diagnostic from the same function, so they can never disagree.