Designing an API layer over a dozen music data vendors
Streaming, social, advertising, ticketing, email and search all speak differently, fail differently and rate limit differently. The patterns that keep an integration layer from becoming a liability.
Danny Starr
Co-founder, Backline · 22 June 2026 · 3 min read
In short
- Normalise at the edge. One module per vendor turning their shape into yours, and nothing downstream that knows a vendor exists.
- Verify response shapes with a real request before writing types. Similar looking APIs nest their payloads differently and the wrong assumption fails at runtime.
- Every integration must fail open. One vendor being down should cost one section of a page, never the page.
- Concurrency limits are per vendor and often undocumented. Unbounded fan-out silently returns empty results rather than errors.
- Assume tokens expire and grants get revoked. Rotation, refresh and a visible connection status are part of the integration, not operational extras.
A music analytics platform is mostly an integration layer. The interesting parts are the derivations, and the volume of work is in talking to a dozen services that agree on nothing.
These are the patterns that have held up.
Normalise at the edge
One module per vendor, whose only job is to speak that vendor's dialect and return your shapes. Nothing downstream knows which vendor a number came from.
Two payoffs. Swapping a vendor is a rewrite of one module rather than a search across the codebase. And the vendor's peculiarities, its pagination, its nulls, its idea of a date, are quarantined in a file that can be tested against recorded responses.
The discipline that makes it work is that the shared module has no framework or database imports, so it can be unit tested directly. Once an integration module imports a database client, its logic stops being testable and starts being a job for a staging environment.
Verify the shape before writing the types
Read the documentation, then make the request and look at what comes back.
APIs that look similar nest their payloads differently. One provider we use returns a status as a plain string with the payload as a sibling key at the top level, which is not what a similar-looking API next to it does. Assuming the wrong nesting produces types that compile and code that fails at runtime, on a schedule, in production.
Recording a real response and building the types from it costs ten minutes and removes a class of bug entirely.
Five vendor behaviours worth designing for
Illustrative| What happens | What it costs you | Pattern | |
|---|---|---|---|
| Unexpected payload shape | Types compile, runtime fails | Silent gaps on a schedule | Verify with a real request before typing |
| Concurrency cap | Rate limit responses read as empty | Missing data, no error | Bounded fan-out plus retry with backoff |
| Rotating refresh tokens | New token returned on refresh | Next refresh fails permanently | Always persist the returned token |
| Required companion identifier | Endpoint needs an artist id too | New projects cannot resolve anything | Set it at import, borrow from a sibling |
| Undocumented cadence | Totals move weekly, not daily | Phantom spikes in charts | Detect from your own snapshots, bucket |
Fail open, everywhere
The rule: one vendor being unavailable costs one section, never a page and never a sync run.
Concretely, every read path returns an unavailable marker rather than throwing, every section renders its own empty state, and a cron continues to the next client after a failure rather than aborting the run.
The same applies to schema. A read path that selects a column added by a migration should retry without that column if it errors, so the code can ship before the migration is applied. It looks defensive to the point of paranoia until the first time a deployment lands ahead of a migration and nothing breaks.
Concurrency limits are real and undocumented
Some vendors cap concurrent requests per project, and exceeding it does not always produce a clean error. One we work with returns rate limit responses that, handled naively, appear as empty result sets rather than failures, so the symptom is missing data instead of an exception.
Two mitigations. A wrapper around every outbound call that retries on rate limiting with backoff, and a bounded concurrency helper used for any fan-out, so a project with 200 tracks does not open 200 connections.
The general lesson is that unbounded parallelism over third-party APIs eventually produces silent data loss, and silent is the expensive part.
Credentials expire and grants get revoked
Every long-lived integration eventually breaks through no fault of yours. A token expires, a customer removes an app, a partner rotates a key, a permission gets withdrawn during an unrelated audit.
So the integration includes refresh, rotation and status. Persist the refreshed token every time, since some providers rotate the refresh token itself on use and dropping the new one means the next refresh fails. And surface a connection status the customer can see, because the alternative is a chart quietly going flat and nobody noticing for a month.
What a broken connection should do
Illustrative- 1The call fails or returns unavailableNo exception escapes the integration module. It returns a marker.
- 2The section renders its own empty stateNaming the source and saying it needs reconnecting. The rest of the page is unaffected.
- 3The run records it per project and per vendorWith the error, so support is a query rather than an investigation.
- 4The connection status changes where the customer looksA flat chart with no explanation is the failure mode to design out.
- 5The next run retriesNo manual intervention for a transient failure, and no baseline advanced on an unsuccessful write.
Secrets stay on the server
Credentials belong in server-side storage and never reach a browser. A read endpoint returns a boolean saying whether a secret is set, never the value, and a write treats an empty field as leave unchanged so an interface that cannot display a secret can still edit around it.
Related and worth stating: a key that is safe in a browser and a key that is not are different things, and mixing them up is a common failure. An ingestion key designed for client-side use is fine in a public page. An export key with read access to everything is not, and they often look similar.
Log per vendor, per project
When a sync covers a dozen integrations across many projects, "the sync failed" is not a diagnosis.
Every run records what it did per vendor and per project: successes, failures, counts, durations. That turns a support question into a query, and it makes the difference between a customer telling you a number looks wrong and you telling them a grant was revoked four days ago.
Common questions
- How should a platform integrate many third-party data APIs?
- One module per vendor at the edge, translating that vendor's shape into your own, with no framework or database imports so the logic stays unit testable. Nothing downstream should know which vendor a number came from, which makes replacing one a single-module rewrite.
- What happens when a third-party music API rate limits you?
- Often not a clean error. Some return responses that, handled naively, appear as empty result sets, so the symptom is missing data rather than a failure. The mitigations are a retry wrapper with backoff on every outbound call and a bounded concurrency helper for any fan-out across a catalogue.
- Why do OAuth integrations break months after being connected?
- Tokens expire, customers remove apps, partners rotate keys and permissions get withdrawn. Some providers also rotate the refresh token itself on every use, so failing to persist the newly returned one permanently breaks the next refresh. A visible connection status matters because the alternative is a chart quietly going flat.
Sources
- 1Meta, Reviewed August 2026. Graph API insights reference
- 2TikTok for Developers, Reviewed August 2026. Display API: get started
- 3Google, Reviewed August 2026. Search Console API: Search Analytics query reference
Danny Starr
Co-founder, Backline
Danny Starr is a co-founder of Backline and builds the platform. He writes about the data engineering behind music analytics: ingestion, identity, honesty in charts, and the AI layer on top of it.
Backline does this for the projects you run
Streaming, audience, social, advertising, website, search, ticketing and press data in one dashboard per project, with an AI assistant that answers questions about your own connected data. Invite-only.
Keep reading
Data engineering
Observability for daily data syncs
A sync that fails loudly is a good day. The dangerous one succeeds while quietly returning nothing, and only monitoring built for that case will catch it.
Danny Starr · 3 min read
Data engineering
ISRC, UPC, and why your track has four identities
Every analytics platform in music runs on identifiers, and the identifiers disagree. What ISRCs and UPCs are for, where they break, and what to do when a track has none.
Danny Starr · 3 min read
Privacy and security
Why data isolation is not negotiable for multi-tenant platforms
Every artist's data must be truly inaccessible to every other artist, at the database layer. Why isolation breaks, and why application-only checks fail.
Danny Starr · 2 min read

