API Composability Patterns: How to Chain Iteration Layer APIs

8 min read

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, confidence score (0.0–1.0), citations (source text from the document), and source (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:

  1. Extract the report for reporting_period, total_revenue, revenue_growth_percent, and top_products. Each field comes back with a confidence score.
  2. 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.
  3. 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:

Request
# 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
        }
      }
    ]
  }"
Response
{
  "success": true,
  "data": {
    "buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mime_type": "image/png"
  }
}
Request
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,
      },
    },
  ],
});
Response
{
  "success": true,
  "data": {
    "buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mime_type": "image/png"
  }
}
Request
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,
            },
        },
    ],
)
Response
{
  "success": true,
  "data": {
    "buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mime_type": "image/png"
  }
}
Request
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
}
Response
{
  "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.

Request
# 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\"
        }
      ]
    }
  }"
Response
{
  "success": true,
  "data": {
    "buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mime_type": "image/png"
  }
}
Request
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,
            },
          },
        ],
      })
    ),
);
Response
{
  "success": true,
  "data": {
    "buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mime_type": "image/png"
  }
}
Request
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)
Response
{
  "success": true,
  "data": {
    "buffer": "iVBORw0KGgoAAAANSUhEUgAA...",
    "mime_type": "image/png"
  }
}
Request
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()
}
Response
{
  "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.

Start building in minutes

Free trial included. No credit card required.