Skip to content

Handling Bulk Data Extraction and ETL Workflows Through Unified APIs

Operational runbook for bulk data extraction across SaaS APIs: aggregate users, handle rate limits with RapidBridge, monitor syncs, and scale to thousands.

Riya Sethi Riya Sethi · · 16 min read
Handling Bulk Data Extraction and ETL Workflows Through Unified APIs

B2B SaaS applications are no longer standalone silos. To provide actual value, your product must ingest millions of rows of data from your customers' existing tools - pulling records from CRMs, HRIS platforms, applicant tracking systems, and accounting software. We are talking about handling bulk data extraction and ETL workflows through unified APIs to power multi-tenant integrations at scale.

If you are an engineering leader tasked with building these integrations, you face a brutal architectural decision. Do you build custom data pipelines for 50 different APIs, adopt a traditional ETL tool designed for internal analytics, or route everything through a unified API?

This guide breaks down exactly how to scale multi-tenant data ingestion, why internal ETL architectures fail at customer-facing syncs, and how to handle the inevitable rate limits and pagination nightmares without caching sensitive customer data on third-party servers.

The Challenge of Multi-Tenant Bulk Data Extraction in B2B SaaS

Bulk data extraction in a B2B SaaS context is fundamentally different from internal data warehousing. When you pull data for internal business intelligence, you control the source, the credentials, and the destination. When you extract data on behalf of your customers, you control none of those things.

Precedence Research estimates the 2026 SaaS market at $466 billion, meaning the ecosystem of third-party tools your application must integrate with is expanding exponentially. Furthermore, global buyers rank integrations as the number three priority when evaluating new software, trailing only behind security and ease of use. Integrations are a direct revenue driver, but extracting that data presents massive engineering hurdles.

The Multi-Tenant Multiplier Effect If you have 1,000 customers and offer 50 integrations, you do not have 50 data pipelines. You have 50,000 unique integration states. Each of those connections has its own OAuth token lifecycle, its own API rate limit bucket, and its own custom field schema.

When extracting bulk data across this matrix, engineering teams run into three immediate walls:

  • Disparate Pagination Strategies: HubSpot uses cursor-based pagination. Salesforce relies on SOQL offset queries. Other legacy APIs use page numbers or link headers. Writing custom logic to traverse millions of records across six different pagination paradigms creates brittle code.
  • Authentication State Drift: OAuth tokens expire. Refresh tokens get revoked by IT admins. A bulk extraction job that takes four hours might outlive the lifespan of the access token it started with, requiring mid-flight credential refreshes.
  • Schema Normalization: Extracting the data is only half the battle. Mapping 50 different representations of a "Contact" into a single format your application can digest requires thousands of lines of transformation code.

Building this in-house means dedicating a permanent team of engineers just to maintain API parity. To solve this, many teams turn to third-party tools. But picking the wrong tool category can permanently cripple your infrastructure.

For a deeper dive into the architectural problem of scheduled syncs, read our guide on ETL Workflows Using Unified APIs: Solving the Bulk Extraction Problem.

Aggregating Users Across All Your Customers' SaaS Apps

A common enterprise ask sounds like this: "pull a list of users from every SaaS app my customers are using - HRIS, CRM, ticketing, identity - without building each integration one after another." That single sentence encodes three problems a unified API is designed to collapse:

  1. Every SaaS app names "user" differently. HRIS platforms call them employees. CRMs call them owners or reps. Ticketing systems call them agents. Identity providers call them directory users.
  2. Each customer connects a different mix of tools, so you cannot hardcode a fixed pipeline.
  3. Users churn constantly. A one-time export is useless; you need incremental syncs keyed on updated_at.

With Truto, one resource per category returns a normalized user schema: hris/employees, crm/users, ticketing/users, ats/users, identity/directory-users. Salesforce's FirstName, HubSpot's properties.firstname, and Workday's nested name.givenName collapse into the same shape server-side via JSONata mappings.

One sync configuration, fanned out across every integrated account, gives you a consolidated user directory:

{
  "resources": [
    { "resource": "hris/employees", "method": "list", "query": { "updated_at": { "gt": "{{previous_run_date}}" } } },
    { "resource": "crm/users", "method": "list", "query": { "updated_at": { "gt": "{{previous_run_date}}" } } },
    { "resource": "ticketing/users", "method": "list", "query": { "updated_at": { "gt": "{{previous_run_date}}" } } },
    { "resource": "ats/users", "method": "list", "query": { "updated_at": { "gt": "{{previous_run_date}}" } } }
  ]
}

