What an in-house Meta data pipeline actually costs to keep running

The Pullsy engineering team8 min read

In short

The build is the cheap part. What costs is the standing commitment: Meta access tokens expire in 60 days and become unrefreshable, API versions retire after two years and silently fall back to an older one rather than failing, and the seven distinct ways a sync can fail each need a different response.

Most build-versus-buy posts are written by vendors and are worth roughly what you paid for them. This one is too, so here is the bias declared up front: we sell a connector. What follows is still the honest version, because the argument that actually decides this is not “it is hard”, it is “here is the specific list of things that break, and how you find out”. If that list looks manageable to you, build it. Several of the best reasons to build are at the bottom.

What we can quantify, we quantify. What we cannot — and engineering cost in currency is firmly in that category, because it depends on salaries, seniority and opportunity cost we have no source for — we leave out rather than invent.

Why is a Meta integration expensive to maintain rather than to build?

Getting first data out of the Meta Marketing API is a few days' work. What follows is not a project with an end: three independent clocks run whether or not anyone is looking at them — the token clock, the version clock and the schema clock — and all three fail quietly rather than loudly.

Quietly is the operative word. A pipeline that throws is a pipeline you fix on Tuesday. A pipeline that returns an empty array with a 200 status, or silently falls back to an older API version, or drops a metric it used to collect, is one you fix in March when a customer asks why their chart has a flat spot in November.

The token clock: 60 days, and then it is gone

Meta's long-lived access tokens are not permanent, and the refresh rules have two edges that are easy to build past.

StageRule
Authorization code*“valid for 1 hour and can only be used once”*
Long-lived token*“valid for 60 days”*
Refresh eligibility*“The existing long-lived access token is at least 24 hours old”*
Missed window*“Tokens that have not been refreshed in 60 days will expire and can no longer be refreshed”*
The Instagram long-lived token lifecycle, from Meta's business login documentation.
Those two rules define a refresh window, not a refresh deadline: a token younger than 24 hours cannot be refreshed, and one older than 60 days cannot be recovered. Miss the window and there is no recovery path in code — the customer has to go back through the authorization flow, which means an email, a support conversation, and a gap in their data.

A correct implementation therefore needs a scheduled job that refreshes ahead of expiry with real margin — ours refreshes anything expiring within ten days, and only if the token is at least 24 hours old — plus a durable record of when each token was issued, plus a state a connection can enter that means “this needs the customer, not a retry”, plus the interface surface to tell them so.

And that is one provider's rules. X, for example, issues refresh tokens that are replaced on every use, so a refresh that succeeds at the API and fails to persist locally has destroyed the credential. Each provider's token model is its own small state machine, and they do not resemble each other.

The version clock: two years, and the failure is silent

Meta's versioning policy is clear about the timeline. *“Each version will remain for at least 2 years from release”*, and *“A version will no longer be usable two years after the date that the subsequent version is released.”*

Two years is generous. The part worth reading twice is what happens when you miss it:

*“For APIs, once a version is no longer usable, any calls made to it will be defaulted to the next oldest, usable version.”* Your requests do not start failing. They start being answered by a different API than the one you wrote against — with different field names, different defaults and different breakdown behaviour — while returning 200 the whole time.

This is the single strongest argument for treating an API integration as owned software rather than as a finished feature. There is no alert for it. Nothing in your error budget moves. The only defences are a calendar reminder on a two-year cycle and somebody who reads the changelog, and both of those are a person's habit rather than a system.

Failure is not one thing

The instinct when a sync fails is to retry it. That instinct is wrong more often than it is right, and the cost of getting it wrong compounds: Meta holds apps to an error-rate threshold for continued Advanced Access, so a client that retries un-retryable requests is spending a budget it needs for real outages.

Our connector layer distinguishes seven failure classes, and the number is not architecture astronomy — each one has a genuinely different correct response.

ClassCorrect response
TransientRetry with backoff. This is the only one retrying helps.
Rate limitedStop and wait for the window. Retrying makes it worse.
Reauthorization requiredStop retrying entirely and tell the customer.
Permission deniedA reauthorization that needs a *different scope* — a different message.
Provider billingThe customer's ad account has a payment problem. Not our fault, not retryable, and they need to know.
PermanentThe request will never succeed. Record it and move on.
Unsupported / emptyNot an error at all. Record *which* kind of empty, or lose the distinction forever.
Seven failure classes, and why merging any two of them is a bug.

That last row is the one that most in-house pipelines never build, because it does not look like a failure. An ad-account breakdown that returns HTTP 200 and no rows can mean the account did not spend, or that a per-account opt-in was never granted, or that the breakdown only covers part of delivery by design. Storing all three as a zero is a decision, and it is irreversible.

