Errors, retries and timeouts
Configure how a step behaves when it fails, how retries and timeouts work, and the shape of the error responses the Loopfour API returns.
This page covers two kinds of failure: a step that fails inside a run, and an API request that Loopfour refuses. The plain-language guide for people reviewing runs is Fix a failed run.
When a step fails
Each step can carry an errorHandling object:
{
"id": "sync_customer",
"type": "action",
"name": "Update the CRM record",
"action": "hubspot.updateContact",
"config": { "...": "..." },
"errorHandling": {
"strategy": "continue",
"fallback": { "synced": false }
}
}strategy | What happens |
|---|---|
fail (default) | The step is recorded as failed, the run stops and its status becomes failed |
continue | The step is recorded as failed and the run goes on to the next step. Later steps read a replacement output (below) |
retry | The run engine does not retry the step. The run stops as with fail |
With continue, the step's output becomes:
- without a
fallback:{ "error": true, "message": "<error message>" }; - with a
fallback: the fallback fields, plusmessageandoriginalErrorset to the error message.
When the failing action names the records behind the failure, the output also has them in details, for example {{steps.sync_customer.details.<field>}}. An action keeps only short text, finite numbers and true/false values there, so a field can be missing.
A later Condition step can branch on it, for example { "left": "{{steps.sync_customer.error}}", "operator": "eq", "right": "true" }.
Rejected approvals
A rejected Approval step, or one that times out without approval, fails the step and stops the run. With strategy: "continue" the run goes on, and the step's output holds the decision plus error: true and a message.
Retries and timeouts per step
The step schema accepts retryConfig and timeoutMs, but the run engine does not apply them today, and a failed run is not retried on its own. Retries happen inside a block instead:
- Some integration actions retry a provider call on their own when it answers
408,429or a5xxstatus, or on certain network errors. By default only repeat-safe methods (GET,HEAD,PUT,DELETE) are retried, so aPOSTthat creates a record is not sent twice. Whether an action does this is set per action. - The API Request block has its own retry and timeout settings.
To limit a whole run, set settings.maxDurationSeconds on the workflow (minimum 5). The run is stopped when the limit is reached. See Settings.
Step error codes
A failed step's entry in the run's stepResults has an error object with a code and message:
code | Meaning |
|---|---|
STEP_ERROR | The step's action threw an error. details.errorClass and details.causeChain describe the underlying error |
INPUT_VALIDATION_FAILED | The resolved config did not match the step's inputSchema. details.validationErrors lists the problems |
OUTPUT_VALIDATION_FAILED | The step's output did not match its outputSchema |
A run that cannot find a secret it references fails with the message Missing required secrets: <keys>.
Retry or restart a failed run
| Call | Scope | What it does |
|---|---|---|
POST /runs/:id/retry | workflows:execute | Starts a new run for a failed or cancelled run, from the first failed step, or from the first step a cancelled run did not finish. Earlier results are reused |
GET /run-observability/runs/:id/retry-preview | runs:read | Shows which steps a retry or restart would run, with a planFingerprint |
POST /runs/:id/restart | workflows:execute | Starts a new run from fromStepId, optionally with overrideInput. Send the planFingerprint from the preview; if the plan has changed since, the API answers 409 with code RETRY_PLAN_CHANGED |
POST /runs/:id/cancel | workflows:execute | Stops a run that is still going |
Retry and restart answer 400 with code INVALID_STATE for a run that is still live. Branch on error.code, not on the HTTP status. The full request and response shapes are in the Runs API.
A retried step runs its action again. If the step created a record in another system before it failed, check that system before you retry, so you do not create the record twice.
API error responses
Most errors share one envelope:
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Workflow not found",
"requestId": "req_7f3a...",
"details": { }
}
}details is present only on some errors. Quote requestId when you contact support.
| Status | Common codes | When |
|---|---|---|
400 | BAD_REQUEST, VALIDATION_ERROR, WORKFLOW_NOT_ACTIVE, INVALID_STATE | The request cannot be applied as sent. POST /workflows/:id/run answers WORKFLOW_NOT_ACTIVE for a workflow that is not active, and VALIDATION_ERROR with details.errors when the input fails the workflow's inputSchema |
401 | UNAUTHORIZED | Missing, invalid or expired API key |
403 | FORBIDDEN | The key lacks a scope; the message names it |
404 | NOT_FOUND | The resource does not exist in your company |
408 | SYNC_TIMEOUT | A sync run did not finish in time and onTimeout was throw_error |
409 | CONFLICT, RETRY_PLAN_CHANGED | The resource changed under you, or a name is already used. Reload and try again |
422 | UNPROCESSABLE_ENTITY and specific codes | Well-formed but cannot be applied; retrying the same request will not help |
429 | RATE_LIMITED | Too many requests. Wait for the seconds in Retry-After |
500 | INTERNAL_ERROR, TRIGGER_ERROR | Something failed on Loopfour's side. TRIGGER_ERROR means the run was recorded but could not be queued |
502 | UPSTREAM_ERROR | A provider Loopfour called could not be reached |
503 | SERVICE_UNAVAILABLE | A dependency is down for a moment. Retry later |
Request body validation
When a request body does not match an endpoint's schema, the 400 body lists each problem instead of a code:
{
"success": false,
"error": {
"name": "ZodError",
"issues": [
{ "path": ["steps"], "message": "Too small: expected array to have >=1 items" }
]
}
}Read issues[].path to find the field.
Inbound webhook routes
The /webhooks/... routes do not use the envelope. They answer with a short body such as { "error": "Invalid signature" } (401) or { "error": "Unknown provider: <name>" } (404). See Webhook trigger.
Retry your own requests safely
- Retry
429,502,503and network errors with a growing wait. On429, wait at leastRetry-Afterseconds. - Do not retry other
4xxresponses unchanged; fix the request first. - Send an
idempotencyKeywithPOST /workflows/:id/run. A repeat with the same key for the same workflow returns the existing run instead of starting a second one.
Variable references
Reference data between steps with template expressions, using the roots that resolve at run time, such as input, steps, secrets and trigger.
Triggers
Reference for the trigger types that start a workflow, from API calls and webhooks to schedules, inbound email and events in other systems.
Loopfour