REST API Rate Limits: Handle 429 Errors Without Losing Sync Progress

REST APIs
Laravel
API Integration
Rate Limiting
HTTP 429
Background Jobs
Data Synchronization

By Kashii Malik ? Updated

A practical guide to handling API rate limits with sensible retry delays, shared request budgets, resumable syncs, and clear failure states in Laravel integrations.

REST API Rate Limits: Handle 429 Errors Without Losing Sync Progress

A customer sync works during testing, then starts failing once a real business has several people using it. The API returns 429, the job stops halfway through, and someone clicks “Sync again.” Now the system is doing even more work while the original backlog is still waiting.

Handling that situation takes more than adding a retry loop. A useful integration needs to slow down, remember what it has finished, and explain what is happening to the person waiting for it. Here is how I would design that behavior in a Laravel app or another custom API integration.

What a 429 response actually tells you

HTTP 429 means the caller has exceeded a request limit within a period of time. It can include a Retry-After header, but that header is optional. The standard also leaves the provider to decide how requests are counted and which callers share a limit. See RFC 6585, section 4.

Before changing code, check the provider’s documentation. Is the quota attached to an account, API credential, store, endpoint, or IP address? Ten workers with separate local counters can still exhaust one shared account allowance. Adding workers may make the backlog worse.

Treat authentication failures and invalid input separately. Repeating the same request with the same invalid credentials or malformed payload will usually need a configuration or data fix. A retry policy should classify failures instead of treating every unsuccessful response as temporary.

Read Retry-After before choosing a delay

Retry-After supports two formats: a delay in seconds or an HTTP date. A value of 30 means thirty seconds, not thirty milliseconds. A date needs to be converted into a delay using the current time. These formats are defined in RFC 9110, section 10.2.3.

My recommended policy is to validate the header, avoid retrying before its stated time, and schedule the next attempt in the background. If the wait is unusually long, record a next-attempt timestamp or flag it for review. Do not silently shorten the provider’s requested wait just because your application has a smaller default delay.

When the header is missing or unusable, use a bounded backoff policy. For example, a non-urgent sync could wait roughly 2, 4, 8, then 16 seconds between successive attempts, with a small random addition each time. Those are example application settings, not universal API limits. Set an overall deadline so a permanently blocked integration cannot retry forever.

The random addition is often called jitter. Its practical purpose is simple: jobs that fail together should not all wake up and hit the API again at precisely the same moment.

Make one page of work resumable

Imagine a business portal fetching 2,000 customer records from a supplier. The exact page size depends on that supplier. Give the sync its own durable run record rather than holding its progress only in a worker’s memory.

  • Save the account, run ID, current cursor or page, last successful checkpoint, attempt count, and next permitted attempt time.
  • Fetch one page and validate the response before writing its records.
  • Write records using stable remote identifiers, with database uniqueness rules that prevent duplicate local rows.
  • Commit the page’s writes and its next checkpoint together where possible. A crash between them should not cause the application to skip unfinished work.
  • On a throttle response, keep the current checkpoint and defer the same unit of work. Mark the run complete only when all pages have been processed.

If a worker crashes after a page has been saved, replaying that page should be harmless. If the provider’s cursors expire, define a documented recovery path, such as restarting with deduplication or using a supported updated-since filter. Also check whether records can move between pages while a long sync is running.

AI-generated photorealistic editorial image of a small retailer's packing desk, laptop, paper checklist and cardboard boxes. Conceptual scene about tracking sync progress; not documentary evidence.

AI-generated photorealistic editorial image of a small retailer's packing desk, laptop, paper checklist and cardboard boxes. Conceptual scene about tracking sync progress; not documentary evidence.

Conceptual AI-generated editorial image: a packing desk and progress checklist, illustrating why business syncs need visible checkpoints.

Where Laravel helps—and where you still need a policy

Laravel’s HTTP client provides status inspection, connection and response timeouts, response headers, and configurable retries. Its ordinary requests do not automatically throw exceptions for HTTP 4xx and 5xx responses. Check the response or deliberately call throw() when that suits your error handling. The Laravel 12.x HTTP client documentation explains these behaviors.