What else is on the list

Beyond the three clocks, these are the surfaces that exist in any correct implementation, whoever writes it. None is hard. Together they are the reason the estimate is always wrong.

SurfaceWhy it is not optional
App review and business verificationAdvanced Access to ads and Instagram data is granted, not assumed. It gates your ability to serve customers other than yourself.
Backfill checkpointingA 37-month backfill across nineteen breakdowns will be interrupted. Without a checkpoint per pass it restarts from zero.
Rate-limit awarenessMeta returns usage headers. Reading them and pausing before the ceiling is the difference between a slow sync and a blocked app.
Per-request window clampingRetention differs per breakdown and the edges fail differently — one of them returns nothing for the whole range, not just the part out of range.
Schema drift absorptionMeta adds and removes fields. A normalized schema means your customers' queries do not change when Meta's do.
Tenancy isolationIf you serve more than one customer, every query needs scoping, and the guarantee has to be enforced rather than remembered.
Re-auth surfacing in the productA broken connection the customer cannot see is a chart that quietly goes flat.
The standing surfaces of a Meta data integration.

For scale: the connector layer alone in our codebase is 24 modules and roughly 10,300 lines, for three providers, not counting the sync engine that runs them. That is not a boast — most of it is unglamorous and none of it is clever. It is the honest size of the thing.

When should you build it yourself?

Genuinely often. Buying is wrong in at least these cases:

  • You need one account, not your customers'. Almost all of the above exists to handle many accounts authorized by many people. Your own ad account, one token, one schema you control — build it in an afternoon and never think about it again.
  • You need a narrow slice. Daily spend by campaign is a single request with no breakdowns, no asset splits and no frequency distribution. The complexity here is in coverage, and coverage you do not need is free to skip.
  • The integration is your product. If reading the Marketing API well *is* the differentiator you sell, outsourcing it outsources the thing customers pay you for.
  • You have strict data-residency or on-premise requirements that a third party cannot satisfy. This is a real constraint and it settles the question on its own.
  • You already run this class of system. If you have a team that maintains provider integrations, the marginal cost of one more is much lower than the first one was.

The case for buying is narrower than vendors pretend. It is strongest when you need broad coverage across many customer-authorized accounts, the data is an input to your product rather than the product, and the engineer who would own it has something more valuable to do.

How to decide

Do not estimate the build. Estimate the second year. Ask concretely: who refreshes the tokens, who reads the changelog, who notices a breakdown that went silent, and what happens when that person is on holiday. If those questions have names attached, building is a reasonable call. If they do not, the integration will work for about eleven months.

If you want to see the shape of the finished thing before deciding, the API overview lays out what a normalized version of this data looks like, and the Meta Ads breakdown post is the most concrete list we have of what goes wrong.

Questions people ask

How long does a Meta access token last?

A long-lived Instagram token is valid for 60 days. It can be refreshed for another 60, but only once it is at least 24 hours old, and Meta states that tokens not refreshed within 60 days expire and can no longer be refreshed at all.

How often does Meta deprecate API versions?

Meta states each version remains available for at least two years from release, and becomes unusable two years after the following version ships. In practice that means a scheduled upgrade roughly every two years, not a one-off migration.

What happens if you call a retired Meta API version?

It does not fail. Meta's documentation states that calls to an unusable version are defaulted to the next oldest usable version — so your requests keep returning 200 while being answered by an API you did not write against.

Is it cheaper to build or buy a Meta Ads integration?

It depends on whether you need one account or many customer-authorized ones. For a single owned ad account and a narrow metric set, building is clearly cheaper. For broad coverage across many accounts, the ongoing maintenance usually dominates the build cost.

What breaks most often in a Meta API integration?

Tokens, silently-empty responses, and metric availability. Tokens expire on a fixed clock; breakdowns can return HTTP 200 with no rows for several different reasons; and metric lists change by addition, so a hardcoded list quietly stops being complete.

Do you need Meta app review to read ad data?

Yes, for data belonging to accounts other than your own. Advanced Access to ads and Instagram permissions requires app review and business verification, and Meta also holds apps to an error-rate threshold to keep that access.

Sources

  1. Meta — Graph API versioning — The two-year lifecycle, and the fallback to the next oldest usable version
  2. Meta — Instagram business login — Authorization code, long-lived token and refresh window lifetimes
  3. Meta — Marketing API breakdowns reference — Breakdown permutations and the per-account opt-in instruction

Start pulling in about ten minutes

Connect one real account, run a sync, and query it. That is the whole evaluation — no call, no card.