You do not write one integration per connector. You write one sync config that describes what you want, and the runtime routes each resource call to the correct upstream API for whatever tool the customer connected. Adding a new provider in that category is a config change, not a code deployment.

Traditional ETL vs. Unified APIs for Data Ingestion

When evaluating how to move bulk data from customer systems into your own, the market presents three distinct architectural approaches: internal BI tools, embedded ETL platforms, and declarative unified APIs.

Why Internal ETL Tools Fail at Embedded Integrations

Traditional ETL and ELT tools like Fivetran or Airbyte are exceptionally good at what they were designed for: moving internal company data into centralized cloud warehouses (like Snowflake or BigQuery) for business intelligence.

However, they are fundamentally misaligned for B2B SaaS product integrations. Internal ETL tools assume a single-tenant architecture. They expect a data engineer to manually configure a connector, map the fields, and schedule the sync. In a B2B SaaS product, this process must be entirely automated and self-serve for the end user. You cannot ask your customers to write SQL transformations or configure warehouse destinations just to connect their CRM to your app.

The Technical Debt of Python-Based Embedded ETL

Recognizing the gap left by internal BI tools, a category of embedded ETL platforms emerged (such as Hotglue). These platforms are built for multi-tenant batch syncing, but they rely on a code-first architecture.

To map data from a third-party API to your application, your engineers must write and maintain custom Python transformation scripts for every single connector. If you want to support 40 CRMs, you maintain 40 Python scripts. When an API provider deprecates an endpoint or changes a payload structure, your Python script breaks, and your engineers have to deploy a fix.

This approach simply shifts the maintenance burden from your infrastructure to the ETL vendor's infrastructure, but the integration-specific code still exists, and you still have to maintain it.

For a detailed comparison of code-first versus declarative approaches, see Truto vs Hotglue: Declarative JSON vs Code-First Python for B2B Integrations.

The Declarative Unified API Architecture

A modern unified API takes a radically different approach. Instead of writing integration-specific code, the system uses declarative configuration to handle data extraction and transformation.

In Truto's architecture, there is zero integration-specific code. The runtime engine is a generic pipeline that reads JSON configuration blobs describing how to talk to an API, and JSONata expressions describing how to translate the data.

When you request a bulk list of contacts, the unified API engine:

  1. Reads the integration config to determine the correct HTTP method, path, and pagination strategy.
  2. Injects the customer's OAuth token from the secure context.
  3. Executes the HTTP fetch via a generic proxy layer.
  4. Evaluates a JSONata expression against the response to map the proprietary fields (like Salesforce's FirstName or HubSpot's properties.firstname) into your unified schema.

Adding a new integration or updating a broken one is a data operation, not a code deployment. This architecture scales infinitely better than maintaining dozens of Python scripts.

Bulk Extraction Architecture and Sequencing

Running bulk extraction across hundreds of accounts is a sequencing problem before it is a throughput problem. If you fan out naively, you will hit rate limits on every provider at the same instant and leave your queue thrashing.

A production-grade sequence looks like this:

  1. Discover connected accounts per integration. List integrated accounts filtered by category or integration name.
  2. Bootstrap on first sync. Kick off a full historical extraction with ignore_previous_run: true on the first Sync Job Run for each new account.
  3. Incremental from then on. Every subsequent run passes previous_run_date into an updated_at.gt filter so only changed records come through.
  4. Sequence dependent resources. Parent resources first, then children. Comments depend on tickets. Pull requests depend on repos. depends_on in the sync config enforces the ordering.
  5. Fan-out per account. Each account gets its own sync run keyed by mutex_key: "{{args.integrated_account_id}}" so two runs cannot collide for the same tenant.
graph TD
  Q["Scheduler tick"] --> D["List integrated accounts"]
  D --> F["Fan out one sync run per account"]
  F --> B{"First run<br>for account?"}
  B -->|"Yes"| BS["Bootstrap sync<br>(ignore_previous_run)"]
  B -->|"No"| IN["Incremental sync<br>(updated_at > previous_run_date)"]
  BS --> W["Write to your store"]
  IN --> W
  W --> WM["Advance watermark<br>on success only"]

The watermark only advances when the run completes successfully. A failed run replays from the last good sync, so partial failures never silently lose records.

Handling API Rate Limits and Pagination at Scale

Bulk data extraction is inherently hostile to third-party API limits. When you attempt to pull 500,000 records from a customer's system, you will hit rate limits. How your integration middleware handles those limits determines whether your pipeline succeeds or experiences catastrophic failure.

