Retry failed tasks
Every integration eventually hits a downstream service that times out, a DNS blip, or a 503 that clears itself in nine seconds. The Retry Policy API lets you attach exponential backoff and a dead-letter queue to any flow step in one call, so those transient failures stop turning into pages at 3am. Configure it once and every task in the flow inherits the same discipline.
The problem: transient failures shouldn't be your problem
Most task failures inside an automation pipeline are not real failures — they are a service warming up, a rate limit resetting, or a load balancer mid-deploy. Teams that hand-roll retry logic end up with inconsistent rules scattered across scripts: one worker retries three times, another retries forever, a third just fails loudly. flow.retry centralizes that decision so every step in a flow follows the same backoff curve, and engineers stop debugging retry logic instead of the actual business problem.
How it works
You send a POST to /flow/retry with the flow or step identifier and a policy: base delay, multiplier, maximum attempts, and an optional jitter flag to avoid synchronized retry storms across many tasks. The call is asynchronous and returns a task_id immediately; the policy is applied and confirmed via signed webhook or a signed link valid for 24 hours. Once set, any matching task that fails is automatically requeued with delays that grow exponentially — for example 1s, 2s, 4s, 8s — until it either succeeds or exhausts the configured attempts.
Where the dead-letter queue comes in
Exponential backoff handles the common case, but some failures are permanent: a malformed payload, a revoked credential, a target that will never respond. After the final retry attempt, the task lands in a dead-letter queue instead of vanishing or spinning forever. You get a clear, itemized error rather than a silent drop, and you can inspect, replay, or discard dead-lettered tasks without touching the rest of the flow.
A brief note on the pattern
Exponential backoff dates back to the original Ethernet collision-avoidance algorithm, and the dead-letter queue concept comes from enterprise messaging systems built to keep one bad message from blocking an entire queue. Both ideas exist for the same reason: distributed systems fail in small, recoverable ways far more often than in catastrophic ones, and treating every failure as identical wastes both compute and attention.
Fitting it into automation
Because the policy is set per flow or per step, you can be aggressive on cheap, idempotent calls and conservative on anything that mutates state or costs money downstream. It composes cleanly with the rest of the flow engine: retries happen inside the same execution graph, webhook notifications fire on both eventual success and final dead-letter, and nothing needs a separate cron job or watchdog process to babysit it.
What you can do with it
Flaky third-party webhooks
A partner API occasionally returns 502 during their own deploys; instead of failing your flow, the step retries with backoff and usually succeeds within seconds.
Rate-limited enrichment calls
A data-enrichment step that occasionally hits a rate limit backs off automatically instead of hammering the same endpoint and getting temporarily blocked.
Batch imports with a bad record
Out of 10,000 rows, three have malformed data and will never succeed — they land in the dead-letter queue after retries so the other 9,997 aren't held up.
Payment webhook confirmation
A confirmation call to a downstream ledger fails once due to a cold start; exponential backoff retries it before escalating, avoiding a false failure alert.
FAQ
What does the automatic retry API actually configure?
It sets a per-flow or per-step retry policy — base delay, backoff multiplier, maximum attempts, and optional jitter — that applies automatically whenever that step fails.
Is flow.retry free to use?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
Do I get charged if a retry attempt fails?
No. A failed task is never charged. The system attempts up to 3 retries internally and only a clear final error is returned if all attempts fail.
What happens after the maximum retries are exhausted?
The task moves to a dead-letter queue instead of being silently dropped, so you can inspect, replay, or discard it without disrupting the rest of the flow.
How do I get the result of a POST /flow/retry call?
The endpoint is asynchronous and returns a task_id immediately; results arrive via signed webhook, which is recommended, or a signed link valid for 24 hours.
Can I use different retry policies for different steps in the same flow?
Yes, policies are set per flow or per step, so you can be aggressive on cheap idempotent calls and conservative on anything that mutates state.
What is jitter and why would I enable it?
Jitter randomizes retry delays slightly so that many tasks failing at once don't all retry in the same instant and overwhelm the downstream service again.
Does this replace the need for my own error monitoring?
No, it complements it: transient errors are absorbed automatically, while dead-lettered tasks still surface a clear error you can route into your own monitoring.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/flow/retry \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"…"}'const res = await fetch("https://api.kit.forhosting.com/flow/retry", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"input": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/flow/retry",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"input": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/flow/retry", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"input":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"input":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/flow/retry", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"input": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "flow.retry",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |