Home Blog Contact
Home/Blog/Why One API Key Throttles Six Klaviyo Brands
ArticleKlaviyoKlaviyoAPIMulti-Account

Why One API Key Throttles Six Klaviyo Brands

10 min readBy Miloš Mitrović

If your integration authenticates to six Klaviyo accounts with six private API keys, you do not get six quotas that belong to you. Klaviyo applies rate limits per account and per endpoint, so each of your keys draws from the same pool as every other private key hitting that endpoint in that account, including the loyalty app, the headless storefront script and the automation a previous agency left running. That is why the same nightly job throttles on brand A this week and brand D next week with nothing changing in your code. The fix is to stop treating the quota as yours, instrument requests per account and per endpoint, reduce volume, and move any integration you distribute onto OAuth so each install carries its own limit.

Key takeaways

  • Klaviyo rate limits are scoped to the account and the endpoint, not to the API key, so a private key competes with every other private-key integration in that account.
  • OAuth app installs are quota isolated per install, which is the only structural fix if you distribute the same integration to many brands.
  • Throttling that moves between brands is a symptom of different neighbours per account, not of an unstable API.
  • Log account, endpoint, status and any retry header on every request. If your own request count in a minute is well under the endpoint limit and you still get 429, the noise is coming from someone else.
  • Size your client-side limiter to roughly 60 to 70 percent of the documented steady limit, because you never own the whole ceiling.
  • A swallowed 429 turns into a missing row, not an error, and that is how rollup reports quietly go wrong.

What exactly shares the quota

The unit of rate limiting is the account plus the endpoint, and the API key is only a credential attached to it. Klaviyo assigns each endpoint a tier with two components, a burst limit measured per second and a steady limit measured per minute, and those tiers differ substantially between a lightweight read and a heavy query, as documented in Klaviyo's rate limits and error handling reference. Two things follow that teams routinely get wrong.

First, creating a second private key for your job does not create a second allowance. Keys are for attribution and revocation, and Klaviyo treats them as ways to authenticate requests rather than as quota partitions. Second, the endpoint tier is what you are competing on, so your profile reads can be throttled while your event writes run fine in the same minute, because the neighbour causing the pressure only touches one of them.

OAuth is the exception that matters. When a brand installs your Klaviyo app through the OAuth authorization flow, that install is metered on its own rather than out of the account's shared private-key pool. Same code, same endpoints, different accounting.

Why the throttling moves between brands

It moves because the neighbours differ per account, and you have no visibility into them. Brand A runs a subscription app that polls profiles every sixty seconds. Brand D runs a reviews platform that pushes events in hourly bursts. Brand F has nothing but you. Your identical worker, on an identical schedule, sees three different ceilings.

The pattern is usually time shaped as well as account shaped. Campaign send days generate a spike of activity from every connected tool at once. A client's warehouse job that backfills at 02:00 will sit exactly on top of your 02:00 sync. And if you run all six accounts from a single scheduler that fires them in parallel at the top of the hour, you also collide with yourself on any endpoint whose limit you are near.

The practical consequence: any capacity plan built from one week of one account is fiction. You are sizing against a ceiling that other people can move without telling you.

How to prove your key is not the only consumer

Log every request with enough context to answer the question, then compare your own rate against the failures. At minimum, record account identifier, key identifier, HTTP method, endpoint path, status code, duration, and the value of any rate limit or retry header the response carried. Aggregate 429s by account, endpoint and minute.

What you seeWhat it means
Your own requests in that minute are well under the endpoint's steady limit, and you still get 429Another integration in that account is consuming the pool
429s cluster on one endpoint only, across several accountsYour own access pattern is too heavy on that endpoint
429s appear only when several accounts run in parallelSelf collision in your scheduler, fix concurrency before blaming neighbours
429s spike at a fixed clock time in one accountA scheduled third-party job you can move around rather than fight

Then inventory the account. List the private keys in the account settings with an owner for each, list the installed integrations, and get a person to confirm what each one does before you revoke anything. In most inherited accounts you will find at least one key nobody can identify. Treat that the way you would treat any other unowned asset in an account you took over, with the same caution described in auditing a portal you have just inherited.

Isolating quota with OAuth instead of a private key

If you ship the same integration to more than a handful of brands, register a Klaviyo app and use OAuth, because per-install metering is the only change that structurally removes you from the shared pool. It also gives you scoped access and token rotation instead of a long-lived secret sitting in an environment file.

Be honest about the cost. You are implementing the authorization code flow, storing and refreshing tokens per install, handling revocation and scope changes, and building an install lifecycle you did not previously have. For a team that has not done this before, plan in weeks. For an internal tool serving six brands the same owner controls, the calculation is closer, and the interim answer is usually good enough: one dedicated key per account per job, a client-side limiter, and headroom.

