Government & Public SectorParse → Extract

W-9 Tax Form Extractor

Extracts taxpayer identification numbers and tax classification from W-9 forms.

Ship it with Extend

Live pipeline

a real document, processed end to end · view only
Source document463754640-2020-w-9-pdf.pdf

Step-by-step

A W-9 form is a U.S. tax document used to collect and certify a taxpayer's identification information, including name, address, taxpayer identification number (TIN), and federal tax classification, which is required by organizations before making payments to contractors and vendors for tax reporting and backup withholding compliance. This template takes in W-9 Tax Form and outputs markdown (.md) preserving the document's original layout and text content, and JSON (.json) with structured tax identification and certification fields including TIN variants (EIN/SSN/ITIN), name, address, tax classification, and signature metadata per the extraction schema by using Extend's Parse, Extract primitives.

Input
W-9 Tax Form
Compatible document types (full list)
.pdf.docx.xlsx.png.jpg.jpeg.tiff.tif.svg.heic.heif.bmp.gif.webp.psd.xls.xltm.xltx.ods.doc.wpd.dotx.odt.pptx.ppt.ppm.csv.txt.html.xml.rtf.lis.md.eml.pcx
Step 1

Parse

Converts the document into clean, layout-aware markdown plus structured blocks with spatial metadata.

InputSource document — PDF, image, spreadsheet, presentation, or scan
Config
blockOptions.text.agentic.enabledtruechanged
chunkingStrategy.type"document"
engine"parse_performance"
OutputMarkdown chunked by page or section, plus typed blocks (text, table, figure) with bounding boxes

You can learn more about Parse configuration in Extend's Parse documentation.

Step 2

Extract

Pulls a defined set of fields from the document and returns them as structured JSON matching a schema.

InputOutput of the Parse step
Config
schemacustom schema — 16 fieldschanged
advancedOptions.advancedMultimodalEnabledtruechanged
advancedOptions.reviewAgent.enabledtruechanged
baseProcessor"extraction_performance"
OutputJSON shaped to the extraction schema, with per-field confidence scores and citations grounding each value to its source location

You can learn more about Extract configuration in Extend's Extract documentation.

Example code

{
  "name": "W-9 Tax Form Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": true
              }
            }
          },
          "chunkingStrategy": {
            "type": "document"
          }
        }
      },
      "next": [
        {
          "step": "extraction2"
        }
      ]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "required": [
              "ein",
              "ssn",
              "itin",
              "name",
              "address",
              "signature",
              "date_signed",
              "business_name",
              "city_state_zip",
              "account_numbers",
              "exempt_payee_code",
              "llc_tax_classification",
              "other_tax_classification",
              "federal_tax_classification",
              "requester_name_and_address",
              "exemption_from_fatca_reporting_code"
            ],
            "properties": {
              "ein": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors."
              },
              "ssn": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors."
              },
              "itin": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form."
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer."
              },
              "signature": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification."
              },
              "date_signed": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format.",
                "extend:type": "date"
              },
              "business_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity."
              },
              "city_state_zip": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code."
              },
              "account_numbers": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester."
              },
              "exempt_payee_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt."
              },
              "llc_tax_classification": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership)."
              },
              "other_tax_classification": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided."
              },
              "federal_tax_classification": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form."
              },
              "requester_name_and_address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name and address of the requester, if provided. This is optional and may be included for reference."
              },
              "exemption_from_fatca_reporting_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S."
              }
            },
            "additionalProperties": false
          },
          "baseProcessor": "extraction_performance",
          "advancedOptions": {
            "reviewAgent": {
              "enabled": true
            },
            "advancedMultimodalEnabled": true
          }
        }
      }
    }
  ]
}
# W-9 Tax Form Processing — Extend AI Skill

## What this pipeline does

Automates extraction of critical tax compliance data from IRS Form W-9s by parsing the document to markdown, then extracting 16 structured fields including taxpayer identification (EIN/SSN/ITIN), name, address, business classification, and certification details. Outputs a JSON object with all required fields for downstream tax reporting, backup withholding compliance, and vendor onboarding systems.

## When to use this

- **Vendor onboarding workflows**: Automatically capture W-9 data at contract signing to populate tax databases and compliance files without manual data entry.
- **Bulk W-9 processing**: Handle hundreds of W-9s from contractors, consultants, and vendors; parse, extract, and validate all in one async pipeline.
- **Tax preparation & reconciliation**: Extract line-item form data to cross-check against 1099-NEC/MISC records and reconcile payment history.
- **Compliance audits**: Verify that all required certification fields (signature, date, federal tax classification) are present and readable before payments are processed.
- **Multi-entity tax structures**: Capture LLC tax classifications, business name variants, and FATCA exemption codes for complex corporate structures.

## Processor pipeline

### 1. Parse (`parse_performance` + agentic OCR)
**Purpose**: Convert W-9 form pages (potentially handwritten, stamped, or faxed) into clean markdown and bounding-box metadata.

**Config**: 
- `engine: "parse_performance"` — balances speed and accuracy; suitable for production volumes.
- `agentic: { enabled: true }` — enables intelligent text recognition for handwritten signatures, checkboxes, and form field fills.
- `chunkingStrategy: { type: "document" }` — treats the entire W-9 as one logical chunk, preserving field relationships.

**Why this config**: W-9s are short, single-document forms with fixed structure. Agentic OCR is essential because real-world W-9s are often faxed, scanned, or hand-filled with varying ink quality. Document-level chunking prevents accidental field fragmentation across chunk boundaries.

### 2. Extract (`extraction_performance` + review agent)
**Purpose**: Apply the 16-field schema to the parsed markdown, using LLM reasoning to map form text to structured fields.

**Config**:
- `baseProcessor: "extraction_performance"` — optimized for accuracy on regulatory forms; worth the extra latency for compliance-critical data.
- `reviewAgent: { enabled: true }` — triggers a secondary agentic review pass to flag and correct low-confidence extractions (e.g., ambiguous TIN formats, partial signatures).
- `advancedMultimodalEnabled: true` — allows the extractor to cross-reference the original image and parsed text when resolving field ambiguities.

**Why this config**: Tax forms are compliance documents; extraction errors can cascade into failed 1099 filings and backup withholding violations. The review agent is the safety net for edge cases like crossed-out names, multiple TINs on the same form, or faded signatures.

---

## TypeScript implementation



---

## CLI equivalent

```bash
# Step 1: Parse the W-9 form to markdown
extend parse w9-form.pdf --mode agentic_ocr --output markdown

# Step 2: Extract all 16 W-9 fields using the predefined schema
extend extract w9-form.pdf \
  --schema w9-schema.json \
  --base-processor extraction_performance \
  --advanced-options '{"reviewAgent": {"enabled": true}, "advancedMultimodalEnabled": true}'

# Or, run the entire workflow in one command (if saved)
extend run workflow_w9_processing --file w9-form.pdf
```

**w9-schema.json** (for CLI use):
```json
{
  "type": "object",
  "properties": {
    "ein": { "type": ["string", "null"], "description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors." },
    "ssn": { "type": ["string", "null"], "description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors." },
    "itin": { "type": ["string", "null"], "description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents." },
    "name": { "type": ["string", "null"], "description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form." },
    "address": { "type": ["string", "null"], "description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer." },
    "signature": { "type": ["string", "null"], "description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification." },
    "date_signed": { "type": ["string", "null"], "extend:type": "date", "description": "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format." },
    "business_name": { "type": ["string", "null"], "description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity." },
    "city_state_zip": { "type": ["string", "null"], "description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code." },
    "account_numbers": { "type": ["string", "null"], "description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester." },
    "exempt_payee_code": { "type": ["string", "null"], "description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt." },
    "llc_tax_classification": { "type": ["string", "null"], "description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership)." },
    "other_tax_classification": { "type": ["string", "null"], "description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided." },
    "federal_tax_classification": { "type": ["string", "null"], "description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form." },
    "requester_name_and_address": { "type": ["string", "null"], "description": "The name and address of the requester, if provided. This is optional and may be included for reference." },
    "exemption_from_fatca_reporting_code": { "type": ["string", "null"], "description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S." }
  },
  "required": ["ein", "ssn", "itin", "name", "address", "signature", "date_signed", "business_name", "city_state_zip", "account_numbers", "exempt_payee_code", "llc_tax_classification", "other_tax_classification", "federal_tax_classification", "requester_name_and_address", "exemption_from_fatca_reporting_code"],
  "additionalProperties": false
}
```

---

## Schema

The extraction schema contains **16 fields**, all nullable (tax forms may be partially filled), covering four categories:

