Four APIs, One Data Contract
Iteration Layer ships four APIs: Document Extraction, Image Transformation, Image Generation, and Document Generation. Each one does one thing. Extraction parses documents into structured data. Transformation processes images through ordered operations. Image Generation composes layers into a visual. Document Generation renders content blocks into PDFs, DOCX, EPUB, or PPTX.
What makes them composable is a shared data contract. Every API accepts files as URLs or base64 buffers. Every API that produces a file returns a base64 buffer. Extraction returns structured JSON with confidence scores on every field. These contracts are stable — the output of one API is always a valid input for the next.
This post covers the patterns for chaining these APIs together, when to use each pattern, and how to handle errors across steps.
The Building Blocks
Before chaining, understand what each API produces:
-
Document Extraction returns structured JSON. Every field has a
value,confidencescore (0.0–1.0),citations(source text from the document), andsource(filename). You define the schema — field names, types, and descriptions — and the API fills it in. - Image Transformation returns a base64 buffer. You send an image and a list of operations (resize, crop, convert, sharpen, etc.). Operations run in order. Up to 30 per request.
- Image Generation returns a base64 buffer. You define layers — backgrounds, text, images, QR codes — and the API composites them into a single image.
- Document Generation returns a base64 buffer. You define content blocks — paragraphs, tables, headlines, images, lists — and the API renders them into a document.
The composability comes from how these connect. Extraction produces data that Generation consumes. Transformation produces images that Generation embeds. Any output buffer can feed into the next step as a base64 input.
Pattern 1: Extract then Render
The most common pattern. Parse a document for structured data, then render that data as a visual or document.
Extract → Image Generation: Parse an invoice, render a summary card. Parse a resume, render a profile card. Parse a report, render a KPI dashboard image.
Extract → Document Generation: Parse receipts, generate an expense report PDF. Parse contracts, generate a summary document. Parse survey responses, generate a formatted report.
The data flows in one direction: document in, structured JSON out, rendered output out. The extraction schema defines exactly which fields to pull, and the generation step maps those fields into the output layout.
The key insight is that extraction handles all the complexity — OCR, layout analysis, field type coercion — so the generation step only deals with clean, typed data. You never parse a PDF yourself.
For full implementations of this pattern, see From Raw Invoice to Branded Social Card and Generate Candidate Profile Cards from Resumes.
Pattern 2: Transform then Compose
Process an image before embedding it in a generated output. This matters when the source image isn’t the right size, format, or quality for the final composition.
Transform → Image Generation: Resize a company logo to exact pixel dimensions, then place it as a layer in a branded card. Smart-crop a product photo to a square, then composite it into a listing image.
Transform → Document Generation: Convert a PNG to JPEG and compress it, then embed it in a PDF report. Upscale a low-resolution chart, then place it in a slide deck.
The transformation step normalizes the image — right dimensions, right format, right quality. The generation step places it.
Without this pattern, you’d either accept whatever the source image gives you (wrong aspect ratio, too large, wrong format) or build image processing into your own code. Transformation handles it in one API call before generation.
For operation ordering and available transformations, see Build an Image Processing Pipeline with One API Call.
Pattern 3: Extract, Transform, Generate
The full three-step pipeline. Parse a document for data and image references, process the images, then generate the final output using both.
A concrete example: a sales report arrives as a PDF. You need a visual summary image with the company logo, key metrics, and a chart. Three steps:
-
Extract the report for
reporting_period,total_revenue,revenue_growth_percent, andtop_products. Each field comes back with a confidence score. - Transform the company logo — resize to 120x40, convert to PNG. The logo URL might point to an SVG or a 2000px image. Transformation normalizes it to exactly what the image layer needs.
- Generate the visual summary — dark background, accent bar, logo layer (from the transform output), text layers populated with extracted data.
The data flow looks like this:
PDF → Extract → { reporting_period, total_revenue, ... }
Logo URL → Transform → base64 PNG buffer
{ extracted data, logo buffer } → Generate Image → base64 PNG summary
Each step is a single API call. No intermediate storage, no file system, no servers. The output of steps 1 and 2 feeds directly into step 3.
For a full implementation of this pattern, see Automated Report Generation Pipeline.
Error Handling Across Steps
When you chain APIs, a failure in step 1 can cascade. Extraction is the most common source of uncertainty — the document might be a bad scan, a new format, or missing fields. The confidence scores are your primary defense.
Gate each step on the confidence of the data it depends on:
# Step 1: Extract the data
EXTRACTION_RESPONSE=$(curl -s -X POST https://api.iterationlayer.com/extract \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"type": "url",
"name": "report.pdf",
"url": "https://example.com/reports/q1-2026.pdf"
}
],
"schema": {
"fields": [
{
"name": "total_revenue",
"type": "CURRENCY_AMOUNT",
"description": "Total revenue for the period"
},
{
"name": "reporting_period",
"type": "TEXT",
"description": "The reporting period covered"
}
]
}
}')
# Step 2: Check confidence before proceeding
REVENUE_CONFIDENCE=$(echo "$EXTRACTION_RESPONSE" | jq '.data.total_revenue.confidence')
PERIOD_CONFIDENCE=$(echo "$EXTRACTION_RESPONSE" | jq '.data.reporting_period.confidence')
if (( $(echo "$REVENUE_CONFIDENCE < 0.8" | bc -l) )) || \
(( $(echo "$PERIOD_CONFIDENCE < 0.8" | bc -l) )); then
echo "Low confidence extraction — flagging for manual review"
exit 1
fi
# Step 3: Generate the visual using extracted data
TOTAL_REVENUE=$(echo "$EXTRACTION_RESPONSE" | jq -r '.data.total_revenue.value')
REPORTING_PERIOD=$(echo "$EXTRACTION_RESPONSE" | jq -r '.data.reporting_period.value')
curl -X POST https://api.iterationlayer.com/image-generation/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"dimensions\": {
\"width\": 1200,
\"height\": 630
},
\"output_format\": \"png\",
\"layers\": [
{
\"index\": 0,
\"type\": \"solid-color\",
\"hex_color\": \"#0f172a\"
},
{
\"index\": 1,
\"type\": \"text\",
\"text\": \"$REPORTING_PERIOD\",
\"font_name\": \"Inter\",
\"font_size_in_px\": 24,
\"text_color\": \"#94a3b8\",
\"position\": {
\"x\": 60.0,
\"y\": 40.0
},
\"dimensions\": {
\"width\": 1080,
\"height\": 40
}
},
{
\"index\": 2,
\"type\": \"text\",
\"text\": \"$$TOTAL_REVENUE\",
\"font_name\": \"Inter\",
\"font_size_in_px\": 64,
\"font_weight\": \"Bold\",
\"text_color\": \"#4ade80\",
\"position\": {
\"x\": 60.0,
\"y\": 100.0
},
\"dimensions\": {
\"width\": 1080,
\"height\": 80
}
}
]
}"{
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const MINIMUM_CONFIDENCE = 0.8;
// Step 1: Extract the data
const extractionResult = await client.extract({
files: [
{
type: "url",
name: "report.pdf",
url: "https://example.com/reports/q1-2026.pdf",
},
],
schema: {
fields: [
{
name: "total_revenue",
type: "CURRENCY_AMOUNT",
description: "Total revenue for the period",
},
{
name: "reporting_period",
type: "TEXT",
description: "The reporting period covered",
},
],
},
});
const { total_revenue, reporting_period } = extractionResult.data;
// Step 2: Check confidence before proceeding
const isHighConfidence = total_revenue.confidence >= MINIMUM_CONFIDENCE
&& reporting_period.confidence >= MINIMUM_CONFIDENCE;
if (!isHighConfidence) {
throw new Error("Low confidence extraction — flagging for manual review");
}
// Step 3: Generate the visual using extracted data
const summaryImage = await client.generateImage({
dimensions: {
width_in_px: 1200,
height_in_px: 630,
},
output_format: "png",
layers: [
{
index: 0,
type: "solid-color",
hex_color: "#0f172a",
},
{
index: 1,
type: "text",
text: reporting_period.value,
font_name: "Inter",
font_size_in_px: 24,
text_color: "#94a3b8",
position: {
x_in_px: 60,
y_in_px: 40,
},
dimensions: {
width_in_px: 1080,
height_in_px: 40,
},
},
{
index: 2,
type: "text",
text: `$${total_revenue.value.toFixed(2)}`,
font_name: "Inter",
font_size_in_px: 64,
font_weight: "Bold",
text_color: "#4ade80",
position: {
x_in_px: 60,
y_in_px: 100,
},
dimensions: {
width_in_px: 1080,
height_in_px: 80,
},
},
],
});{
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")
MINIMUM_CONFIDENCE = 0.8
# Step 1: Extract the data
extraction_result = client.extract(
files=[
{
"type": "url",
"name": "report.pdf",
"url": "https://example.com/reports/q1-2026.pdf",
}
],
schema={
"fields": [
{
"name": "total_revenue",
"type": "CURRENCY_AMOUNT",
"description": "Total revenue for the period",
},
{
"name": "reporting_period",
"type": "TEXT",
"description": "The reporting period covered",
},
]
},
)
total_revenue = extraction_result.data["total_revenue"]
reporting_period = extraction_result.data["reporting_period"]
# Step 2: Check confidence before proceeding
is_high_confidence = (
total_revenue["confidence"] >= MINIMUM_CONFIDENCE
and reporting_period["confidence"] >= MINIMUM_CONFIDENCE
)
if not is_high_confidence:
raise ValueError("Low confidence extraction — flagging for manual review")
# Step 3: Generate the visual using extracted data
summary_image = client.generate_image(
dimensions={
"width_in_px": 1200,
"height_in_px": 630,
},
output_format="png",
layers=[
{
"index": 0,
"type": "solid-color",
"hex_color": "#0f172a",
},
{
"index": 1,
"type": "text",
"text": reporting_period["value"],
"font_name": "Inter",
"font_size_in_px": 24,
"text_color": "#94a3b8",
"position": {
"x_in_px": 60,
"y_in_px": 40,
},
"dimensions": {
"width_in_px": 1080,
"height_in_px": 40,
},
},
{
"index": 2,
"type": "text",
"text": f"${total_revenue['value']:.2f}",
"font_name": "Inter",
"font_size_in_px": 64,
"font_weight": "Bold",
"text_color": "#4ade80",
"position": {
"x_in_px": 60,
"y_in_px": 100,
},
"dimensions": {
"width_in_px": 1080,
"height_in_px": 80,
},
},
],
){
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}package main
import (
"fmt"
il "github.com/iterationlayer/sdk-go"
)
func main() {
client := il.NewClient("YOUR_API_KEY")
minimumConfidence := 0.8
// Step 1: Extract the data
extractionResult, err := client.Extract(il.ExtractRequest{
Files: []il.File{
{
Type: "url",
Name: "report.pdf",
URL: "https://example.com/reports/q1-2026.pdf",
},
},
Schema: il.Schema{
Fields: []il.Field{
{
Name: "total_revenue",
Type: "CURRENCY_AMOUNT",
Description: "Total revenue for the period",
},
{
Name: "reporting_period",
Type: "TEXT",
Description: "The reporting period covered",
},
},
},
})
if err != nil {
panic(err)
}
totalRevenue := extractionResult.Data["total_revenue"]
reportingPeriod := extractionResult.Data["reporting_period"]
// Step 2: Check confidence before proceeding
if totalRevenue.Confidence < minimumConfidence ||
reportingPeriod.Confidence < minimumConfidence {
panic("Low confidence extraction — flagging for manual review")
}
// Step 3: Generate the visual using extracted data
summaryImage, err := client.GenerateImage(il.GenerateImageRequest{
Dimensions: il.Dimensions{
WidthInPx: 1200,
HeightInPx: 630,
},
OutputFormat: "png",
Layers: []il.Layer{
il.NewSolidColorLayer(0, "#0f172a"),
il.NewTextLayer(
1, reportingPeriod.Value.(string),
"Inter", 24, "#94a3b8",
il.Position{
XInPx: 60,
YInPx: 40,
},
il.Dimensions{
WidthInPx: 1080,
HeightInPx: 40,
},
),
il.NewTextLayer(
2, fmt.Sprintf("$%.2f", totalRevenue.Value),
"Inter", 64, "#4ade80",
il.Position{
XInPx: 60,
YInPx: 100,
},
il.Dimensions{
WidthInPx: 1080,
HeightInPx: 80,
},
),
},
})
if err != nil {
panic(err)
}
_ = summaryImage
}{
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}The confidence check between extraction and generation is the critical gate. Set a threshold that matches your use case — 0.8 works for most automated pipelines. For high-stakes outputs (financial reports, legal documents), push it to 0.95 and route low-confidence results to human review.
For fields that are nice-to-have rather than critical, you can use a softer strategy: render the card but show a placeholder or “N/A” for low-confidence fields instead of blocking the entire pipeline.
Pipelines vs Parallel Calls
Not every multi-API workflow is a pipeline. The distinction matters for both performance and correctness.
Use a pipeline when the output of one step is the input of the next. Extract a document, then generate an image from the extracted data. Transform an image, then embed it in a generated document. Each step depends on the previous result. These run sequentially.
Use parallel calls when steps are independent. Extract 50 invoices, then generate 50 summary cards. The extractions don’t depend on each other. Neither do the generations (once the data is available). Run the extractions in parallel, collect the results, then run the generations in parallel.
# Parallel extraction with xargs
echo "invoice-001.pdf
invoice-002.pdf
invoice-003.pdf" | xargs -P 3 -I {} curl -s -X POST https://api.iterationlayer.com/extract \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"files\": [
{
\"type\": \"url\",
\"name\": \"{}\",
\"url\": \"https://example.com/invoices/{}\"
}
],
\"schema\": {
\"fields\": [
{
\"name\": \"vendor_name\",
\"type\": \"TEXT\",
\"description\": \"Vendor or supplier name\"
},
{
\"name\": \"total_amount\",
\"type\": \"CURRENCY_AMOUNT\",
\"description\": \"Total amount due\"
}
]
}
}"{
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });
const invoiceUrls = [
"https://example.com/invoices/invoice-001.pdf",
"https://example.com/invoices/invoice-002.pdf",
"https://example.com/invoices/invoice-003.pdf",
];
// Step 1: Extract all invoices in parallel
const extractionResults = await Promise.all(
invoiceUrls.map((url) =>
client.extract({
files: [
{
type: "url",
name: url.split("/").pop(),
url,
},
],
schema: {
fields: [
{
name: "vendor_name",
type: "TEXT",
description: "Vendor or supplier name",
},
{
name: "total_amount",
type: "CURRENCY_AMOUNT",
description: "Total amount due",
},
],
},
})
),
);
// Step 2: Generate summary cards in parallel (only for high-confidence results)
const summaryCards = await Promise.all(
extractionResults
.filter(
(result) =>
result.data.vendor_name.confidence >= 0.8
&& result.data.total_amount.confidence >= 0.8
)
.map((result) =>
client.generateImage({
dimensions: {
width_in_px: 800,
height_in_px: 400,
},
output_format: "png",
layers: [
{
index: 0,
type: "solid-color",
hex_color: "#0f172a",
},
{
index: 1,
type: "text",
text: result.data.vendor_name.value,
font_name: "Inter",
font_size_in_px: 28,
font_weight: "Bold",
text_color: "#ffffff",
position: {
x_in_px: 40,
y_in_px: 40,
},
dimensions: {
width_in_px: 720,
height_in_px: 40,
},
},
{
index: 2,
type: "text",
text: `$${result.data.total_amount.value.toFixed(2)}`,
font_name: "Inter",
font_size_in_px: 48,
font_weight: "Bold",
text_color: "#4ade80",
position: {
x_in_px: 40,
y_in_px: 100,
},
dimensions: {
width_in_px: 720,
height_in_px: 60,
},
},
],
})
),
);{
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}import asyncio
from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")
invoice_urls = [
"https://example.com/invoices/invoice-001.pdf",
"https://example.com/invoices/invoice-002.pdf",
"https://example.com/invoices/invoice-003.pdf",
]
schema = {
"fields": [
{
"name": "vendor_name",
"type": "TEXT",
"description": "Vendor or supplier name",
},
{
"name": "total_amount",
"type": "CURRENCY_AMOUNT",
"description": "Total amount due",
},
]
}
# Step 1: Extract all invoices in parallel
extraction_tasks = [
client.extract_async(
files=[
{
"type": "url",
"name": url.split("/")[-1],
"url": url,
}
],
schema=schema,
)
for url in invoice_urls
]
extraction_results = await asyncio.gather(*extraction_tasks)
# Step 2: Generate summary cards in parallel (only for high-confidence results)
high_confidence_results = [
result
for result in extraction_results
if result.data["vendor_name"]["confidence"] >= 0.8
and result.data["total_amount"]["confidence"] >= 0.8
]
generation_tasks = [
client.generate_image_async(
dimensions={
"width_in_px": 800,
"height_in_px": 400,
},
output_format="png",
layers=[
{
"index": 0,
"type": "solid-color",
"hex_color": "#0f172a",
},
{
"index": 1,
"type": "text",
"text": result.data["vendor_name"]["value"],
"font_name": "Inter",
"font_size_in_px": 28,
"font_weight": "Bold",
"text_color": "#ffffff",
"position": {
"x_in_px": 40,
"y_in_px": 40,
},
"dimensions": {
"width_in_px": 720,
"height_in_px": 40,
},
},
{
"index": 2,
"type": "text",
"text": f"${result.data['total_amount']['value']:.2f}",
"font_name": "Inter",
"font_size_in_px": 48,
"font_weight": "Bold",
"text_color": "#4ade80",
"position": {
"x_in_px": 40,
"y_in_px": 100,
},
"dimensions": {
"width_in_px": 720,
"height_in_px": 60,
},
},
],
)
for result in high_confidence_results
]
summary_cards = await asyncio.gather(*generation_tasks){
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}package main
import (
"fmt"
"sync"
il "github.com/iterationlayer/sdk-go"
)
func main() {
client := il.NewClient("YOUR_API_KEY")
invoiceURLs := []string{
"https://example.com/invoices/invoice-001.pdf",
"https://example.com/invoices/invoice-002.pdf",
"https://example.com/invoices/invoice-003.pdf",
}
schema := il.Schema{
Fields: []il.Field{
{
Name: "vendor_name",
Type: "TEXT",
Description: "Vendor or supplier name",
},
{
Name: "total_amount",
Type: "CURRENCY_AMOUNT",
Description: "Total amount due",
},
},
}
// Step 1: Extract all invoices in parallel
extractionResults := make([]il.ExtractResult, len(invoiceURLs))
var wg sync.WaitGroup
for i, url := range invoiceURLs {
wg.Add(1)
go func(idx int, fileURL string) {
defer wg.Done()
result, err := client.Extract(il.ExtractRequest{
Files: []il.File{
{
Type: "url",
Name: fmt.Sprintf("invoice-%03d.pdf", idx+1),
URL: fileURL,
},
},
Schema: schema,
})
if err != nil {
panic(err)
}
extractionResults[idx] = result
}(i, url)
}
wg.Wait()
// Step 2: Generate summary cards in parallel (only for high-confidence results)
var highConfidenceResults []il.ExtractResult
for _, result := range extractionResults {
vendorName := result.Data["vendor_name"]
totalAmount := result.Data["total_amount"]
if vendorName.Confidence >= 0.8 && totalAmount.Confidence >= 0.8 {
highConfidenceResults = append(highConfidenceResults, result)
}
}
summaryCards := make([]il.GenerateImageResult, len(highConfidenceResults))
var wg2 sync.WaitGroup
for i, result := range highConfidenceResults {
wg2.Add(1)
go func(idx int, data il.ExtractResult) {
defer wg2.Done()
card, err := client.GenerateImage(il.GenerateImageRequest{
Dimensions: il.Dimensions{
WidthInPx: 800,
HeightInPx: 400,
},
OutputFormat: "png",
Layers: []il.Layer{
il.NewSolidColorLayer(0, "#0f172a"),
il.NewTextLayer(
1, data.Data["vendor_name"].Value.(string),
"Inter", 28, "#ffffff",
il.Position{
XInPx: 40,
YInPx: 40,
},
il.Dimensions{
WidthInPx: 720,
HeightInPx: 40,
},
),
il.NewTextLayer(
2, fmt.Sprintf("$%.2f", data.Data["total_amount"].Value),
"Inter", 48, "#4ade80",
il.Position{
XInPx: 40,
YInPx: 100,
},
il.Dimensions{
WidthInPx: 720,
HeightInPx: 60,
},
),
},
})
if err != nil {
panic(err)
}
summaryCards[idx] = card
}(i, result)
}
wg2.Wait()
}{
"success": true,
"data": {
"buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
"mime_type": "image/png"
}
}The pattern is: fan out at each stage, collect results, filter, then fan out again. Extractions run in parallel. Confidence filtering happens synchronously. Generations run in parallel. Total wall-clock time is roughly the slowest extraction plus the slowest generation — not the sum of all calls.
When to Split Steps vs Combine Them
Some Iteration Layer APIs handle multiple concerns in a single call. Image Transformation chains up to 30 operations sequentially. Document Generation accepts images as URLs or base64 inline. You don’t always need separate API calls.
Combine when you can. If you need to resize and sharpen an image before converting it to WebP, that’s one Image Transformation call with three operations — not three calls. If you need to embed a logo in a PDF and the logo URL is already the right size, pass the URL directly to Document Generation — no transformation step needed.
Split when the intermediate result matters. If you need the transformed image for both a generated card and a PDF report, transform it once and reuse the buffer. If you need to validate extracted data before generating anything, extraction must be a separate step so you can inspect the confidence scores.
Split when steps have different error modes. Extraction can fail silently (low confidence on a badly scanned document). Transformation fails loudly (unsupported format, file too large). Generation rarely fails if the input is valid. Separating steps lets you handle each failure mode appropriately.
Putting It Together
The four APIs compose because they share a consistent contract: URLs and base64 in, structured data or base64 buffers out, confidence scores where uncertainty exists. The patterns are simple:
- Extract → Render for document-to-visual or document-to-document workflows
- Transform → Compose for image preprocessing before generation
- Extract → Transform → Generate for full pipelines that touch all three concerns
- Parallel fan-out for batch processing independent items at each stage
- Confidence gating between extraction and downstream steps
Start with the simplest pattern that solves your problem. Chain more APIs only when the workflow requires it. Every API call is a POST request with JSON — no SDKs required, no infrastructure to deploy, no state to manage between steps.
Check the full API references: Document Extraction, Image Transformation, Image Generation, and Document Generation.