Say the interim answer out loud so nobody misreads it. Separate keys per job do not give you separate quota. They give you attribution, so that when 429s appear you can tell your own reporting sync apart from your own profile writer in the logs, and revoke one without breaking the other.

Backing off so retries do not make it worse

Rate limit yourself before the API does it for you, per account and per endpoint tier, using a token bucket sized to about 60 to 70 percent of the documented steady limit. You are leaving that headroom deliberately, because the rest of the pool belongs to integrations you do not control.

When a 429 does arrive, honour the retry header if the response includes one. That header is a standard instruction from the server about how long to wait, defined in RFC 9110, and it beats any guess your client makes. If there is no header, use exponential backoff with full jitter rather than fixed intervals. Six workers retrying on the same fixed schedule resynchronise on every failure and produce the herd behaviour that the AWS Builders' Library piece on timeouts, retries and backoff describes in detail. Jitter is what breaks the lockstep.

Three more things belong in the client. Cap concurrency per account rather than globally, so one brand cannot consume all your workers. Add a circuit breaker that parks an account's queue after a run of consecutive 429s on the same endpoint and lets the other five accounts finish. And make writes idempotent, because a retry that duplicates a profile or an event costs you more to clean up than the throttle cost you in the first place.

Cutting request volume before you tune retries

The cheapest capacity is the request you never send, and most multi-account syncs send several times more than they need. Work through these in order.

  • Use bulk jobs for writes. Upserting profiles one at a time is the single most common cause of self-inflicted throttling. The bulk profile import job endpoint replaces thousands of individual calls with a job you poll.
  • Sync incrementally. Filter on an updated timestamp and store a high water mark per account. A full scan of 300,000 profiles every night is a choice, not a requirement.
  • Check what your query costs. Requesting expensive additional fields can move an endpoint into a lower tier, so read the tier for the exact call you are making rather than the endpoint family.
  • Stop polling for things that can be pushed. Event driven feeds remove entire read loops, though they introduce their own gaps worth understanding, which is the subject of webhook versus integration event tracking.
  • Cache what is stable. Metric identifiers, list identifiers and flow identifiers are account scoped and rarely change. Resolving them on every run is pure overhead, and holding them in a per-account map is part of the same discipline as a schema built for cross-account reporting.

What silently breaks downstream when requests drop

The damage from throttling is rarely the failed request, it is the row that never arrived and never got reported as missing. A sync that retries three times, gives up, logs a warning nobody reads and marks the run successful will produce a dataset that looks complete. Then a brand appears down 20 percent month over month in the rollup, and someone spends a day looking for a marketing explanation for a truncated sync.

Make partial completion loud. Track expected pages against fetched pages per account per run, fail the run when they disagree, and mark the destination dataset stale rather than publishing a partial refresh over a good one. If the numbers already disagree between systems, that reconciliation is its own exercise, covered in reconciling Klaviyo revenue with Shopify and in why multi-brand rollup reports never line up. Rule of thumb: a sync that can silently return less than everything is a reporting bug waiting for a quarter-end.

Trade-offs and what I would do

The ordering most teams use is backwards. They tune retries first because it is the smallest code change, then discover retries do not create capacity. Volume reduction comes first, then a limiter with deliberate headroom, then isolation, because each step makes the next one cheaper.

For six brands under one owner, with one integration you control end to end, I would not build OAuth yet. Dedicated keys per account and per job, a per-account token bucket at 60 percent of the steady limit, staggered schedules instead of a shared top-of-hour trigger, bulk writes and incremental reads will hold comfortably, and the account inventory usually finds one abandoned integration whose removal buys back more capacity than any code change. Revisit the decision when the account count passes roughly ten or when you start installing for clients you do not administer.

For an integration you distribute, build OAuth now. Every additional account you add on private keys is a new set of neighbours you cannot see, cannot log and cannot ask to move their schedule, and the support burden of explaining unpredictable throttling to brands compounds faster than the auth work you postponed. If you are still deciding how the accounts themselves should be arranged, that question sits upstream of this one and is worth settling first in one account or one per brand.

The judgement underneath all of it: design for a ceiling that moves. Do not hard code the documented number, read it from configuration, alert when your throttle rate crosses a threshold rather than when a run fails, and assume that on any given night some other integration in the account will take a share you did not plan for.

Sources

M
Miloš Mitrović
Email Marketing for Ecommerce

Have a question or a project?

Whether it is about this post or a system you want built, I'm happy to talk.

Get in touch

404

Post not found. It may have been moved or the link is incorrect.

← Back to the blog
Summarize with AI
ChatGPT, Perplexity, and Grok open with the prompt ready to run. Claude, Gemini, and Copilot open a chat with the prompt copied; press Ctrl+V (Cmd+V on Mac) to paste. The full text is included, so it works even without web access.