### Taxpayer Identification (mutually exclusive, at most one should be non-null):
- **`ein`** (string | null): Employer Identification Number. Format: `XX-XXXXXXX` (9 digits). Used for corporations, S-corps, partnerships, and some sole proprietors.
- **`ssn`** (string | null): Social Security Number. Format: `
import { ExtendClient, extendDate, extendSignature } from "extend-ai";
import { z } from "zod";
import fs from "fs";

const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });

/**
 * Process a W-9 tax form: parse + extract all required fields.
 * 
 * @param filePath Local path to the W-9 PDF or image
 * @returns Extracted W-9 data as typed object, or null if processing failed
 */
export async function processW9TaxForm(filePath: string) {
  console.log(`Processing W-9 from: ${filePath}`);

  // Convert local file to data URL for SDK
  const fileBuffer = fs.readFileSync(filePath);
  const dataUrl = `data:application/octet-stream;base64,${fileBuffer.toString("base64")}`;

  // Step 1: Parse the W-9 form to markdown + bounding boxes
  console.log("Step 1: Parsing W-9 form...");
  const parseRun = await client.parseRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      blockOptions: {
        text: {
          agentic: {
            enabled: true, // Enable OCR for handwritten/faxed forms
          },
        },
      },
      chunkingStrategy: {
        type: "document", // Keep entire form as one chunk
      },
    },
  });

  if (parseRun.status !== "PROCESSED") {
    console.error(`Parse failed with status: ${parseRun.status}`);
    return null;
  }

  const parsedMarkdown = parseRun.output.chunks
    .map((chunk) => chunk.content)
    .join("\n\n");
  console.log("Parse complete. Parsed content length:", parsedMarkdown.length);

  // Step 2: Extract structured fields using Zod schema
  console.log("Step 2: Extracting W-9 fields...");
  const w9Schema = z.object({
    ein: z
      .string()
      .nullable()
      .describe(
        "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors."
      ),
    ssn: z
      .string()
      .nullable()
      .describe(
        "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors."
      ),
    itin: z
      .string()
      .nullable()
      .describe(
        "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents."
      ),
    name: z
      .string()
      .nullable()
      .describe(
        "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form."
      ),
    address: z
      .string()
      .nullable()
      .describe(
        "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer."
      ),
    signature: z
      .string()
      .nullable()
      .describe(
        "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification."
      ),
    date_signed: extendDate()
      .nullable()
      .describe(
        "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format (ISO yyyy-mm-dd)."
      ),
    business_name: z
      .string()
      .nullable()
      .describe(
        "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity."
      ),
    city_state_zip: z
      .string()
      .nullable()
      .describe(
        "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code."
      ),
    account_numbers: z
      .string()
      .nullable()
      .describe(
        "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester."
      ),
    exempt_payee_code: z
      .string()
      .nullable()
      .describe(
        "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt."
      ),
    llc_tax_classification: z
      .string()
      .nullable()
      .describe(
        "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership)."
      ),
    other_tax_classification: z
      .string()
      .nullable()
      .describe(
        "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided."
      ),
    federal_tax_classification: z
      .string()
      .nullable()
      .describe(
        "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form."
      ),
    requester_name_and_address: z
      .string()
      .nullable()
      .describe(
        "The name and address of the requester, if provided. This is optional and may be included for reference."
      ),
    exemption_from_fatca_reporting_code: z
      .string()
      .nullable()
      .describe(
        "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S."
      ),
  });

  const extractRun = await client.extractRuns.createAndPoll({
    file: { url: dataUrl },
    config: {
      schema: w9Schema,
      baseProcessor: "extraction_performance", // Accuracy-optimized for compliance
    },
  });

  if (extractRun.status !== "PROCESSED") {
    console.error(`Extraction failed with status: ${extractRun.status}`);
    return null;
  }

  const extractedData = extractRun.output.value;
  console.log("Extraction complete.");

  // Return structured W-9 data
  return {
    status: "success",
    parsed_markdown_length: parsedMarkdown.length,
    extracted_fields: extractedData,
  };
}

// Main entry point for testing
const filePath = process.argv[2] || "./sample-w9.pdf";
processW9TaxForm(filePath).then((result) => {
  if (result) {
    console.log("\n✓ W-9 Processing Result:");
    console.log(JSON.stringify(result, null, 2));
  } else {
    console.error("✗ W-9 processing failed");
    process.exit(1);
  }
});
import os
import json
import sys
from extend_ai import Extend

client = Extend(token=os.environ["EXTEND_API_KEY"])


async def process_w9_tax_form(file_path: str):
    """
    Process a W-9 tax form: parse + extract all required fields.
    
    Args:
        file_path: Local path to the W-9 PDF or image
        
    Returns:
        Extracted W-9 data as typed dict, or None if processing failed
    """
    print(f"Processing W-9 from: {file_path}")
    
    # Step 1: Parse the W-9 form to markdown + bounding boxes
    print("Step 1: Parsing W-9 form...")
    
    with open(file_path, "rb") as f:
        parse_run = client.parse_runs.create_and_poll(
            file=f,
            config={
                "blockOptions": {
                    "text": {
                        "agentic": {
                            "enabled": True,  # Enable OCR for handwritten/faxed forms
                        },
                    },
                },
                "chunkingStrategy": {
                    "type": "document",  # Keep entire form as one chunk
                },
            },
        )
    
    if parse_run.status != "PROCESSED":
        print(f"Parse failed with status: {parse_run.status}")
        return None
    
    parsed_markdown = "\n\n".join(
        chunk.get("content", "") for chunk in parse_run.output.get("chunks", [])
    )
    print("Parse complete. Parsed content length:", len(parsed_markdown))
    
    # Step 2: Extract structured fields using schema
    print("Step 2: Extracting W-9 fields...")
    
    w9_schema = {
        "type": "object",
        "properties": {
            "ein": {
                "type": ["string", "null"],
                "description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors.",
            },
            "ssn": {
                "type": ["string", "null"],
                "description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors.",
            },
            "itin": {
                "type": ["string", "null"],
                "description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents.",
            },
            "name": {
                "type": ["string", "null"],
                "description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form.",
            },
            "address": {
                "type": ["string", "null"],
                "description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer.",
            },
            "signature": {
                "type": ["string", "null"],
                "description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification.",
            },
            "date_signed": {
                "type": ["string", "null"],
                "extend:type": "date",
                "description": "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format (ISO yyyy-mm-dd).",
            },
            "business_name": {
                "type": ["string", "null"],
                "description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity.",
            },
            "city_state_zip": {
                "type": ["string", "null"],
                "description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code.",
            },
            "account_numbers": {
                "type": ["string", "null"],
                "description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester.",
            },
            "exempt_payee_code": {
                "type": ["string", "null"],
                "description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt.",
            },
            "llc_tax_classification": {
                "type": ["string", "null"],
                "description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership).",
            },
            "other_tax_classification": {
                "type": ["string", "null"],
                "description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided.",
            },
            "federal_tax_classification": {
                "type": ["string", "null"],
                "description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form.",
            },
            "requester_name_and_address": {
                "type": ["string", "null"],
                "description": "The name and address of the requester, if provided. This is optional and may be included for reference.",
            },
            "exemption_from_fatca_reporting_code": {
                "type": ["string", "null"],
                "description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S.",
            },
        },
        "required": [
            "ein",
            "ssn",
            "itin",
            "name",
            "address",
            "signature",
            "date_signed",
            "business_name",
            "city_state_zip",
            "account_numbers",
            "exempt_payee_code",
            "llc_tax_classification",
            "other_tax_classification",
            "federal_tax_classification",
            "requester_name_and_address",
            "exemption_from_fatca_reporting_code",
        ],
        "additionalProperties": False,
    }
    
    with open(file_path, "rb") as f:
        extract_run = client.extract_runs.create_and_poll(
            file=f,
            config={
                "schema": w9_schema,
                "baseProcessor": "extraction_performance",  # Accuracy-optimized for compliance
            },
        )
    
    if extract_run.status != "PROCESSED":
        print(f"Extraction failed with status: {extract_run.status}")
        return None
    
    extracted_data = extract_run.output.get("value", {})
    print("Extraction complete.")
    
    # Return structured W-9 data
    return {
        "status": "success",
        "parsed_markdown_length": len(parsed_markdown),
        "extracted_fields": extracted_data,
    }


# Main entry point for testing
if __name__ == "__main__":
    import asyncio
    
    file_path = sys.argv[1] if len(sys.argv) > 1 else "./sample-w9.pdf"
    
    result = asyncio.run(process_w9_tax_form(file_path))
    if result:
        print("\n✓ W-9 Processing Result:")
        print(json.dumps(result, indent=2))
    else:
        print("✗ W-9 processing failed")
        sys.exit(1)
// This code uses Extend's REST API directly because there is no official Java SDK.
// It calls https://api.extend.ai endpoints using only java.net.http.HttpClient (built-in, no external dependencies).

import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class W9TaxFormProcessor {
  private static final String API_BASE_URL = "https://api.extend.ai";
  private static final HttpClient httpClient = HttpClient.newHttpClient();
  private static final String apiKey = System.getenv("EXTEND_API_KEY");

  /**
   * Process a W-9 tax form: parse + extract all required fields.
   * 
   * @param filePath Local path to the W-9 PDF or image
   * @return Map containing extracted W-9 data, or null if processing failed
   */
  public static Map<String, Object> processW9TaxForm(String filePath) throws IOException, InterruptedException {
    System.out.println("Processing W-9 from: " + filePath);

    // Convert local file to data URL for API
    byte[] fileBuffer = Files.readAllBytes(Paths.get(filePath));
    String base64Content = Base64.getEncoder().encodeToString(fileBuffer);
    String dataUrl = "data:application/octet-stream;base64," + base64Content;

    // Step 1: Parse the W-9 form to markdown + bounding boxes
    System.out.println("Step 1: Parsing W-9 form...");
    Map<String, Object> parseRequest = new LinkedHashMap<>();
    parseRequest.put("file", Map.of("url", dataUrl));
    parseRequest.put("config", Map.of(
      "blockOptions", Map.of(
        "text", Map.of(
          "agentic", Map.of("enabled", true)
        )
      ),
      "chunkingStrategy", Map.of("type", "document")
    ));

    Map<String, Object> parseRun = createAndPollParseRun(parseRequest);

    if (parseRun == null || !parseRun.get("status").equals("PROCESSED")) {
      String status = parseRun != null ? (String) parseRun.get("status") : "null";
      System.err.println("Parse failed with status: " + status);
      return null;
    }

    String parsedMarkdown = extractMarkdownFromParseOutput(parseRun);
    System.out.println("Parse complete. Parsed content length: " + parsedMarkdown.length());

    // Step 2: Extract structured fields using schema
    System.out.println("Step 2: Extracting W-9 fields...");
    Map<String, Object> w9Schema = buildW9Schema();
    Map<String, Object> extractRequest = new LinkedHashMap<>();
    extractRequest.put("file", Map.of("url", dataUrl));
    extractRequest.put("config", Map.of(
      "schema", w9Schema,
      "baseProcessor", "extraction_performance"
    ));

    Map<String, Object> extractRun = createAndPollExtractRun(extractRequest);

    if (extractRun == null || !extractRun.get("status").equals("PROCESSED")) {
      String status = extractRun != null ? (String) extractRun.get("status") : "null";
      System.err.println("Extraction failed with status: " + status);
      return null;
    }

    Map<String, Object> extractedData = extractOutputValue(extractRun);
    System.out.println("Extraction complete.");

    // Return structured W-9 data
    Map<String, Object> result = new LinkedHashMap<>();
    result.put("status", "success");
    result.put("parsed_markdown_length", parsedMarkdown.length());
    result.put("extracted_fields", extractedData);
    return result;
  }

  private static Map<String, Object> createAndPollParseRun(Map<String, Object> request) throws IOException, InterruptedException {
    String runId = createParseRun(request);
    if (runId == null) return null;

    // Poll for completion
    while (true) {
      Map<String, Object> run = getParseRun(runId);
      if (run == null) return null;

      String status = (String) run.get("status");
      if (status.equals("PROCESSED") || status.equals("FAILED")) {
        return run;
      }
      Thread.sleep(2000); // Wait 2 seconds before polling again
    }
  }

  private static String createParseRun(Map<String, Object> request) throws IOException, InterruptedException {
    String jsonBody = mapToJson(request);
    HttpRequest httpRequest = HttpRequest.newBuilder()
      .uri(URI.create(API_BASE_URL + "/v1/parse-runs"))
      .header("Authorization", "Bearer " + apiKey)
      .header("Content-Type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
      .build();

    HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 201 && response.statusCode() != 200) {
      System.err.println("Failed to create parse run: " + response.statusCode());
      return null;
    }

    Map<String, Object> responseBody = jsonToMap(response.body());
    return (String) responseBody.get("id");
  }

  private static Map<String, Object> getParseRun(String runId) throws IOException, InterruptedException {
    HttpRequest httpRequest = HttpRequest.newBuilder()
      .uri(URI.create(API_BASE_URL + "/v1/parse-runs/" + runId))
      .header("Authorization", "Bearer " + apiKey)
      .GET()
      .build();

    HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 200) {
      System.err.println("Failed to get parse run: " + response.statusCode());
      return null;
    }

    return jsonToMap(response.body());
  }

  private static Map<String, Object> createAndPollExtractRun(Map<String, Object> request) throws IOException, InterruptedException {
    String runId = createExtractRun(request);
    if (runId == null) return null;

    // Poll for completion
    while (true) {
      Map<String, Object> run = getExtractRun(runId);
      if (run == null) return null;

      String status = (String) run.get("status");
      if (status.equals("PROCESSED") || status.equals("FAILED")) {
        return run;
      }
      Thread.sleep(2000); // Wait 2 seconds before polling again
    }
  }

  private static String createExtractRun(Map<String, Object> request) throws IOException, InterruptedException {
    String jsonBody = mapToJson(request);
    HttpRequest httpRequest = HttpRequest.newBuilder()
      .uri(URI.create(API_BASE_URL + "/v1/extract-runs"))
      .header("Authorization", "Bearer " + apiKey)
      .header("Content-Type", "application/json")
      .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
      .build();

    HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 201 && response.statusCode() != 200) {
      System.err.println("Failed to create extract run: " + response.statusCode());
      return null;
    }

    Map<String, Object> responseBody = jsonToMap(response.body());
    return (String) responseBody.get("id");
  }

  private static Map<String, Object> getExtractRun(String runId) throws IOException, InterruptedException {
    HttpRequest httpRequest = HttpRequest.newBuilder()
      .uri(URI.create(API_BASE_URL + "/v1/extract-runs/" + runId))
      .header("Authorization", "Bearer " + apiKey)
      .GET()
      .build();

    HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 200) {
      System.err.println("Failed to get extract run: " + response.statusCode());
      return null;
    }

    return jsonToMap(response.body());
  }

  private static String extractMarkdownFromParseOutput(Map<String, Object> parseRun) {
    Map<String, Object> output = (Map<String, Object>) parseRun.get("output");
    if (output == null) return "";

    List<Map<String, Object>> chunks = (List<Map<String, Object>>) output.get("chunks");
    if (chunks == null) return "";

    StringBuilder markdown = new StringBuilder();
    for (int i = 0; i < chunks.size(); i++) {
      if (i > 0) markdown.append("\n\n");
      String content = (String) chunks.get(i).get("content");
      if (content != null) {
        markdown.append(content);
      }
    }
    return markdown.toString();
  }

  private static Map<String, Object> extractOutputValue(Map<String, Object> extractRun) {
    Map<String, Object> output = (Map<String, Object>) extractRun.get("output");
    if (output == null) return new LinkedHashMap<>();
    return (Map<String, Object>) output.get("value");
  }

  private static Map<String, Object> buildW9Schema() {
    Map<String, Object> schema = new LinkedHashMap<>();
    schema.put("type", "object");

    Map<String, Object> properties = new LinkedHashMap<>();
    properties.put("ein", createStringProperty("The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors."));
    properties.put("ssn", createStringProperty("The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors."));
    properties.put("itin", createStringProperty("The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents."));
    properties.put("name", createStringProperty("The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form."));
    properties.put("address", createStringProperty("The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer."));
    properties.put("signature", createStringProperty("The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification."));
    properties.put("date_signed", createDateProperty("The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format (ISO yyyy-mm-dd)."));
    properties.put("business_name", createStringProperty("The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity."));
    properties.put("city_state_zip", createStringProperty("The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code."));
    properties.put("account_numbers", createStringProperty("Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester."));
    properties.put("exempt_payee_code", createStringProperty("The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt."));
    properties.put("llc_tax_classification", createStringProperty("If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership)."));
    properties.put("other_tax_classification", createStringProperty("If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided."));
    properties.put("federal_tax_classification", createStringProperty("The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form."));
    properties.put("requester_name_and_address", createStringProperty("The name and address of the requester, if provided. This is optional and may be included for reference."));
    properties.put("exemption_from_fatca_reporting_code", createStringProperty("The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S."));

    schema.put("properties", properties);
    schema.put("required", Arrays.asList(
      "ein", "ssn", "itin", "name", "address", "signature", "date_signed",
      "business_name", "city_state_zip", "account_numbers", "exempt_payee_code",
      "llc_tax_classification", "other_tax_classification", "federal_tax_classification",
      "requester_name_and_address", "exemption_from_fatca_reporting_code"
    ));
    schema.put("additionalProperties", false);

    return schema;
  }

  private static Map<String, Object> createStringProperty(String description) {
    Map<String, Object> prop = new LinkedHashMap<>();
    prop.put("type", Arrays.asList("string", "null"));
    prop.put("description", description);
    return prop;
  }

  private static Map<String, Object> createDateProperty(String description) {
    Map<String, Object> prop = new LinkedHashMap<>();
    prop.put("type", Arrays.asList("string", "null"));
    prop.put("extend:type", "date");
    prop.put("description", description);
    return prop;
  }

  // Simple JSON serialization/deserialization helpers (no external library)
  private static String mapToJson(Object obj) throws IOException {
    if (obj == null) return "null";
    if (obj instanceof String) return "\"" + escapeJson((String) obj) + "\"";
    if (obj instanceof Number) return obj.toString();
    if (obj instanceof Boolean) return obj.toString();
    if (obj instanceof Map) {
      Map<String, Object> map = (Map<String, Object>) obj;
      StringBuilder sb = new StringBuilder("{");
      boolean first = true;
      for (Map.Entry<String, Object> entry : map.entrySet()) {
        if (!first) sb.append(",");
        sb.append("\"").append(entry.getKey()).append("\":");
        sb.append(mapToJson(entry.getValue()));
        first = false;
      }
      sb.append("}");
      return sb.toString();
    }
    if (obj instanceof List) {
      List<?> list = (List<?>) obj;
      StringBuilder sb = new StringBuilder("[");
      for (int i = 0; i < list.size(); i++) {
        if (i > 0) sb.append(",");
        sb.append(mapToJson(list.get(i)));
      }
      sb.append("]");
      return sb.toString();
    }
    return "null";
  }

  private static String escapeJson(String s) {
    return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
  }

  private static Map<String, Object> jsonToMap(String json) throws IOException {
    json = json.trim();
    if (!json.startsWith("{")) throw new IOException("Expected JSON object");
    
    Map<String, Object> map = new LinkedHashMap<>();
    int depth = 0;
    int start = 1;
    String key = null;
    
    for (int i = 1; i < json.length(); i++) {
      char c = json.charAt(i);
      
      if (c == '"' && (i == 0 || json.charAt(i - 1) != '\\')) {
        if (key == null) {
          int end = i + 1;
          while (end < json.length() && !(json.charAt(end) == '"' && json.charAt(end - 1) != '\\')) end++;
          key = json.substring(i + 1, end);
          i = end;
          while (i < json.length() && json.charAt(i) != ':') i++;
          start = i + 1;
        } else {
          int end = i + 1;
          while (end < json.length() && !(json.charAt(end) == '"' && json.charAt(end - 1) != '\\')) end++;
          String value = json.substring(i + 1, end);
          if (key != null) map.put(key, value);
          key = null;
          i = end;
        }
      } else if ((c == ',' || c == '}') && depth == 0 && key != null) {
        String value = json.substring(start, i).trim();
        if (value.startsWith("\"") && value.endsWith("\"")) {
          value = value.substring(1, value.length() - 1);
        }
        map.put(key, value);
        key = null;
      } else if (c == '{' || c == '[') {
        depth++;
      } else if (c == '}' || c == ']') {
        depth--;
      }
    }
    
    return map;
  }

  public static void main(String[] args) throws IOException, InterruptedException {
    String filePath = args.length > 0 ? args[0] : "./sample-w9.pdf";
    Map<String, Object> result = processW9TaxForm(filePath);

    if (result != null) {
      System.out.println("\n✓ W-9 Processing Result:");
      System.out.println(mapToJson(result));
    } else {
      System.err.println("✗ W-9 processing failed");
      System.exit(1);
    }
  }
}
// This code calls Extend's REST API directly using only Go standard library.
// Extend does not publish an official Go SDK; this uses the same REST endpoints
// the TypeScript SDK wraps.

package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const extendAPIBase = "https://api.extend.ai"

// W9Data represents the structured extraction output for a W-9 form
type W9Data struct {
	EIN                           *string `json:"ein"`
	SSN                           *string `json:"ssn"`
	ITIN                          *string `json:"itin"`
	Name                          *string `json:"name"`
	Address                       *string `json:"address"`
	Signature                     *string `json:"signature"`
	DateSigned                    *string `json:"date_signed"`
	BusinessName                  *string `json:"business_name"`
	CityStateZip                  *string `json:"city_state_zip"`
	AccountNumbers                *string `json:"account_numbers"`
	ExemptPayeeCode               *string `json:"exempt_payee_code"`
	LLCTaxClassification          *string `json:"llc_tax_classification"`
	OtherTaxClassification        *string `json:"other_tax_classification"`
	FederalTaxClassification      *string `json:"federal_tax_classification"`
	RequesterNameAndAddress       *string `json:"requester_name_and_address"`
	ExemptionFromFATCAReportingCode *string `json:"exemption_from_fatca_reporting_code"`
}

// ProcessW9Result is the final output structure
type ProcessW9Result struct {
	Status                string  `json:"status"`
	ParsedMarkdownLength  int     `json:"parsed_markdown_length"`
	ExtractedFields       W9Data  `json:"extracted_fields"`
}

// parseRunResponse represents the API response from createAndPoll parse
type parseRunResponse struct {
	Status string `json:"status"`
	Output struct {
		Chunks []struct {
			Content string `json:"content"`
		} `json:"chunks"`
	} `json:"output"`
}

// extractRunResponse represents the API response from createAndPoll extract
type extractRunResponse struct {
	Status string `json:"status"`
	Output struct {
		Value W9Data `json:"value"`
	} `json:"output"`
}

// w9Schema is the JSON schema for extraction
var w9Schema = map[string]interface{}{
	"type": "object",
	"properties": map[string]interface{}{
		"ein": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors.",
		},
		"ssn": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors.",
		},
		"itin": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents.",
		},
		"name": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form.",
		},
		"address": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer.",
		},
		"signature": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification.",
		},
		"date_signed": map[string]interface{}{
			"type":         []string{"string", "null"},
			"extend:type":  "date",
			"description":  "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format (ISO yyyy-mm-dd).",
		},
		"business_name": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity.",
		},
		"city_state_zip": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code.",
		},
		"account_numbers": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester.",
		},
		"exempt_payee_code": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt.",
		},
		"llc_tax_classification": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership).",
		},
		"other_tax_classification": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided.",
		},
		"federal_tax_classification": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form.",
		},
		"requester_name_and_address": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The name and address of the requester, if provided. This is optional and may be included for reference.",
		},
		"exemption_from_fatca_reporting_code": map[string]interface{}{
			"type":        []string{"string", "null"},
			"description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S.",
		},
	},
	"required": []string{
		"ein", "ssn", "itin", "name", "address", "signature", "date_signed",
		"business_name", "city_state_zip", "account_numbers", "exempt_payee_code",
		"llc_tax_classification", "other_tax_classification", "federal_tax_classification",
		"requester_name_and_address", "exemption_from_fatca_reporting_code",
	},
	"additionalProperties": false,
}

// ProcessW9TaxForm processes a W-9 tax form: parse + extract all required fields.
func ProcessW9TaxForm(filePath string) (*ProcessW9Result, error) {
	apiKey := os.Getenv("EXTEND_API_KEY")
	if apiKey == "" {
		return nil, fmt.Errorf("EXTEND_API_KEY environment variable not set")
	}

	fmt.Printf("Processing W-9 from: %s\n", filePath)

	// Convert local file to data URL
	fileBuffer, err := os.ReadFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read file: %w", err)
	}
	dataURL := fmt.Sprintf("data:application/octet-stream;base64,%s", base64.StdEncoding.EncodeToString(fileBuffer))

	// Step 1: Parse the W-9 form
	fmt.Println("Step 1: Parsing W-9 form...")
	parseReq := map[string]interface{}{
		"file": map[string]string{
			"url": dataURL,
		},
		"config": map[string]interface{}{
			"blockOptions": map[string]interface{}{
				"text": map[string]interface{}{
					"agentic": map[string]bool{
						"enabled": true,
					},
				},
			},
			"chunkingStrategy": map[string]string{
				"type": "document",
			},
		},
	}

	parseRespBody, err := callExtendAPI(apiKey, "/parse-runs", parseReq)
	if err != nil {
		return nil, fmt.Errorf("parse request failed: %w", err)
	}

	var parseResp parseRunResponse
	if err := json.Unmarshal(parseRespBody, &parseResp); err != nil {
		return nil, fmt.Errorf("failed to unmarshal parse response: %w", err)
	}

	if parseResp.Status != "PROCESSED" {
		return nil, fmt.Errorf("parse failed with status: %s", parseResp.Status)
	}

	parsedMarkdown := ""
	for i, chunk := range parseResp.Output.Chunks {
		if i > 0 {
			parsedMarkdown += "\n\n"
		}
		parsedMarkdown += chunk.Content
	}
	fmt.Printf("Parse complete. Parsed content length: %d\n", len(parsedMarkdown))

	// Step 2: Extract structured fields
	fmt.Println("Step 2: Extracting W-9 fields...")
	extractReq := map[string]interface{}{
		"file": map[string]string{
			"url": dataURL,
		},
		"config": map[string]interface{}{
			"schema":        w9Schema,
			"baseProcessor": "extraction_performance",
		},
	}

	extractRespBody, err := callExtendAPI(apiKey, "/extract-runs", extractReq)
	if err != nil {
		return nil, fmt.Errorf("extraction request failed: %w", err)
	}

	var extractResp extractRunResponse
	if err := json.Unmarshal(extractRespBody, &extractResp); err != nil {
		return nil, fmt.Errorf("failed to unmarshal extraction response: %w", err)
	}

	if extractResp.Status != "PROCESSED" {
		return nil, fmt.Errorf("extraction failed with status: %s", extractResp.Status)
	}

	fmt.Println("Extraction complete.")

	return &ProcessW9Result{
		Status:               "success",
		ParsedMarkdownLength: len(parsedMarkdown),
		ExtractedFields:      extractResp.Output.Value,
	}, nil
}

// callExtendAPI makes an HTTP POST request to the Extend API with polling
func callExtendAPI(apiKey, endpoint string, payload interface{}) ([]byte, error) {
	url := extendAPIBase + endpoint

	payloadBytes, err := json.Marshal(payload)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal payload: %w", err)
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(payloadBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 120 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response body: %w", err)
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
	}

	return body, nil
}

func main() {
	filePath := "./sample-w9.pdf"
	if len(os.Args) > 1 {
		filePath = os.Args[1]
	}

	result, err := ProcessW9TaxForm(filePath)
	if err != nil {
		fmt.Printf("✗ W-9 processing failed: %v\n", err)
		os.Exit(1)
	}

	fmt.Println("\n✓ W-9 Processing Result:")
	resultJSON, _ := json.MarshalIndent(result, "", "  ")
	fmt.Println(string(resultJSON))
}
// Deploy the "W-9 Tax Form" pipeline to YOUR Extend account.
//
// The workflow below is fully self-contained — every EXTRACT/CLASSIFY/SPLIT
// step carries its extractor/classifier/splitter config INLINE, so this is a
// single API call. No processors to create or wire up beforehand.
// Idempotent: the created workflow id is cached in .extend/w-9-tax-form.json,
// so re-running updates the existing workflow instead of duplicating it.
//
// Usage:
//   export EXTEND_API_KEY=sk_...   (from https://dashboard.extend.ai → API Keys)
//   npx tsx provision.ts
//
// Generated by doc1 (template: w-9-tax-form).

import fs from "node:fs";
import path from "node:path";

const API = "https://api.extend.ai";
const VERSION = "2026-02-09";
const API_KEY = process.env.EXTEND_API_KEY;
if (!API_KEY) { console.error("Set EXTEND_API_KEY first."); process.exit(1); }

const STATE_DIR = path.join(process.cwd(), ".extend");
const STATE_FILE = path.join(STATE_DIR, "w-9-tax-form.json");

type State = { workflowId?: string };
const state: State = fs.existsSync(STATE_FILE)
  ? JSON.parse(fs.readFileSync(STATE_FILE, "utf8"))
  : {};
function saveState() {
  fs.mkdirSync(STATE_DIR, { recursive: true });
  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
}

async function api(method: string, pathName: string, body?: unknown) {
  const res = await fetch(API + pathName, {
    method,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "x-extend-api-version": VERSION,
      ...(body ? { "Content-Type": "application/json" } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(`${method} ${pathName} failed (${res.status}): ${JSON.stringify(data).slice(0, 300)}`);
  return data;
}

// ── Workflow definition — extractor/classifier/splitter configs inline ──────
const WORKFLOW = {
  "name": "W-9 Tax Form Processing Pipeline",
  "steps": [
    {
      "name": "startTrigger1",
      "type": "TRIGGER",
      "next": [
        {
          "step": "parse1"
        }
      ]
    },
    {
      "name": "parse1",
      "type": "PARSE",
      "config": {
        "parseConfig": {
          "blockOptions": {
            "text": {
              "agentic": {
                "enabled": true
              }
            }
          },
          "chunkingStrategy": {
            "type": "document"
          }
        }
      },
      "next": [
        {
          "step": "extraction2"
        }
      ]
    },
    {
      "name": "extraction2",
      "type": "EXTRACT",
      "config": {
        "extractorConfig": {
          "schema": {
            "type": "object",
            "required": [
              "ein",
              "ssn",
              "itin",
              "name",
              "address",
              "signature",
              "date_signed",
              "business_name",
              "city_state_zip",
              "account_numbers",
              "exempt_payee_code",
              "llc_tax_classification",
              "other_tax_classification",
              "federal_tax_classification",
              "requester_name_and_address",
              "exemption_from_fatca_reporting_code"
            ],
            "properties": {
              "ein": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors."
              },
              "ssn": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors."
              },
              "itin": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form."
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer."
              },
              "signature": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification."
              },
              "date_signed": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format.",
                "extend:type": "date"
              },
              "business_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity."
              },
              "city_state_zip": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code."
              },
              "account_numbers": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester."
              },
              "exempt_payee_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt."
              },
              "llc_tax_classification": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership)."
              },
              "other_tax_classification": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided."
              },
              "federal_tax_classification": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form."
              },
              "requester_name_and_address": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name and address of the requester, if provided. This is optional and may be included for reference."
              },
              "exemption_from_fatca_reporting_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S."
              }
            },
            "additionalProperties": false
          },
          "baseProcessor": "extraction_performance",
          "advancedOptions": {
            "reviewAgent": {
              "enabled": true
            },
            "advancedMultimodalEnabled": true
          }
        }
      }
    }
  ]
};

async function main() {
  console.log(`Deploying "${WORKFLOW.name}"…`);

  if (state.workflowId) {
    console.log(`✓ workflow already provisioned (${state.workflowId}) — updating steps`);
    await api("POST", `/workflows/${state.workflowId}`, { steps: WORKFLOW.steps });
  } else {
    // Reuse an existing workflow with the same name if one exists (e.g. a
    // previous run's state file was lost) instead of creating a duplicate.
    try {
      const list = await api("GET", `/workflows?name=${encodeURIComponent(WORKFLOW.name)}`);
      const items = (list.data ?? list.items ?? []) as Array<{ name?: string; id?: string }>;
      const existing = items.find((x) => x.name === WORKFLOW.name);
      if (existing?.id) {
        state.workflowId = existing.id; saveState();
        console.log(`✓ workflow "${WORKFLOW.name}" found in your account (${existing.id}) — updating steps`);
        await api("POST", `/workflows/${existing.id}`, { steps: WORKFLOW.steps });
      }
    } catch { /* lookup is best-effort; fall through to create */ }

    if (!state.workflowId) {
      const created = await api("POST", "/workflows", WORKFLOW);
      const wfId = created.id ?? created.workflow?.id;
      if (!wfId) throw new Error("Could not read created workflow id from response");
      state.workflowId = wfId; saveState();
      console.log(`+ created workflow (${wfId})`);
    }
  }

  // Deploy the current draft as a new version so the workflow is runnable —
  // best-effort: some accounts/plans may not require this explicit step.
  await api("POST", `/workflows/${state.workflowId}/versions`, {}).catch(() => {});

  console.log("\nDone. Run documents through it with:");
  console.log(`  POST ${API}/workflow_runs  { workflow: { id: "${state.workflowId}" }, file: { url: "https://…" } }`);
  console.log("Or open the workflow in the Extend dashboard to review and deploy it.");
}

main().catch((e) => { console.error(e.message ?? e); process.exit(1); });
import os
import json
import sys
from pathlib import Path
from typing import Any, Optional

from extend_ai import Extend

API_KEY = os.environ.get("EXTEND_API_KEY")
if not API_KEY:
    print("Error: Set EXTEND_API_KEY first.", file=sys.stderr)
    sys.exit(1)

STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "w-9-tax-form.json"


def load_state() -> dict[str, Any]:
    if STATE_FILE.exists():
        return json.loads(STATE_FILE.read_text())
    return {}


def save_state(state: dict[str, Any]) -> None:
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, indent=2))


WORKFLOW = {
    "name": "W-9 Tax Form Processing Pipeline",
    "steps": [
        {
            "name": "startTrigger1",
            "type": "TRIGGER",
            "next": [{"step": "parse1"}],
        },
        {
            "name": "parse1",
            "type": "PARSE",
            "config": {
                "parseConfig": {
                    "blockOptions": {"text": {"agentic": {"enabled": True}}},
                    "chunkingStrategy": {"type": "document"},
                }
            },
            "next": [{"step": "extraction2"}],
        },
        {
            "name": "extraction2",
            "type": "EXTRACT",
            "config": {
                "extractorConfig": {
                    "schema": {
                        "type": "object",
                        "required": [
                            "ein",
                            "ssn",
                            "itin",
                            "name",
                            "address",
                            "signature",
                            "date_signed",
                            "business_name",
                            "city_state_zip",
                            "account_numbers",
                            "exempt_payee_code",
                            "llc_tax_classification",
                            "other_tax_classification",
                            "federal_tax_classification",
                            "requester_name_and_address",
                            "exemption_from_fatca_reporting_code",
                        ],
                        "properties": {
                            "ein": {
                                "type": ["string", "null"],
                                "description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors.",
                            },
                            "ssn": {
                                "type": ["string", "null"],
                                "description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors.",
                            },
                            "itin": {
                                "type": ["string", "null"],
                                "description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents.",
                            },
                            "name": {
                                "type": ["string", "null"],
                                "description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form.",
                            },
                            "address": {
                                "type": ["string", "null"],
                                "description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer.",
                            },
                            "signature": {
                                "type": ["string", "null"],
                                "description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification.",
                            },
                            "date_signed": {
                                "type": ["string", "null"],
                                "description": "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format.",
                                "extend:type": "date",
                            },
                            "business_name": {
                                "type": ["string", "null"],
                                "description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity.",
                            },
                            "city_state_zip": {
                                "type": ["string", "null"],
                                "description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code.",
                            },
                            "account_numbers": {
                                "type": ["string", "null"],
                                "description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester.",
                            },
                            "exempt_payee_code": {
                                "type": ["string", "null"],
                                "description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt.",
                            },
                            "llc_tax_classification": {
                                "type": ["string", "null"],
                                "description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership).",
                            },
                            "other_tax_classification": {
                                "type": ["string", "null"],
                                "description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided.",
                            },
                            "federal_tax_classification": {
                                "type": ["string", "null"],
                                "description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form.",
                            },
                            "requester_name_and_address": {
                                "type": ["string", "null"],
                                "description": "The name and address of the requester, if provided. This is optional and may be included for reference.",
                            },
                            "exemption_from_fatca_reporting_code": {
                                "type": ["string", "null"],
                                "description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S.",
                            },
                        },
                        "additionalProperties": False,
                    },
                    "baseProcessor": "extraction_performance",
                    "advancedOptions": {
                        "reviewAgent": {"enabled": True},
                        "advancedMultimodalEnabled": True,
                    },
                }
            },
        },
    ],
}


async def main() -> None:
    client = Extend(token=API_KEY)
    state = load_state()

    print(f'Deploying "{WORKFLOW["name"]}…"')

    if state.get("workflowId"):
        workflow_id = state["workflowId"]
        print(f"✓ workflow already provisioned ({workflow_id}) — updating steps")
        await client.workflows.update(workflow_id, steps=WORKFLOW["steps"])
    else:
        try:
            workflows = await client.workflows.list(name=WORKFLOW["name"])
            items = workflows.data if hasattr(workflows, "data") else workflows.get("data", [])
            existing = next((w for w in items if w.get("name") == WORKFLOW["name"]), None)
            if existing and existing.get("id"):
                workflow_id = existing["id"]
                state["workflowId"] = workflow_id
                save_state(state)
                print(f'✓ workflow "{WORKFLOW["name"]}" found in your account ({workflow_id}) — updating steps')
                await client.workflows.update(workflow_id, steps=WORKFLOW["steps"])
            else:
                raise ValueError("No existing workflow found")
        except Exception:
            created = await client.workflows.create(**WORKFLOW)
            workflow_id = created.id if hasattr(created, "id") else created.get("id")
            if not workflow_id:
                raise ValueError("Could not read created workflow id from response")
            state["workflowId"] = workflow_id
            save_state(state)
            print(f"+ created workflow ({workflow_id})")

    try:
        await client.workflows.create_version(state["workflowId"])
    except Exception:
        pass

    print("\nDone. Run documents through it with:")
    print(f'  POST https://api.extend.ai/workflow_runs  {{ "workflow": {{ "id": "{state["workflowId"]}" }}, "file": {{ "url": "https://…" }} }}')
    print("Or open the workflow in the Extend dashboard to review and deploy it.")


if __name__ == "__main__":
    import asyncio

    try:
        asyncio.run(main())
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)
// This script uses Extend's REST API directly because Extend has no official Java SDK yet.
// It deploys the "W-9 Tax Form" pipeline to your Extend account.
//
// Usage:
//   export EXTEND_API_KEY=sk_...   (from https://dashboard.extend.ai → API Keys)
//   javac Provision.java && java Provision
//
// Generated by doc1 (template: w-9-tax-form).

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

public class Provision {
  private static final String API = "https://api.extend.ai";
  private static final String VERSION = "2026-02-09";
  private static final String API_KEY = System.getenv("EXTEND_API_KEY");
  private static final Path STATE_DIR = Paths.get(System.getProperty("user.dir"), ".extend");
  private static final Path STATE_FILE = STATE_DIR.resolve("w-9-tax-form.json");
  private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

  static class State {
    String workflowId;

    void save() throws IOException {
      Files.createDirectories(STATE_DIR);
      String json = workflowId != null
        ? String.format("{\"workflowId\":\"%s\"}", escapeJson(workflowId))
        : "{}";
      Files.writeString(STATE_FILE, json);
    }

    static State load() throws IOException {
      State s = new State();
      if (Files.exists(STATE_FILE)) {
        String content = Files.readString(STATE_FILE);
        if (content.contains("\"workflowId\"")) {
          int start = content.indexOf("\"workflowId\":\"") + 14;
          int end = content.indexOf("\"", start);
          s.workflowId = content.substring(start, end);
        }
      }
      return s;
    }
  }

  static String escapeJson(String s) {
    if (s == null) return "";
    return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
  }

  static class ApiResponse {
    String status;
    int code;
    String body;

    ApiResponse(int code, String body) {
      this.code = code;
      this.body = body;
      this.status = code >= 200 && code < 300 ? "ok" : "error";
    }

    String getString(String key) {
      int idx = body.indexOf("\"" + key + "\":\"");
      if (idx < 0) return null;
      int start = idx + key.length() + 4;
      int end = body.indexOf("\"", start);
      return end > start ? body.substring(start, end) : null;
    }

    String getNestedId() {
      String id = getString("id");
      if (id != null) return id;
      int idx = body.indexOf("\"workflow\":{\"id\":\"");
      if (idx >= 0) {
        int start = idx + 18;
        int end = body.indexOf("\"", start);
        return end > start ? body.substring(start, end) : null;
      }
      return null;
    }

    boolean isOk() {
      return status.equals("ok");
    }
  }

  static ApiResponse api(String method, String pathName, String bodyStr) throws Exception {
    URI uri = URI.create(API + pathName);
    HttpRequest.Builder builder = HttpRequest.newBuilder()
      .uri(uri)
      .method(method, bodyStr != null
        ? HttpRequest.BodyPublishers.ofString(bodyStr, StandardCharsets.UTF_8)
        : HttpRequest.BodyPublishers.noBody())
      .header("Authorization", "Bearer " + API_KEY)
      .header("x-extend-api-version", VERSION);

    if (bodyStr != null) {
      builder.header("Content-Type", "application/json");
    }

    HttpRequest request = builder.build();
    HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
    return new ApiResponse(response.statusCode(), response.body());
  }

  static String buildWorkflowJson() {
    StringBuilder sb = new StringBuilder();
    sb.append("{\"name\":\"W-9 Tax Form Processing Pipeline\",\"steps\":[");
    sb.append("{\"name\":\"startTrigger1\",\"type\":\"TRIGGER\",\"next\":[{\"step\":\"parse1\"}]},");
    sb.append("{\"name\":\"parse1\",\"type\":\"PARSE\",\"config\":{\"parseConfig\":{\"blockOptions\":{\"text\":{\"agentic\":{\"enabled\":true}}},\"chunkingStrategy\":{\"type\":\"document\"}}},\"next\":[{\"step\":\"extraction2\"}]},");
    sb.append("{\"name\":\"extraction2\",\"type\":\"EXTRACT\",\"config\":{\"extractorConfig\":{");
    sb.append("\"schema\":{\"type\":\"object\",");
    sb.append("\"required\":[\"ein\",\"ssn\",\"itin\",\"name\",\"address\",\"signature\",\"date_signed\",\"business_name\",\"city_state_zip\",\"account_numbers\",\"exempt_payee_code\",\"llc_tax_classification\",\"other_tax_classification\",\"federal_tax_classification\",\"requester_name_and_address\",\"exemption_from_fatca_reporting_code\"],");
    sb.append("\"properties\":{");
    sb.append("\"ein\":{\"type\":[\"string\",\"null\"],\"description\":\"The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors.\"},");
    sb.append("\"ssn\":{\"type\":[\"string\",\"null\"],\"description\":\"The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors.\"},");
    sb.append("\"itin\":{\"type\":[\"string\",\"null\"],\"description\":\"The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents.\"},");
    sb.append("\"name\":{\"type\":[\"string\",\"null\"],\"description\":\"The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form.\"},");
    sb.append("\"address\":{\"type\":[\"string\",\"null\"],\"description\":\"The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer.\"},");
    sb.append("\"signature\":{\"type\":[\"string\",\"null\"],\"description\":\"The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification.\"},");
    sb.append("\"date_signed\":{\"type\":[\"string\",\"null\"],\"description\":\"The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format.\",\"extend:type\":\"date\"},");
    sb.append("\"business_name\":{\"type\":[\"string\",\"null\"],\"description\":\"The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity.\"},");
    sb.append("\"city_state_zip\":{\"type\":[\"string\",\"null\"],\"description\":\"The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code.\"},");
    sb.append("\"account_numbers\":{\"type\":[\"string\",\"null\"],\"description\":\"Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester.\"},");
    sb.append("\"exempt_payee_code\":{\"type\":[\"string\",\"null\"],\"description\":\"The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt.\"},");
    sb.append("\"llc_tax_classification\":{\"type\":[\"string\",\"null\"],\"description\":\"If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership).\"},");
    sb.append("\"other_tax_classification\":{\"type\":[\"string\",\"null\"],\"description\":\"If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided.\"},");
    sb.append("\"federal_tax_classification\":{\"type\":[\"string\",\"null\"],\"description\":\"The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form.\"},");
    sb.append("\"requester_name_and_address\":{\"type\":[\"string\",\"null\"],\"description\":\"The name and address of the requester, if provided. This is optional and may be included for reference.\"},");
    sb.append("\"exemption_from_fatca_reporting_code\":{\"type\":[\"string\",\"null\"],\"description\":\"The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S.\"}");
    sb.append("},\"additionalProperties\":false},");
    sb.append("\"baseProcessor\":\"extraction_performance\",");
    sb.append("\"advancedOptions\":{\"reviewAgent\":{\"enabled\":true},\"advancedMultimodalEnabled\":true}");
    sb.append("}}}]}");
    return sb.toString();
  }

  public static void main(String[] args) throws Exception {
    if (API_KEY == null || API_KEY.isEmpty()) {
      System.err.println("Set EXTEND_API_KEY first.");
      System.exit(1);
    }

    System.out.println("Deploying \"W-9 Tax Form Processing Pipeline\"…");

    State state = State.load();
    String workflowJson = buildWorkflowJson();

    if (state.workflowId != null && !state.workflowId.isEmpty()) {
      System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
      String stepsJson = "{\"steps\":" + workflowJson.substring(workflowJson.indexOf("[\"name\":\"startTrigger1\""), workflowJson.lastIndexOf("]")) + "]}";
      api("POST", "/workflows/" + state.workflowId, stepsJson);
    } else {
      try {
        String listPath = "/workflows?name=" + URLEncoder.encode("W-9 Tax Form Processing Pipeline", StandardCharsets.UTF_8);
        ApiResponse listResp = api("GET", listPath, null);
        if (listResp.isOk()) {
          String body = listResp.body;
          int dataIdx = body.indexOf("\"data\":[");
          int itemsIdx = body.indexOf("\"items\":[");
          int arrayStart = Math.max(dataIdx, itemsIdx);
          if (arrayStart > 0) {
            int arrayEnd = body.indexOf("]", arrayStart);
            String arrayStr = body.substring(arrayStart + 8, arrayEnd);
            if (arrayStr.contains("\"name\":\"W-9 Tax Form Processing Pipeline\"")) {
              int idIdx = arrayStr.indexOf("\"id\":\"");
              if (idIdx > 0) {
                int start = idIdx + 6;
                int end = arrayStr.indexOf("\"", start);
                String foundId = arrayStr.substring(start, end);
                state.workflowId = foundId;
                state.save();
                System.out.println("✓ workflow \"W-9 Tax Form Processing Pipeline\" found in your account (" + foundId + ") — updating steps");
                String stepsJson = "{\"steps\":" + workflowJson.substring(workflowJson.indexOf("[\"name\":\"startTrigger1\""), workflowJson.lastIndexOf("]")) + "]}";
                api("POST", "/workflows/" + foundId, stepsJson);
              }
            }
          }
        }
      } catch (Exception e) {
        // lookup is best-effort; fall through to create
      }

      if (state.workflowId == null || state.workflowId.isEmpty()) {
        ApiResponse created = api("POST", "/workflows", workflowJson);
        if (!created.isOk()) {
          throw new Exception("POST /workflows failed (" + created.code + "): " + created.body.substring(0, Math.min(300, created.body.length())));
        }
        String wfId = created.getNestedId();
        if (wfId == null || wfId.isEmpty()) {
          throw new Exception("Could not read created workflow id from response");
        }
        state.workflowId = wfId;
        state.save();
        System.out.println("+ created workflow (" + wfId + ")");
      }
    }

    try {
      api("POST", "/workflows/" + state.workflowId + "/versions", "{}");
    } catch (Exception e) {
      // best-effort: some accounts/plans may not require this explicit step
    }

    System.out.println("\nDone. Run documents through it with:");
    System.out.println("  POST " + API + "/workflow_runs  { workflow: { id: \"" + state.workflowId + "\" }, file: { url: \"https://…\" } }");
    System.out.println("Or open the workflow in the Extend dashboard to review and deploy it.");
  }
}
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
)

// This script uses the Extend REST API directly because Extend has no official Go SDK yet.
// It deploys the "W-9 Tax Form" pipeline to your Extend account.

const (
	API     = "https://api.extend.ai"
	VERSION = "2026-02-09"
)

type State struct {
	WorkflowID string `json:"workflowId,omitempty"`
}

var (
	stateDir  string
	stateFile string
	state     State
)

func init() {
	cwd, _ := os.Getwd()
	stateDir = filepath.Join(cwd, ".extend")
	stateFile = filepath.Join(stateDir, "w-9-tax-form.json")

	if data, err := os.ReadFile(stateFile); err == nil {
		json.Unmarshal(data, &state)
	}
}

func saveState() error {
	if err := os.MkdirAll(stateDir, 0755); err != nil {
		return err
	}
	data, _ := json.MarshalIndent(state, "", "2")
	return os.WriteFile(stateFile, data, 0644)
}

func apiCall(method, pathName string, reqBody interface{}) (map[string]interface{}, error) {
	var body io.Reader
	if reqBody != nil {
		data, _ := json.Marshal(reqBody)
		body = bytes.NewReader(data)
	}

	req, _ := http.NewRequest(method, API+pathName, body)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("EXTEND_API_KEY")))
	req.Header.Set("x-extend-api-version", VERSION)
	if reqBody != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	respBody, _ := io.ReadAll(resp.Body)
	var data map[string]interface{}
	json.Unmarshal(respBody, &data)

	if resp.StatusCode >= 400 {
		errMsg := string(respBody)
		if len(errMsg) > 300 {
			errMsg = errMsg[:300]
		}
		return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, errMsg)
	}

	return data, nil
}

var workflow = map[string]interface{}{
	"name": "W-9 Tax Form Processing Pipeline",
	"steps": []map[string]interface{}{
		{
			"name": "startTrigger1",
			"type": "TRIGGER",
			"next": []map[string]interface{}{
				{"step": "parse1"},
			},
		},
		{
			"name": "parse1",
			"type": "PARSE",
			"config": map[string]interface{}{
				"parseConfig": map[string]interface{}{
					"blockOptions": map[string]interface{}{
						"text": map[string]interface{}{
							"agentic": map[string]interface{}{
								"enabled": true,
							},
						},
					},
					"chunkingStrategy": map[string]interface{}{
						"type": "document",
					},
				},
			},
			"next": []map[string]interface{}{
				{"step": "extraction2"},
			},
		},
		{
			"name": "extraction2",
			"type": "EXTRACT",
			"config": map[string]interface{}{
				"extractorConfig": map[string]interface{}{
					"schema": map[string]interface{}{
						"type": "object",
						"required": []string{
							"ein", "ssn", "itin", "name", "address", "signature", "date_signed",
							"business_name", "city_state_zip", "account_numbers", "exempt_payee_code",
							"llc_tax_classification", "other_tax_classification", "federal_tax_classification",
							"requester_name_and_address", "exemption_from_fatca_reporting_code",
						},
						"properties": map[string]interface{}{
							"ein": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The Employer Identification Number (EIN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XX-XXXXXXX and is used for entities such as corporations, partnerships, and some sole proprietors.",
							},
							"ssn": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The Social Security Number (SSN) entered as the taxpayer identification number, if applicable. This is a 9-digit number formatted as XXX-XX-XXXX and is used for individuals and some sole proprietors.",
							},
							"itin": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The Individual Taxpayer Identification Number (ITIN), if provided instead of an SSN. This is a 9-digit number formatted as XXX-XX-XXXX and is used for certain nonresident and resident aliens, their spouses, and dependents.",
							},
							"name": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The full legal name of the individual or entity as shown on their tax return. This is the primary name associated with the taxpayer identification number (TIN) and is required for IRS reporting. Commonly appears as the first field on the form.",
							},
							"address": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The street address, including number, street, and apartment or suite number, where information returns should be mailed. This is the primary mailing address for the taxpayer.",
							},
							"signature": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The signature of the U.S. person certifying the information on the form. This may be a handwritten or electronic signature and is required for certification.",
							},
							"date_signed": map[string]interface{}{
								"type":            []string{"string", "null"},
								"description":     "The date the form was signed and certified. This is the date the certification was completed and should be in a recognizable date format.",
								"extend:type":     "date",
							},
							"business_name": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The business, trade, or disregarded entity name if different from the name on the tax return. This field is used when the taxpayer operates under a different name or has a disregarded entity.",
							},
							"city_state_zip": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The city, state, and ZIP code corresponding to the mailing address. This field combines locality, state abbreviation, and postal code.",
							},
							"account_numbers": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "Any account numbers listed for reference, if provided. This field is optional and may include one or more account identifiers relevant to the requester.",
							},
							"exempt_payee_code": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The code indicating exemption from backup withholding, if applicable. This is typically a numeric code for certain entities and is entered if the payee is exempt.",
							},
							"llc_tax_classification": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "If 'Limited liability company' is selected as the federal tax classification, this field captures the specific tax classification entered (e.g., 'C', 'S', or 'P' for partnership).",
							},
							"other_tax_classification": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "If 'Other' is selected as the federal tax classification, this field captures the specific classification or description provided.",
							},
							"federal_tax_classification": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The federal tax classification selected for the person or entity named. Typical values include 'Individual/sole proprietor or single-member LLC', 'C Corporation', 'S Corporation', 'Partnership', 'Trust/estate', 'Limited liability company', or 'Other'. Only one classification should be selected per form.",
							},
							"requester_name_and_address": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The name and address of the requester, if provided. This is optional and may be included for reference.",
							},
							"exemption_from_fatca_reporting_code": map[string]interface{}{
								"type":        []string{"string", "null"},
								"description": "The code indicating exemption from FATCA reporting, if applicable. This is typically a letter code and is only relevant for accounts maintained outside the U.S.",
							},
						},
						"additionalProperties": false,
					},
					"baseProcessor": "extraction_performance",
					"advancedOptions": map[string]interface{}{
						"reviewAgent": map[string]interface{}{
							"enabled": true,
						},
						"advancedMultimodalEnabled": true,
					},
				},
			},
		},
	},
}

func main() {
	if os.Getenv("EXTEND_API_KEY") == "" {
		fmt.Fprintln(os.Stderr, "Set EXTEND_API_KEY first.")
		os.Exit(1)
	}

	workflowName := workflow["name"].(string)
	fmt.Printf("Deploying \"%s\"…\n", workflowName)

	steps := workflow["steps"]

	if state.WorkflowID != "" {
		fmt.Printf("✓ workflow already provisioned (%s) — updating steps\n", state.WorkflowID)
		apiCall("POST", fmt.Sprintf("/workflows/%s", state.WorkflowID), map[string]interface{}{"steps": steps})
	} else {
		// Try to find an existing workflow with the same name
		list, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", url.QueryEscape(workflowName)), nil)
		if err == nil {
			var items []map[string]interface{}
			if data, ok := list["data"].([]interface{}); ok {
				for _, item := range data {
					if m, ok := item.(map[string]interface{}); ok {
						items = append(items, m)
					}
				}
			} else if data, ok := list["items"].([]interface{}); ok {
				for _, item := range data {
					if m, ok := item.(map[string]interface{}); ok {
						items = append(items, m)
					}
				}
			}

			for _, item := range items {
				if name, ok := item["name"].(string); ok && name == workflowName {
					if id, ok := item["id"].(string); ok && id != "" {
						state.WorkflowID = id
						saveState()
						fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", workflowName, id)
						apiCall("POST", fmt.Sprintf("/workflows/%s", id), map[string]interface{}{"steps": steps})
						break
					}
				}
			}
		}

		if state.WorkflowID == "" {
			created, err := apiCall("POST", "/workflows", workflow)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error creating workflow: %v\n", err)
				os.Exit(1)
			}

			var wfID string
			if id, ok := created["id"].(string); ok {
				wfID = id
			} else if wf, ok := created["workflow"].(map[string]interface{}); ok {
				if id, ok := wf["id"].(string); ok {
					wfID = id
				}
			}

			if wfID == "" {
				fmt.Fprintln(os.Stderr, "Could not read created workflow id from response")
				os.Exit(1)
			}

			state.WorkflowID = wfID
			saveState()
			fmt.Printf("+ created workflow (%s)\n", wfID)
		}
	}

	// Deploy the current draft as a new version — best-effort
	apiCall("POST", fmt.Sprintf("/workflows/%s/versions", state.WorkflowID), map[string]interface{}{})

	fmt.Println("\nDone. Run documents through it with:")
	fmt.Printf("  POST %s/workflow_runs  { workflow: { id: \"%s\" }, file: { url: \"https://…\" } }\n", API, state.WorkflowID)
	fmt.Println("Or open the workflow in the Extend dashboard to review and deploy it.")
}

Frequently Asked Questions (FAQ)

Nuanced question and depends on the use case! For an agent pipeline, you'll likely just stop at Parsing, take the markdown/HTML output and feed that into your pipeline. For Key-Value extraction into JSON, you can jump straight into Extraction because there is always a Parse step beforehand
Set minimum confidence at 0.85 for all fields; reject or route to human review anything below that, since errors in tax ID or legal name can cause serious downstream compliance issues. Test against a sample of 20 diverse W-9 scans in your environment to validate the threshold works for your document quality.
Use async polling for batches of 5+ forms—it's more reliable and cost-effective at scale. Sync works fine for single on-demand extractions (< 10 pages), but async with `mode: "agentic_ocr"` handles handwritten fields and poor-quality scans better, making it the safer choice for production payroll pipelines.
Tags
Tax ComplianceIRS DocumentationTaxpayer IDContractor Verification
About this template

The W-9 form is a critical tax document used to collect taxpayer identification information and certification from individuals and entities. This template captures essential data including name, business classification, address, and TIN/SSN for tax reporting and backup withholding compliance. Organizations use this form to verify contractor and vendor tax information before making payments.

Document formats
  • PDF
  • Images & Scans
  • Word / DOCX
Requirements
  • Checkboxes & Strikethroughs
  • Complex layouts