The Danger of Silent Middleware Retries

Many legacy integration platforms attempt to be "helpful" by automatically catching HTTP 429 (Too Many Requests) errors and applying exponential backoff inside the middleware layer.

Middleware that silently catches and retries HTTP 429 rate limit errors is an architectural landmine for high-throughput bulk extraction pipelines. If your worker thread makes an API call and the middleware pauses for 60 seconds to respect a rate limit, your worker thread is left hanging. In a multi-tenant environment, this causes massive queue backups, timeout cascades, and state mismatches between your workers and the integration layer.

The IETF Standardized Approach

Truto takes a stance of radical transparency regarding rate limits. Truto does not retry, throttle, or apply backoff on rate limit errors. When an upstream API returns an HTTP 429, Truto passes that exact error back to the caller immediately.

To make this actionable, Truto normalizes the upstream rate limit information into standardized headers per the IETF specification:

  • ratelimit-limit: The maximum number of requests permitted in the current window.
  • ratelimit-remaining: The number of requests remaining in the current window.
  • ratelimit-reset: The time at which the current rate limit window resets.

By passing the 429 error and the normalized headers back to your system, you retain complete control over your worker queues. Your extraction job can read the ratelimit-reset header, safely sleep the specific worker thread, and free up computing resources for other tenants, rather than dying in a blind timeout cascade.

Rate-Limit Handling: Backoff, Batching, and RapidBridge Patterns

Three techniques compound to keep throughput high without breaking upstream contracts.

1. Read the headers, do not guess. Inspect ratelimit-remaining before firing the next call and defer that tenant's queue when it approaches zero. This avoids the "spray and pray" pattern where you burn a full quota and then stare at 429s for 60 seconds.

2. Batch by tenant, not by resource. The wrong pattern: fan 500 requests at Salesforce simultaneously across 500 accounts. The right pattern: process each tenant serially internally (the mutex_key guard), and parallelize across tenants up to a per-integration concurrency ceiling. Each tenant has its own upstream quota, so cross-tenant parallelism is safe.

3. Exponential backoff with jitter. When you do get a 429, sleep for ratelimit-reset seconds plus a random jitter of 0-30% to prevent every worker from hammering the API the instant the window resets. Then re-enqueue the exact request.

RapidBridge codifies these patterns internally so you do not have to implement the 429 loop yourself. A scheduled RapidBridge sync configured for incremental extraction looks like this:

{
  "integration_name": "salesforce",
  "resources": [
    {
      "resource": "crm/users",
      "method": "list",
      "query": {
        "updated_at": { "gt": "{{previous_run_date}}" }
      }
    }
  ]
}

When RapidBridge encounters a 429, it reads ratelimit-reset, defers only the affected account's run, and resumes automatically at the reset time. Other accounts keep flowing. The watermark for the deferred account stays frozen until its resumed run completes successfully.

For providers with especially aggressive limits (Zendesk, HubSpot free tier), lower the fan-out concurrency for that integration specifically. Keep aggressive parallelism for providers with generous quotas.

Normalizing Pagination

Pagination is equally chaotic across APIs. To extract bulk data, your system must page through the entire dataset. A robust unified API handles this at the proxy layer, normalizing all upstream pagination methods (cursor, page, offset, link-header, range, and dynamic JSONata-driven) into a single, consistent cursor-based interface.

Your application simply passes the next_cursor returned by the unified API into the subsequent request. The unified API engine translates that cursor back into whatever arcane format the specific third-party API requires.

Learn more about this mechanism in our guide on How to Normalize API Pagination and Error Handling Across 50+ APIs Without Building It Yourself.

Zero Data Retention: Avoiding the Caching Trap

The architectural choices your unified API provider makes regarding data storage dictate your compliance posture. When evaluating unified APIs for ETL workflows, the most critical question is: where does the extra copy of customer data live?

The Security Risks of Cached Unified APIs

Many first-generation unified APIs rely on a sync-and-cache architecture. To provide a unified interface, they continuously poll the third-party APIs in the background, extract all the data, normalize it, and store it in their own managed databases. When you make an API call, you are actually querying their database, not the customer's actual software.

For bulk data extraction, this is a massive liability. It means millions of rows of your customers' highly sensitive data - employee salaries, unreleased financial records, private CRM contacts - are sitting at rest on a third-party vendor's servers. This introduces severe data residency and compliance risks for enterprise customers. Passing a SOC 2 or ISO 27001 audit becomes exponentially harder when you have to explain why a middleman is storing a permanent copy of your customers' databases.

