How to Build a Retry-with-Fallback Pattern for Flaky REST APIs
Poll an unreliable REST endpoint on a schedule in Spojit, detect failures with a Condition node, fall back to a secondary endpoint and then a cached value, and alert a Slack channel only when every path has failed.
What This Integration Does
Some upstream REST APIs are unreliable: they time out, return 500s under load, or briefly go offline during deploys. If your workflow simply calls the endpoint and moves on, a single transient failure pollutes downstream data or pages your team for nothing. This tutorial builds a resilient read pattern in Spojit that tries a primary endpoint, falls back to a secondary endpoint when the primary fails, falls back again to a last-known-good cached value, and only escalates to Slack when all three sources are exhausted. The result is a workflow that stays quiet during normal blips and speaks up only when there is a real outage worth a human's attention.
The workflow runs on a Schedule trigger (for example every five minutes), so it self-heals without any external caller. Each run calls the primary endpoint with the http connector's http-get tool, inspects the returned status with a Condition node, and branches: success continues normally, failure routes to the fallback chain. The cached value is read from and written to a small store you control (for example a JSON document fetched over HTTP). Runs are independent and stateless apart from that cache, so a later run automatically recovers once the primary comes back. Re-runs are safe because a read pattern has no side effects until the final notify step, and the Slack alert fires only on total failure.
Prerequisites
- An http connection for your primary and secondary REST endpoints. The http connector is built in and needs no auth for public endpoints; for authenticated endpoints, add an http connection that carries your base URL and default headers (such as an API key).
- A Slack connection with permission to post to your alert channel, and the channel ID or name you want alerts in.
- A reachable "cache" location that returns your last-known-good payload over HTTP (any endpoint you control that supports
http-getto read andhttp-putorhttp-postto write). - A workspace where you can create a scheduled workflow, and an IANA timezone for the schedule (for example
Australia/Sydney).
Step 1: Start with a Schedule trigger
Add a Trigger node and set its type to Schedule. Schedules use a 5-field Unix cron expression plus a timezone. To poll every five minutes, enter the cron value */5 * * * * and pick your timezone, for example Australia/Sydney. A single trigger can hold multiple schedules if you want a denser cadence during business hours and a sparser one overnight. The trigger output is { scheduledAt }, which you can reference downstream as {{ trigger.scheduledAt }} if you want to stamp the poll time onto your alert.
Step 2: Call the primary endpoint with http-get
Add a Connector node in Direct mode, choose the http connector, and select the http-get tool. Map its fields:
url: your primary endpoint, for examplehttps://api.example.com/v1/status.headers: any required key-value headers, such asAccept: application/json.timeout: a request timeout in milliseconds, for example5000, so a hung endpoint fails fast instead of stalling the run.
The tool returns status (the HTTP status code), statusText, headers, and data (the parsed JSON body, or the raw string if it was not JSON). Name the output variable primary so you can read {{ primary.status }} and {{ primary.data }} later. The http tools do not throw on a 4xx or 5xx response; they hand you the status so you decide what counts as a failure.
Step 3: Detect failure with a Condition node
Add a Condition node right after the primary call. Define the success test as the primary returning a healthy status code, for example {{ primary.status }} is greater than or equal to 200 and less than 300. Wire the True output to your normal downstream processing (whatever you do with a good payload, such as a Transform node or a database write). Wire the False output into the fallback chain in the next step. Treat a timeout or any non-2xx as a failure so the workflow tries the next source instead of trusting a bad response.
Step 4: Fall back to a secondary endpoint with http-request
On the False branch, add a second Connector node in Direct mode on the http connector. Use the http-request tool so you can fall back to a differently shaped call (for example a mirror with a different method or path):
method:GET(or whatever the mirror requires).url: your secondary endpoint, for examplehttps://api-mirror.example.com/v1/status.headers: any headers the mirror needs.timeout: again a tight value such as5000.
Name the output secondary. Add another Condition node testing {{ secondary.status }} for a 2xx result. On True, treat {{ secondary.data }} as your payload and (optionally) write it back to the cache with a http-put call so the cache stays fresh:
http-put
url: https://cache.example.com/last-known-good
body: {{ secondary.data }}
headers:
Content-Type: application/json
On False, continue to the cached-value fallback in the next step.
Step 5: Fall back to the cached last-known-good value
When both live endpoints have failed, add a third Connector node in Direct mode on the http connector using http-get to read your cache:
url:https://cache.example.com/last-known-good.timeout:5000.
Name the output cached. Add a final Condition node testing whether the cache itself returned a usable value, for example {{ cached.status }} is in the 2xx range and {{ cached.data }} is not empty. On True, your workflow degrades gracefully: it proceeds with stale-but-valid data and no alert is sent. On False, you have exhausted every source, so route to the alert step.
Step 6: Alert Slack only when every path fails
On the final False branch (primary failed, secondary failed, cache empty), add a Connector node in Direct mode, choose the Slack connector, and select the send-message tool. Map its fields to your alert channel and a message that includes the failing statuses so responders have context:
send-message
channel: C0123456789
text: |
Status poll failed on all sources at {{ trigger.scheduledAt }}.
Primary status: {{ primary.status }}
Secondary status: {{ secondary.status }}
Cache status: {{ cached.status }}
Investigate https://api.example.com/v1/status
Because this node sits only on the all-failed branch, Spojit stays silent during the normal case and during single-source blips. If you would rather look up the channel from a name, call lookup-user-by-email or list-channels first; for a simple fixed alert channel, the channel ID is enough.
Step 7: Add a heartbeat with a Send Email node (optional)
If you want a record that the poll ran even on quiet days, add a Send Email node on the recovery branches to mail yourself a short daily summary, or keep it strictly for failures by placing it next to the Slack alert. The Send Email node sends from Spojit's built-in mail service with no connection needed: set Recipients, a templated Subject such as API outage detected at {{ trigger.scheduledAt }}, and a plain-text Body. External recipients must be on your org allowlist under Settings > General > Email recipients. To keep noise down, prefer Slack for real-time alerts and reserve email for a scheduled digest.
Tips
- Keep every
timeouttight (3000 to 8000 ms). A flaky API is more useful failing fast than hanging your whole run. - Write the cache only from a successful primary or secondary response (Step 4), never from the cache-read branch, so a bad value can never overwrite a good one.
- Use Miraxa, the intelligent layer across your automation, to scaffold the branching quickly: try "Add a Condition node that checks if
{{ primary.status }}is between 200 and 299 and connect the false branch to an http connector node callinghttp-request." Then fine-tune fields in the properties panel. - If your primary and secondary share auth, set the API key once as a default header on the http connection rather than repeating it in every node.
Common Pitfalls
- Trusting the absence of an error. The http tools return a 4xx or 5xx as a normal
statusvalue, so you must test{{ primary.status }}in a Condition. Skipping that test means a 503 page sails through as "success". - Cron timezone surprises. The schedule fires in the IANA timezone you pick, not UTC.
*/5 * * * *inAustralia/Sydneyshifts with daylight saving, so confirm the timezone matches your on-call expectations. - Alert storms. Without the cache layer, a multi-hour outage at a 5-minute cadence pages Slack a dozen times an hour. Letting a valid cached value suppress the alert keeps escalation meaningful.
- Empty-but-200 cache responses. A cache that returns
200with an empty body will look healthy. Test both{{ cached.status }}and that{{ cached.data }}is non-empty before treating it as a valid fallback.
Testing
Before turning the schedule on, validate each branch in isolation. Temporarily point the primary url at a known-bad address (or an endpoint you can force to 500) and run the workflow manually to confirm the False branch reaches the secondary call. Repeat with a bad secondary to confirm the cache read fires, and with an empty cache to confirm the Slack alert posts to the right channel with the expected statuses. Watch each run in the execution history to see which path was taken. Once every branch behaves, restore the real URLs and enable the Schedule trigger, starting with a sparse cadence (such as every 15 minutes) before tightening it.
Learn More
- HTTP connector reference for every tool, field, and the response shape.
- Condition node documentation for building if/else branches.
- Trigger node documentation for schedule cron syntax and timezones.
- How to Handle Errors and Build Fallback Workflows for broader error-handling patterns.
- How to Connect to Any REST API Using HTTP Requests for setting up authenticated http calls.
- Setting Up a Schedule Trigger for cron and timezone details.
- How to Set Up Slack Alerts for Workflow Failures for richer failure notifications.