Quick Reference
Chain calls yourself when: Each step's outcome needs custom branching logic specific to your application, or you're mixing in calls to systems outside the JSON tooling itself
Use a single pipelined call when: The sequence is fixed (repair, then validate, then maybe reshape) and you just want the end result, billed only for what actually ran
You're processing JSON coming out of an LLM, a flaky third-party API, or a legacy system that's never quite RFC-compliant. The obvious approach: call a repair endpoint, take the result, call a validation endpoint with it, check the report. Two requests, two round trips, done.
Then you actually build it, and you notice you're now also responsible for: what happens if the repair call succeeds but the validation call times out? Did you get billed for the repair even though the overall operation never really finished? What if repair succeeds, validation says the data is invalid, and now you've paid for a validation check on data that's going to be tossed anyway -- is that... fine? Was that the intended cost model, or did you just not think about it?
None of these are hard problems individually. But they're all *client-side* problems you've now signed up to solve, for a sequence that's the same every single time. This article walks through why that manual chaining gets messier than it looks, and what a purpose-built pipeline call actually needs to guarantee to make the problem disappear.
1. The Problem: Two Calls, One Intent, and a Growing List of Edge Cases
Here's the naive version, and it's genuinely fine for a prototype:
// Step 1: repair
const cleanRes = await fetch('/api/json/v1/clean', {
method: 'POST',
headers: { 'X-API-Key': apiKey },
body: rawInput,
});
const cleanData = await cleanRes.json();
if (!cleanData.success) {
throw new Error('Repair failed: ' + cleanData.error);
}
// Step 2: validate
const schemaRes = await fetch('/api/json/v1/schema', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ schema, data: cleanData.data }),
});
const schemaData = await schemaRes.json();This works. But look at what it quietly assumes:
- Both requests will complete -- no partial-failure handling
- You're billed for step 1 regardless of what step 2 says -- is that what you actually want, or did you just not think about it yet?
- Nothing halts the sequence early if step 1's output is garbage that step 2 was never going to validate cleanly anyway
- Rate limiting is now two independent budgets you have to track, not one
Scale this to three steps (repair, validate, reshape) and the bookkeeping compounds. This isn't a criticism of the naive approach -- it's the correct starting point. It's just worth being honest about what it doesn't handle yet.
2. The Cause: Sequential HTTP Calls Don't Give You Atomicity for Free
2.1 Billing and Execution Are Two Separate Questions You Have to Reconcile Yourself
When you make two independent billed API calls, "did the overall operation succeed" and "was I charged appropriately" are two separate facts you have to reconcile in your own code. If step 1 succeeds and step 2 fails, you were charged for step 1 -- correctly, since it did run and complete. But now your application logic has to know that, and decide what "partial success" means for whatever comes after this in your pipeline.
2.2 There's No Server-Side Concept of "This Sequence, As A Unit"
Two separate HTTP calls are, as far as either service is concerned, two unrelated requests. Nothing on the server side knows they're meant to be steps 1 and 2 of the same logical operation -- which means nothing on the server side can make a decision like "don't bother running step 3 if step 2's validation says the data is invalid." That decision has to live in your client code, and it has to be right every time, for every caller.
2.3 Rate Limits Don't Compose the Way You'd Expect
If repair and validation each have their own independent per-key rate limit, calling both in sequence for every request means you're burning two budgets per logical operation, not one. At low volume this doesn't matter. At the volume where it starts to matter, it's a surprising thing to discover mid-incident.
3. The Solution: What a Single Pipelined Call Needs to Guarantee
The naive two-call version isn't wrong -- it's incomplete. A workflow-style endpoint that combines repair and validation into one billed request needs to explicitly guarantee a few things the naive version leaves as an exercise for the caller:
3.1 Gate on Total Cost Before Anything Runs
Check that the caller can afford the full sequence before executing step 1 -- not discover halfway through that they can't afford step 3. This also means checking for maintenance mode across every step in the sequence upfront, not partway through, so you don't run (and bill for) a step whose downstream step is known to be unavailable.
3.2 Halt on Failure, and Only Bill What Actually Ran
// Conceptual shape of the guarantee, not literal implementation:
// - repair fails -> nothing after it runs, nothing after it is billed
// - repair succeeds, validate says invalid, and the caller opted into
// halt-on-invalid-schema -> repair IS billed (it ran and succeeded),
// any step after validation is not
// - every step succeeds -> every step is billed, sum of each step's own costThis is a specific, checkable contract: charged only for steps that actually completed successfully, with no markup for the convenience of bundling -- the total should be identical to what calling each step separately and successfully would have cost.
3.3 One Rate Limit Budget for the Combined Operation
The pipelined endpoint's rate limit should be independent from -- not shared with -- the rate limits on the individual steps. Calling the combined operation shouldn't silently eat into the budget you have for calling repair or validation on their own for unrelated one-off requests.
3.4 Client Code Gets Simpler, Not Just Shorter
// Python -- calling repair and validation as one request
import requests
response = requests.post(
'https://json.flyingfishspace.com/api/json/v1/workflow/clean-validate',
headers={'X-API-Key': api_key, 'Content-Type': 'application/json'},
json={
'input': raw_text, # the original, possibly malformed text
'schema': target_schema, # required because this combo includes a schema step
},
)
result = response.json()
if result['success']:
print('Clean, validated data:', result['data'])
else:
# abort_reason tells you exactly which step halted the chain and why
print('Halted at:', result.get('abort_reason'))
for step in result['steps']:
print(' ', step['segment'], '->', 'ok' if step['success'] else step.get('error'))
print('Remaining balance:', result['remaining_balance'])No manual retry bookkeeping, no manual "should step 2 even run" branching, no separate rate-limit tracking for two endpoints -- one request, one response that tells you exactly where things stood at every step.
4. Common Pitfalls
Pitfall #1: Assuming a Validation Step Transforms the Document
In a repair-then-validate sequence, the validation step's job is to produce a report about the document -- valid or not, and why -- not to change the document itself. If your pipeline has a reshape step after validation, that reshape step should still be operating on the output of the repair step, not on whatever the validation step returned. Conflating "the data" with "the validation report" is an easy mistake if you're writing the orchestration yourself and treating every step's output as "the new input for the next step" uniformly.
Pitfall #2: Assuming Every Combo Has a Fixed, Predictable Cost
If any step in your sequence has tiered pricing based on its own internal logic (for example, an AI-repair step that only invokes the model when a cheaper rule-based pass isn't sufficient), the total cost of the combined sequence inherits that variability. Don't hardcode a single expected charge in your billing reconciliation -- read the actual cost back from the response, every time.
Pitfall #3: Not Deciding Up Front Whether an Invalid Result Should Halt the Chain
If your sequence is repair β validate β reshape, and validation reports the data as invalid, do you still want the reshape step to run on invalid data? Sometimes yes (you want the reshaped output regardless, for debugging). Often no (there's no point transforming data you're about to reject). This needs to be an explicit decision baked into the pipeline definition, not something your client code has to infer from a validation report after the fact.
Pitfall #4: Treating a 402 Mid-Chain as Recoverable
If the caller can't afford the full sequence, that should be caught before the first step runs -- not discovered after step 1 has already been billed and step 2 comes back insufficient-balance. A pipeline that checks total cost upfront avoids ever putting a caller in the position of having paid for an incomplete, unusable partial result.
5. Practical Checklist: Chain It Yourself, or Use a Combined Call?
Step 1: Is the Sequence Actually Fixed?
- β Do you always call repair, then always call validation, in the same order, for this data path?
- β If yes -- a combined call removes real bookkeeping. If the sequence varies per request based on business logic, keep chaining it yourself.
Step 2: Do You Need Per-Step Branching Outside the JSON Tooling?
- β Does anything between step 1 and step 2 need to call a *different* system (your own database, a notification service, etc.)?
- β If yes -- you need the intermediate result client-side anyway, so manual chaining may be simpler than working around a combined endpoint.
Step 3: Check the Billing Contract, Not Just the Feature List
- β Are you billed only for steps that actually completed?
- β Is the total cost checked upfront, before any step runs?
- β Does a failed step block billing for everything after it?
Step 4: Check Whether Rate Limits Compose the Way You Expect
- β Does the combined endpoint have its own rate limit budget, independent of the individual steps?
- β Will calling it at your expected volume interact badly with any other calls you make to the same underlying steps?
6. Decision Helper
A short script to reason through the trade-off for your specific case -- not a real API call, just a way to make the decision explicit instead of gut-feel:
function shouldUseCombinedCall({
sequenceIsFixed,
needsExternalSystemBetweenSteps,
stepCount,
callsPerDay,
}) {
if (!sequenceIsFixed) {
return 'Chain it yourself -- the order or steps vary per request.';
}
if (needsExternalSystemBetweenSteps) {
return 'Chain it yourself -- you need the intermediate result for something outside this pipeline.';
}
if (stepCount >= 2 && callsPerDay > 100) {
return 'Use a combined call -- fixed sequence at meaningful volume is exactly what removes the most manual bookkeeping.';
}
return 'Either works at this volume -- use whichever is less code for you right now.';
}
console.log(shouldUseCombinedCall({
sequenceIsFixed: true,
needsExternalSystemBetweenSteps: false,
stepCount: 2,
callsPerDay: 5000,
}));
// "Use a combined call..."Final Thoughts
Chaining two API calls yourself isn't wrong -- it's the natural first version of the solution, and for genuinely variable sequences, it's still the right one. The problem is that a fixed sequence you call the same way every time accumulates real bookkeeping the moment you think through the edge cases: partial billing, partial failure, two separate rate-limit budgets, and orchestration logic that has to be correct every single time it runs.
None of that complexity is inherent to "repair, then validate." It's inherent to doing that sequence as two independent, uncoordinated requests.
If you remember one thing: a fixed sequence is a strong signal that the orchestration belongs on the server, not in your client code -- and the contract to check before you rely on it is simple: bill only for what ran, check the total cost before anything starts, and halt cleanly when a step fails.
π JSON Pipeline's Workflow endpoints do exactly this
clean-validate runs repair and schema validation as one request, billed only for steps that complete, with the total cost checked before either step runs. If your JSON is coming from an LLM and might need AI-assisted repair before it can validate cleanly, ai-validate runs the same sequence with AI-powered repair as the first step -- inheriting its tiered pricing, so you're not always paying for the AI tier when the free rule-based pass alone would have worked. If you only need one of the two steps on their own, JSON Clean and Schema Validator are both available independently.