The Pass-Through Proxy Architecture

Modern enterprise architecture demands zero data retention. Truto utilizes a strict pass-through proxy architecture.

When you trigger an extraction job, the request passes through Truto's proxy layer, the data is fetched directly from the upstream provider, transformed in memory via the JSONata engine, and returned directly to your application.

Customer data is never stored at rest on Truto's servers. The payload exists in memory just long enough to be transformed and delivered. This eliminates the compliance overhead of managing third-party data lakes and ensures that the data you receive is exactly what exists in the source system at that exact millisecond.

Read more about the compliance advantages of this approach in Zero Data Retention for AI Agents: Why Pass-Through Architecture Wins.

Architecting ETL Workflows Through Unified APIs

To build a reliable, high-volume ingestion pipeline using a declarative unified API, you must separate the concerns of data extraction, state management, and schema mapping. Here is the blueprint for architecting a scalable ETL workflow.

1. The Execution Engine

Your application should act as the orchestrator. You maintain a job queue (using Redis, Kafka, or a managed queueing service) that dictates which tenant needs to be synced and when.

When a job triggers, your worker makes a request to the unified API's list endpoint (e.g., GET /unified/crm/contacts).

graph TD
  A["Your Worker Thread"] -->|"GET /unified/crm/contacts"| B["Truto Unified API"]
  B -->|"Resolve Config & Token"| C["Proxy Layer"]
  C -->|"Native Request"| D["Third-Party API (Salesforce)"]
  D -->|"Raw Native JSON"| C
  C -->|"Evaluate JSONata Mapping"| B
  B -->|"Unified Schema JSON"| A

2. Cursor Management and Checkpointing

Because bulk extraction can take time, your workers must be stateless and capable of resuming interrupted jobs.

When you receive a page of results from the unified API, you should immediately write the next_cursor to your database alongside the job state. If the worker crashes or is preempted, the next worker can pick up the job, read the saved cursor, and resume the extraction exactly where it left off. Because the unified API handles translating that cursor into the provider-specific format, your checkpointing logic remains identical whether you are syncing HubSpot, Pipedrive, or Zendesk.

3. Handling Backoff via IETF Headers

As discussed, you must inspect the response headers for rate limit data. A standard implementation looks like this:

  1. Check the HTTP status code.
  2. If 200 OK, process the data and enqueue the next page using the next_cursor.
  3. If 429 Too Many Requests, read the ratelimit-reset header.
  4. Pause the specific tenant's queue until the reset timestamp is reached.
  5. Re-enqueue the exact same request.
Tip

Idempotency is critical. Ensure your database upsert logic relies on the unified id field provided by the normalized schema. If a rate limit error occurs mid-page and you re-fetch the data, your database should cleanly overwrite the existing records without creating duplicates.

4. Scheduled Syncs via RapidBridge

If you prefer not to manage the worker queues and cursor checkpointing yourself, Truto provides a built-in ETL pipeline called RapidBridge.

RapidBridge allows you to configure scheduled syncs that automatically extract data from the unified API and write it directly into your own data store (like PostgreSQL or a vector database). It handles the pagination, the checkpointing, and the scheduling out of the box, while still adhering to the zero data retention policy on Truto's end. The data flows from the third-party API, through the in-memory transformation layer, and directly into your infrastructure.

Monitoring, Retries, and Observability

A sync pipeline you cannot observe is a sync pipeline you cannot trust. Instrument these signals from day one.

Per sync run:

  • Status (running, completed, failed, cancelled)
  • Records synced per resource
  • Pages fetched per resource
  • Wall-clock duration
  • Error class if failed (auth, rate limit, upstream 5xx, transform failure)

Per integration:

  • P50/P95/P99 sync duration
  • 429 rate per hour
  • Auth failure rate (usually a sign of token revocation or IT admin lockouts)
  • Records extracted per minute (the practical throughput ceiling per provider)

Cross-tenant:

  • Number of accounts with a failed last-run (the "unhealthy" cohort)
  • Watermark lag - how far behind now the last successful watermark sits
  • Schema drift signals - a transform expression that returned null where it used to return records, or a sudden drop in field coverage on the normalized payload

Truto emits complete, start, and error events per sync run. Subscribe your alerting system to those webhooks and correlate them with your worker logs via the run id. A sample alert set:

  • Page: watermark lag > 24h for any account on a Tier 1 integration
  • Warn: 429 rate > 10% of requests over a rolling 15-minute window for any integration
  • Warn: more than 5% of accounts on a given integration have a failed last-run
  • Info: schema drift - transform expression returned zero fields on a payload that historically had records

