DEV Community

Iteration Layer
Iteration Layer

Posted on • Originally published at iterationlayer.com

Building a Multi-Client Document Pipeline with One API

The Setup: Three Clients, Three Pipelines, One Account

You're an agency with three active clients. Client A is a logistics company that needs invoice extraction. Client B is a law firm that needs contract parsing. Client C is a real estate agency that needs property listing extraction and brochure generation. Each client has different document types, different fields, and different output requirements.

The typical approach: three vendor accounts, three sets of credentials, three billing relationships. You've done this before and you know where it leads — a tangle of API keys, inconsistent error handling, and a billing reconciliation process that eats an afternoon every month.

This tutorial walks through setting up all three pipelines on Iteration Layer with project-scoped API keys, per-client extraction schemas, a shared credit pool, and budget caps to keep costs predictable.

Step 1: Create Projects

In the Iteration Layer dashboard, create a project for each client. Projects are organizational containers — they scope API keys and track usage separately while sharing your account's credit pool.

Navigate to Projects in the sidebar and create three projects:

  • acme-logistics — invoice extraction
  • sterling-legal — contract parsing
  • summit-realty — property listing extraction + brochure generation

Each project gets its own usage tracking. You can see exactly how many credits each client's pipeline consumes per billing period.

Step 2: Generate Project-Scoped API Keys

For each project, generate an API key. Navigate to API Keys, select the project from the dropdown, and create a new key. The key is scoped to that project — all requests made with it are tracked under that project's usage.

You'll end up with three API keys:

  • il_acme1234_... — scoped to acme-logistics
  • il_sterl567_... — scoped to sterling-legal
  • il_summt890_... — scoped to summit-realty

Store these in your deployment environment. Each client's pipeline uses its own key. If you need to revoke access for one client, you disable their key without affecting the others.

Step 3: Define Extraction Schemas Per Client

Each client needs different fields extracted from different document types. The extraction API uses the same endpoint for all of them — the schema defines what to extract.

Client A: Invoice Extraction

import { IterationLayer } from "iterationlayer";

const acmeClient = new IterationLayer({
  apiKey: "il_acme1234_YOUR_KEY",
});

