logo

API Chaining in Async Workflows

API chaining means calling multiple endpoints in sequence, where each call depends on the result of the previous one. The output of one request becomes the input - or trigger - for the next, forming a dependent HTTP pipeline.

How It Works

  1. Your application calls API A
  2. The response from API A contains data needed for API B
  3. API B is called with that data
  4. This continues until the full workflow completes

A simple synchronous example:

// Synchronous chaining - blocks your server
const user = await fetch('https://api.example.com/users/123').then(r => r.json());
const orders = await fetch(`https://api.example.com/orders?userId=${user.id}`).then(r => r.json());
const invoice = await fetch('https://billing.example.com/generate', {
method: 'POST',
body: JSON.stringify({ orders, customer: user }),
}).then(r => r.json());

The problem: if each call takes 2-3 seconds, your endpoint stays blocked for 6-9 seconds.

Why Synchronous API Chaining Breaks Down

  • Timeouts: Serverless functions and API gateways enforce execution limits (e.g. 10s on Vercel, 30s on AWS API Gateway)
  • Cascading failures: If the third API in a chain goes down, the entire request fails
  • Wasted compute: Your server sits idle while awaiting each response
  • No retry granularity: A failure at step 3 forces you to re-run the full chain from step 1

Async API Chaining with a Task Queue

Offloading each step to a task queue solves these problems. Your endpoint returns right away, and each HTTP call runs as an independent task with its own retries and timeout handling.

// Step 1: Queue the first call
await aq.tasks.create({
callbackUrl: 'https://api.example.com/users/123',
webhookUrl: 'https://your-app.com/api/chain/user-fetched',
});
// Step 2: Handle the result and queue the next call
app.post('/api/chain/user-fetched', async (req, res) => {
const user = req.body.result;
await aq.tasks.create({
callbackUrl: `https://api.example.com/orders?userId=${user.id}`,
webhookUrl: 'https://your-app.com/api/chain/orders-fetched',
payload: { user },
});
res.status(200).json({ received: true });
});
// Step 3: Generate the invoice
app.post('/api/chain/orders-fetched', async (req, res) => {
const { user } = req.body.payload;
const orders = req.body.result;
await aq.tasks.create({
callbackUrl: 'https://billing.example.com/generate',
method: 'POST',
payload: { orders, customer: user },
webhookUrl: 'https://your-app.com/api/chain/invoice-ready',
});
res.status(200).json({ received: true });
});

Each step runs on its own with built-in retries. If the billing API fails, only that step re-executes - not the whole chain.

Common Use Cases

  • Payment flows: Validate card, charge amount, send receipt, update ledger
  • Data enrichment: Fetch user profile, query CRM, pull analytics, merge results
  • Third-party integrations: Sync to CRM, update shipping provider, notify warehouse
  • Report generation: Query database, aggregate metrics, render PDF, email to stakeholder

Best Practices

  • Pass a chain ID through every step so you can trace the full workflow
  • Store intermediate results so failed steps can resume without repeating earlier calls
  • Set per-step retries and timeouts since each endpoint has different reliability characteristics
  • Add a dead letter queue for workflows that fail after all retries are exhausted

API Chaining vs. Task Chaining

API chaining refers to sequencing HTTP calls. Task chaining is the broader pattern of linking any asynchronous operations in sequence. The HTTP variant is a subset where every step happens to be an endpoint request.