A minimal dashboard covers four tiles per integration: run success rate (24h), P95 duration, 429 rate, and unhealthy account count. That is enough to catch most production issues before your customers do.

Scaling Tips: Parallelism, Connector Prioritization, and Incremental Scheduling

Parallelism ceilings. Set two dials per integration: max concurrent accounts (fan-out) and max concurrent requests per account (fan-in). Start conservative (10 accounts, 1 request per account) and raise until you see sustained 429s, then back off 20%.

Connector prioritization. Not every integration deserves the same cadence. Categorize connectors:

  • Tier 1 (hourly incremental): revenue-critical - CRM, HRIS, billing.
  • Tier 2 (every 6 hours): operational - ticketing, ATS, project management.
  • Tier 3 (daily): low-change - accounting exports, static reference data.

This is the single highest-leverage optimization for reducing unified API cost and upstream quota consumption.

Incremental scheduling with jitter. If you schedule every Tier 1 sync at the top of the hour, every provider gets a thundering herd at :00. Spread starts across the interval with per-account offsets (e.g., hash(account_id) % 3600 seconds). Providers see steady load; your workers see steady queue depth.

Backfill separately from steady-state. A new account's bootstrap can pull years of history and take hours. Never run bootstraps on the same scheduler as incremental syncs. Give bootstraps their own worker pool with lower priority so a batch of new customer onboardings does not starve the incremental cadence.

Partition warehouse writes. If you land data in S3, GCS, or a warehouse, partition by ingest_dt and ingest_hr. Downstream crawlers (Athena, Snowflake external tables, Trino, Glue) discover partitions cheaply and query pruning works out of the box.

Operational Checklist for Production Rollouts

Before you flip the switch on a new integration or scale an existing one from 10 to 1,000 accounts, walk this list.

Configuration

  • Sync config uses previous_run_date for every incremental resource
  • mutex_key and state_key set to {{args.integrated_account_id}} for per-tenant isolation
  • depends_on correctly chains parent-child resources
  • args_schema validates required inputs before the run starts

Reliability

  • Retry policy handles 429 by reading ratelimit-reset
  • Retry policy handles 5xx with exponential backoff plus jitter
  • Auth failure triggers a customer-facing "reconnect" notification, not a silent retry loop
  • Idempotent upsert on your database keyed by the unified id field

Observability

  • Webhook subscriber for complete, start, error events wired to alerting
  • Per-integration dashboard with success rate, P95 duration, 429 rate, unhealthy account count
  • Watermark lag alert per Tier 1 integration
  • Structured logs correlated by sync run id

Scale

  • Fan-out concurrency ceilings set per integration
  • Scheduler jitter applied to avoid thundering herd
  • Bootstrap syncs isolated from incremental workers
  • Bulk endpoints preferred over per-record fetches where the upstream supports them

Compliance

  • Data does not persist at rest on the unified API layer (pass-through verified)
  • Customer OAuth tokens rotate before expiry without manual intervention
  • Sync run logs redact PII before shipping to your log store
  • SOC 2 / ISO 27001 review complete for the unified API vendor

If any box is unchecked, do not scale. Fix it first. A pipeline that works at 10 accounts and dies at 1,000 is worse than one that never shipped.

The Strategic Advantage of Declarative Extraction

Handling bulk data extraction and ETL workflows through unified APIs is the only sustainable way to scale B2B SaaS integrations.

By adopting a declarative, pass-through architecture, you eliminate the technical debt of maintaining integration-specific Python scripts. You protect your infrastructure from timeout cascades by taking control of rate limit backoff logic. Most importantly, you keep your customers' sensitive data out of third-party caching databases, ensuring strict compliance with enterprise security standards.

Stop building custom data pipelines for every API your customers use.

FAQ

How do unified APIs handle bulk data extraction?
They normalize disparate pagination strategies and data schemas across multiple providers into a single programmatic interface, allowing scheduled syncs without custom code.
Why not use traditional ETL tools for SaaS integrations?
Tools like Fivetran are designed for internal BI and cloud data warehousing, not multi-tenant embedded syncs that require per-customer OAuth management and bidirectional proxying.
How should data pipelines handle API rate limits?
Middleware should never silently retry HTTP 429 errors. Instead, it should pass the 429 to the caller alongside standardized IETF rate limit headers so the consumer can manage backoff logic.

More from our Blog