const invoiceResult = await acmeClient.extract({
  files: [
    {
      type: "url",
      name: "invoice-2026-0451.pdf",
      url: "https://acme-docs.example.com/invoice-2026-0451.pdf",
    },
  ],
  schema: {
    fields: [
      {
        name: "invoice_number",
        type: "TEXT",
        description: "Invoice number or reference code",
      },
      {
        name: "vendor_name",
        type: "TEXT",
        description: "Name of the company that issued the invoice",
      },
      {
        name: "total_amount",
        type: "CURRENCY_AMOUNT",
        description: "Total amount due",
        decimal_points: 2,
      },
      {
        name: "currency",
        type: "CURRENCY_CODE",
        description: "Currency of the invoice",
      },
      {
        name: "due_date",
        type: "DATE",
        description: "Payment due date",
      },
      {
        name: "line_items",
        type: "ARRAY",
        description: "Individual line items on the invoice",
        fields: [
            {
              name: "description",
              type: "TEXT",
              description: "Line item description",
            },
            {
              name: "quantity",
              type: "INTEGER",
              description: "Quantity",
            },
            {
              name: "unit_price",
              type: "CURRENCY_AMOUNT",
              description: "Price per unit",
              decimal_points: 2,
            },
          ],
      },
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode

Client B: Contract Parsing

The same endpoint, different schema. Client B's law firm needs party names, effective dates, termination clauses, and governing law extracted from contracts.

const sterlingClient = new IterationLayer({
  apiKey: "il_sterl567_YOUR_KEY",
});

const contractResult = await sterlingClient.extract({
  files: [
    {
      type: "url",
      name: "service-agreement-2026.pdf",
      url: "https://sterling-docs.example.com/service-agreement-2026.pdf",
    },
  ],
  schema: {
    fields: [
      {
        name: "party_a",
        type: "TEXT",
        description: "First contracting party (the service provider)",
      },
      {
        name: "party_b",
        type: "TEXT",
        description: "Second contracting party (the client)",
      },
      {
        name: "effective_date",
        type: "DATE",
        description: "Date the contract takes effect",
      },
      {
        name: "termination_date",
        type: "DATE",
        description: "Date the contract expires or can be terminated",
      },
      {
        name: "governing_law",
        type: "TEXT",
        description: "Jurisdiction whose law governs the contract",
      },
      {
        name: "total_value",
        type: "CURRENCY_AMOUNT",
        description: "Total contract value",
        decimal_points: 2,
      },
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode

Two clients, two schemas, same API, same error handling, same SDK. The only difference is the API key and the fields.

Step 4: Chain Extraction into Generation

Client C needs more than extraction — they need the extracted property listing data turned into a PDF brochure. This is where composability matters. The extraction result feeds directly into document generation.

const summitClient = new IterationLayer({
  apiKey: "il_summt890_YOUR_KEY",
});

// Step 1: Extract property listing data
const listingResult = await summitClient.extract({
  files: [
    {
      type: "url",
      name: "listing-445.pdf",
      url: "https://summit-docs.example.com/listing-445.pdf",
    },
  ],
  schema: {
    fields: [
      {
        name: "property_address",
        type: "ADDRESS",
        description: "Full property address",
      },
      {
        name: "asking_price",
        type: "CURRENCY_AMOUNT",
        description: "Listed asking price",
        decimal_points: 0,
      },
      {
        name: "bedrooms",
        type: "INTEGER",
        description: "Number of bedrooms",
      },
      {
        name: "square_meters",
        type: "DECIMAL",
        description: "Living area in square meters",
        decimal_points: 1,
      },
      {
        name: "description",
        type: "TEXTAREA",
        description: "Property description text",
      },
    ],
  },
});

// Step 2: Generate PDF brochure from extracted data
const brochure = await summitClient.generateDocument({
  format: "pdf",
  document: {
    metadata: {
      title: `Property Listing - ${listingResult.property_address.value}`,
    },
    content: [
      {
        type: "headline",
        level: "h1",
        text: String(listingResult.property_address.value),
      },
      {
        type: "paragraph",
        markdown: `**Asking price:** EUR ${listingResult.asking_price.value} | **Bedrooms:** ${listingResult.bedrooms.value} | **Area:** ${listingResult.square_meters.value} m2`,
      },
      {
        type: "separator",
      },
      {
        type: "paragraph",
        markdown: String(listingResult.description.value),
      },
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode

Same API key, same client instance. The extraction output flows directly into the document generation input. No format conversion, no intermediate storage, no second vendor.

Step 5: Set Budget Caps

Every project in the dashboard has an optional budget cap — a maximum number of credits the project can consume per billing period. This is critical for fixed-fee client work.

Set the cap based on your expected usage plus a safety margin. If client A's invoice pipeline typically uses 200 credits per month, set the cap at 300. If the pipeline hits the cap, requests will be rejected with a clear error rather than silently burning through your budget.

This gives you an early warning system. A cap hit means something changed — either the client's volume increased (time to renegotiate) or something in the pipeline is misbehaving (time to debug). Either way, you find out before the bill arrives.

What You Get

Three client pipelines, three API keys, three extraction schemas — all running through one account with one credit pool and one invoice. Adding client D means creating a new project, generating a new key, and writing a new schema. Not evaluating a new vendor, not setting up a new billing relationship, not learning a new SDK.

The per-project usage tracking means you always know what each client costs. The budget caps mean you control what each client can cost. And because every API uses the same auth pattern, error format, and response structure, your team's integration code is reusable across every client project.

Start with a free account — no credit card required. Create your first project, generate an API key, and run an extraction. The Document Extraction docs and Document Generation docs have the full schema reference.

Top comments (0)