For an application-owned queued sync, I would make one outbound attempt per job execution and defer the job when the provider asks it to wait. This keeps a long pause out of the browser request. Configure a finite retry window and account for the queue’s attempt limits; deferred work still needs a route to a visible failure state.

Keep retry ownership clear. If an HTTP helper tries three times and the queue executes that helper five times, one logical operation can produce fifteen outbound attempts. Pick the layer that owns recovery, then document any smaller retries inside it.

For providers that share a quota across workers, coordinate the request budget in shared storage. Keep different customer accounts separate only when the provider’s limit actually allows that separation. A process-local sleep does not coordinate the other workers.

A successful HTTP status is only part of the check

If your integration also talks to Shopify, do not carry a generic REST limit into every endpoint. Shopify’s API limits documentation distinguishes request-based REST Admin limits from calculated query-cost limits for the GraphQL Admin API. Read the documentation for the particular API and inspect its application-level response as well as the HTTP status.

The same design principle applies elsewhere: your adapter should translate provider-specific responses into clear outcomes such as success, wait, invalid input, or reconnect account. The rest of your application should not have to guess what a provider’s error payload means.

Do not blindly replay a write after a timeout

Reading a page again is a different risk from creating a shipment or submitting a payment. A timeout can leave you uncertain whether a remote write succeeded. Where supported, use the provider’s documented idempotency mechanism and preserve the same operation identifier across retries. Otherwise, reconcile against a remote reference before deciding to send the write again.

A local “already sent” flag alone cannot close the gap between a remote success and a local crash. For important writes, define an explicit “outcome unknown” state and a recovery procedure. Someone should be able to inspect that state without repeatedly pressing the submit button.

Test the pauses, not just the happy path

  • Return 429 with a seconds value, then with a valid HTTP date. Confirm neither case retries early.
  • Return 429 without Retry-After and with malformed values. Confirm the fallback delay and final deadline work.
  • Run two workers against the same account. Check that a shared limit is respected.
  • Crash the worker after saving records but before the next execution. Confirm replay does not create duplicate rows or skip work.
  • Simulate a timeout after a remote write. Confirm the operation is reconciled or held for review.
  • Exhaust the allowed retry window. Confirm the dashboard shows a failure and a useful next step.

Log the run ID, provider, account identifier, status, retry count, and next-attempt time. Keep tokens and unnecessary customer payloads out of logs. For the person using the app, “Paused until 10:15; 600 records saved” is more useful than an unexplained spinner. Use their local timezone and avoid an exact completion promise when the provider controls the pace.

A small change that makes integrations easier to trust

A reliable sync can explain three things: what finished, why it paused, and what will happen next. Start by making those states durable, then tune throughput with real usage data. Faster requests are only helpful when the application can recover cleanly.

For related Laravel work, see my Maven Pro company intranet and operations platform. It is an example of the business portal work I build; the sync scenario in this article is illustrative. If your portal or store integration regularly stalls on API limits, I’m happy to discuss a practical recovery flow.

Technical references checked on 31 October 2026. Provider quotas and behavior should be checked for the API version used by your application.

Share this post:

Related Posts

Laravel Scheduled Tasks: Prevent Duplicate Runs and Silent Failures
October 29, 2026

Laravel Scheduled Tasks: Prevent Duplicate Runs and Silent Failures

A practical Laravel scheduler checklist for client portals: verify cron, prevent duplicate runs, check queue workers and catch reports or reminders that never finish.

Secure File Uploads in Laravel: A Practical Client Portal Checklist
October 26, 2026

Secure File Uploads in Laravel: A Practical Client Portal Checklist

A practical guide to Laravel document uploads: validate files, keep storage private, control downloads, handle failed checks, and test the complete client portal workflow.

Google Maps Quote Forms: Secure API Keys and Reliable Pricing
October 25, 2026

Google Maps Quote Forms: Secure API Keys and Reliable Pricing

Build a transport quote form with restricted Google Maps API keys, server-side pricing, clear estimates and sensible handling of route failures.