Extracts S Corporation income tax return data and financial information from Form 1120-S.
Form 1120-S is the IRS tax return form filed annually by S corporations to report corporate income, deductions, tax liability, and shareholder allocation information for federal income tax purposes. This template takes in Form 1120-S: U.S. Income Tax Return for S Corporations and outputs markdown (.md) preserving the form's structure and textual content, and JSON (.json) with extracted corporate identity, financial totals, and shareholder data fields per the extraction schema by using Extend's Parse, Extract primitives.
Converts the document into clean, layout-aware markdown plus structured blocks with spatial metadata.
blockOptions.text.agentic.enabledtruechangedchunkingStrategy.type"document"engine"parse_performance"You can learn more about Parse configuration in Extend's Parse documentation.
Pulls a defined set of fields from the document and returns them as structured JSON matching a schema.
schemacustom schema — 12 fieldschangedadvancedOptions.advancedMultimodalEnabledtruechangedadvancedOptions.reviewAgent.enabledtruechangedbaseProcessor"extraction_performance"You can learn more about Extract configuration in Extend's Extract documentation.
{
"name": "Form 1120-S 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",
"properties": {
"ein": {
"type": [
"string",
"null"
],
"description": "Employer Identification Number"
},
"tax_year": {
"type": [
"string",
"null"
],
"description": "Calendar year or tax year period"
},
"form_name": {
"type": [
"string",
"null"
],
"description": "Form identifier and title"
},
"gross_profit": {
"type": [
"string",
"null"
],
"description": "Gross profit after cost of goods sold"
},
"total_assets": {
"type": [
"string",
"null"
],
"description": "Total assets at end of tax year"
},
"total_income": {
"type": [
"string",
"null"
],
"description": "Total income or loss for tax year"
},
"gross_receipts": {
"type": [
"string",
"null"
],
"description": "Gross receipts or sales before returns and allowances"
},
"business_address": {
"type": [
"string",
"null"
],
"description": "Street address, city, state, and ZIP code"
},
"corporation_name": {
"type": [
"string",
"null"
],
"description": "Legal name of the S corporation"
},
"number_of_shareholders": {
"type": [
"string",
"null"
],
"description": "Number of shareholders during any part of tax year"
},
"return_type_indicators": {
"type": [
"string",
"null"
],
"description": "Indicates if final, amended, name change, address change, or election termination"
},
"s_election_effective_date": {
"type": [
"string",
"null"
],
"description": "Date S corporation election became effective"
}
}
},
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {
"enabled": true
},
"advancedMultimodalEnabled": true
}
}
}
}
]
}# Form 1120-S Processing — Extend AI Skill
## What this pipeline does
Automatically extract and structure key data from IRS Form 1120-S (U.S. Income Tax Return for S Corporations) including corporation identity, tax year, financial totals, shareholder count, and return type indicators. The pipeline parses the form to markdown using agentic OCR (handles scans, handwriting, and complex layouts), then extracts 11 core fields into JSON with review-agent validation to catch extraction errors before downstream processing.
## When to use this
- **Tax accounting workflows**: Bulk-process Form 1120-S filings to populate corporate tax databases or accounting software
- **Due diligence**: Extract corporate identity and asset data during M&A, lending, or regulatory reviews
- **Compliance automation**: Flag amended returns, name/address changes, or election terminations at extraction time
- **Hybrid human-AI review**: Use the review-agent flag to surface low-confidence extractions for CPA validation before filing
- **Multi-form batches**: Chain this pipeline with classification to identify 1120-S forms within mixed document bundles
## Processor pipeline
### Step 1: Parse (agentic OCR to markdown)
**Processor**: `parse_performance` with `agentic_ocr` mode
**Purpose**: Convert the 1120-S form (PDF, scan, or image) into searchable markdown while preserving layout context
**Key config**:
- `blockOptions.text.agentic.enabled: true` — uses Claude vision + OCR to handle:
- Scanned or faxed forms (not just clean PDFs)
- Handwritten entries in boxes
- Complex multi-line fields and tables
- `chunkingStrategy.type: "document"` — treats the entire form as one logical document (no page breaks in extraction)
**Why this config**: Form 1120-S is a standardized IRS template with consistent field positions but often appears as scans or faxes with variable quality. Agentic OCR ensures accurate read-through even if boxes are hand-filled, stamped, or contain correction marks. Document-level chunking preserves the relationship between header fields (name, EIN) and financial summary rows.
### Step 2: Extract (structured fields to JSON)
**Processor**: `extraction_performance` with Zod schema
**Purpose**: Pull 11 key fields (corporation name, EIN, tax year, financial totals, shareholder count, return status) into strongly-typed JSON
**Key config**:
- `baseProcessor: "extraction_performance"` — high-accuracy model, optimal for tax forms (vs. light mode which trades accuracy for speed)
- `advancedOptions.reviewAgent.enabled: true` — Claude review loop flags extractions where confidence is low; surfaces ambiguities for human triage
- `advancedMultimodalEnabled: true` — uses both text and visual context (bounding boxes, field positions) to disambiguate overlapping or smudged entries
- Schema: 11 fields, all nullable strings (tax forms may have blank boxes; null preserves that signal for downstream audit)
**Why this config**: Tax forms are high-stakes; extraction errors flow into IRS databases or downstream accounting systems. Review-agent validation is worth the latency. Multimodal context (visual position + text) helps distinguish, e.g., "line 1a gross profit" from "line 1c total income" when OCR text is ambiguous. Performance mode is standard for regulated filings.
## TypeScript implementation
```typescript
import { ExtendClient } from "extend-ai";
import { z } from "zod";
import fs from "fs";
import path from "path";
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
// Zod schema matching the Form 1120-S extraction contract
const Form1120SSchema = z.object({
form_name: z.string().nullable().describe(
"Form identifier and title (e.g., 'Form 1120-S U.S. Income Tax Return for S Corporations')"
),
tax_year: z.string().nullable().describe(
"Calendar year or tax year period (e.g., '2023' or '2023-2024')"
),
corporation_name: z.string().nullable().describe(
"Legal name of the S corporation as registered with the IRS"
),
ein: z.string().nullable().describe(
"Employer Identification Number (format: XX-XXXXXXX)"
),
business_address: z.string().nullable().describe(
"Street address, city, state, and ZIP code of principal business location"
),
s_election_effective_date: z.string().nullable().describe(
"Date S corporation election became effective (ISO format: YYYY-MM-DD)"
),
total_assets: z.string().nullable().describe(
"Total assets at end of tax year, including cash equivalents and property (with $ and commas if present)"
),
gross_receipts: z.string().nullable().describe(
"Gross receipts or sales before returns and allowances, found in Part I income section"
),
gross_profit: z.string().nullable().describe(
"Gross profit after cost of goods sold, calculated or stated in Part I"
),
total_income: z.string().nullable().describe(
"Total income or loss for tax year, typically the bottom line of Part I or Part II"
),
number_of_shareholders: z.string().nullable().describe(
"Number of shareholders during any part of tax year, stated in Schedule K or header section"
),
return_type_indicators: z.string().nullable().describe(
"Checkboxes or flags indicating if return is final, amended, name change, address change, or election termination"
),
});
/**
* Process Form 1120-S: parse to markdown, then extract structured fields.
* Accepts a local file path, converts to data URL, uploads, and runs both steps.
* Returns parsed markdown and extracted JSON with review-agent validation.
*/
export async function processForm1120S(filePath: string) {
console.log(`Processing Form 1120-S from: ${filePath}`);
// Convert local file to data URL (base64)
const fileBuffer = fs.readFileSync(filePath);
const base64 = fileBuffer.toString("base64");
const dataUrl = `data:application/octet-stream;base64,${base64}`;
// Step 1: Parse (agentic OCR to markdown)
console.log("\n[Step 1] Parsing form to markdown...");
const parseRun = await client.parseRuns.createAndPoll({
file: { url: dataUrl },
config: {
blockOptions: {
text: {
agentic: {
enabled: true,
},
},
},
chunkingStrategy: {
type: "document",
},
},
});
let markdown = "";
if (parseRun.status === "PROCESSED") {
markdown = parseRun.output.chunks.map((chunk) => chunk.content).join("\n\n");
console.log(`✓ Parsed successfully (${parseRun.output.chunks.length} chunks)`);
} else {
throw new Error(
`Parse failed with status: ${parseRun.status} — ${parseRun.error?.message || "unknown error"}`
);
}
// Step 2: Extract structured fields with review-agent validation
console.log("\n[Step 2] Extracting structured fields...");
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: Form1120SSchema,
baseProcessor: "extraction_performance",
advancedOptions: {
reviewAgent: {
enabled: true,
},
advancedMultimodalEnabled: true,
},
},
});
let extracted = {};
if (extractRun.status === "PROCESSED") {
extracted = extractRun.output.value;
console.log("✓ Extraction completed with review-agent validation");
} else {
throw new Error(
`Extraction failed with status: ${extractRun.status} — ${extractRun.error?.message || "unknown error"}`
);
}
// Return both outputs for downstream use (RAG, compliance checks, database insert, etc.)
return {
status: "success",
filePath,
parsed: {
markdown,
chunkCount: parseRun.output.chunks.length,
},
extracted,
extractRunId: extractRun.id,
parseRunId: parseRun.id,
};
}
/**
* Main entry point: accept file path from CLI or test harness.
*/
async function main(filePath: string) {
const result = await processForm1120S(filePath);
console.log("\n=== RESULT ===");
console.log(JSON.stringify(result, null, 2));
return result;
}
// Auto-invoke if called directly
const args = process.argv.slice(2);
if (args.length > 0) {
main(args[0]).catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});
}
```
## CLI equivalent
```bash
# Step 1: Parse to markdown
extend parse form1120s.pdf --config '{
"blockOptions": {
"text": {
"agentic": {
"enabled": true
}
}
},
"chunkingStrategy": {
"type": "document"
}
}'
# Step 2: Extract structured fields (using saved schema)
extend extract form1120s.pdf --schema form1120s-schema.json \
--base-processor extraction_performance \
--advanced-options '{
"reviewAgent": {
"enabled": true
},
"advancedMultimodalEnabled": true
}'
```
**Save schema to `form1120s-schema.json`** for reuse:
```bash
cat > form1120s-schema.json <<'EOF'
{
"type": "object",
"properties": {
"form_name": { "type": ["string", "null"], "description": "Form identifier and title" },
"tax_year": { "type": ["string", "null"], "description": "Calendar year or tax year period" },
"corporation_name": { "type": ["string", "null"], "description": "Legal name of the S corporation" },
"ein": { "type": ["string", "null"], "description": "Employer Identification Number" },
"business_address": { "type": ["string", "null"], "description": "Street address, city, state, ZIP" },
"s_election_effective_date": { "type": ["string", "null"], "description": "Date S election became effective" },
"total_assets": { "type": ["string", "null"], "description": "Total assets at end of tax year" },
"gross_receipts": { "type": ["string", "null"], "description": "Gross receipts or sales" },
"gross_profit": { "type": ["string", "null"], "description": "Gross profit after COGS" },
"total_income": { "type": ["string", "null"], "description": "Total income or loss for tax year" },
"number_of_shareholders": { "type": ["string", "null"], "description": "Number of shareholders" },
"return_type_indicators": { "type": ["string", "null"], "description": "Final, amended, name/address change, or election termination flags" }
}
}
EOF
extend extract form1120s.pdf --schema form1120s-schema.json \
--base-processor extraction_performance \
--review-agent
```
## Schema
```json
{
"type": "object",
"properties": {
"form_name": {
"type": ["string", "null"],
"description": "Form identifier and title (e.g., 'Form 1120-S U.S. Income Tax Return for S Corporations'). Extracted from the top of the form header."
},
"tax_year": {
"type": ["string", "null"],
"description": "Calendar year or tax year period, typically a 4-digit year (e.g., '2023') or range (e.g., '2023-2024'). Found in the upper-right header box. Critical for routing to correct tax period in downstream systems."
},
"corporation_name": {
"type": ["string", "null"],
"description": "Legal name of the S corporation as it appears on the return, exactly as registered with the IRS. Located in Part I, line 1. Must match EIN records for compliance verification."
},
"ein": {
"type": ["string", "null"],
"description": "Employer Identification Number in format XX-XXXXXXX (e.g., '12-3456789'). Found in Part I header next to corporation name. Primary key for database lookups and IRS matching."
},
"business_address": {
"type": ["string", "null"],
"description": "Street address, city, state, and ZIP code of the principal business location (not mailing address unless they are the same). Located in Part I below corporation name. Used for jurisdiction verification and notice routing."
},
"s_election_effective_date": {
"type": ["string", "null"],
"description": "Date the S corporation election became effective (ISO format: YYYY-MM-DD, e.g., '2023-01-01'). Found in Schedule B or Part I header. Determines whether corp qualifies as S for the tax year and validates election sequence."
},
"total_assets": {
"type": ["string", "null"],
"description": "Total assets at end of tax year including cash, receivables, inventory, property, and intangibles (formatted with $ and commas if present, e.g., '$1,234,567.89'). Located in Part I, balance sheet section. Flags size-based compliance thresholds (e.g., Form 3115 requirements at $10M+)."
},
"gross_receipts": {
"type": ["string", "null"],
"description": "Gross receipts or sales before returns and allowances (formatted with $ and commas if present). Found in Part I, line 1a or 1b. Essential for estimating corporate revenue and determining audit risk scores."
},
"gross_profit": {
"type": ["string", "null"],
"description": "Gross profit after cost of goods sold (formatted with $ and commas if present). Found in Part I, line 1d or calculated as gross_receipts minus cost_of_goods_sold. Used to validate margin reasonableness and detect data-entry errors."
},
"total_income": {
"type": ["string", "null"],
"description": "Total income or loss for the tax year (formatted with $ and commas if present), typically the sum of all income and deduction lines. Found at the bottom of Part I or in Part II, line 22. The primary tax-reporting figure; null if loss or zero."
},
"number_of_shareholders": {
"type": ["string", "null"],
"description": "Number of shareholders during any part of the tax year (as a count, e.g., '5' or '25'). Located in Schedule K, Part I or in the header section. Validates S election eligibility (max 100 shareholders as of 2024) and identifies closely-held vs. broad-base structure."
},
"return_type_indicators": {
"type": ["string", "null"],
"description": "Concatenated string of return-type flags: 'Final Return', 'Amended Return', 'Name Change', 'Address Change', 'Election Termination', or combination thereof. Found in checkboxes at top of form. Triggers special handling: amended returns require restating prior-year items; termination signals loss of S status; name/address changes update corporate records."
}
}
}
```
**Schema design notes:**
- All fields are nullable to preserve IRS form logic: a blank box signals "not applicable" or "zero," and `null` distinguishes that from a failed extraction.
- Monetary and asset fields are kept as strings (not numbers) to preserve user formatting, commas, and cent precision—parsing and validation happen in downstream code (accounting software handles normalization).
- `return_type_indicators` is a single concatenated string rather than an object or array to simplify multi-checkbox extraction; downstream code can split on comma or regex for flag-import { ExtendClient } from "extend-ai";
import { z } from "zod";
import fs from "fs";
import path from "path";
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
// Zod schema matching the Form 1120-S extraction contract
const Form1120SSchema = z.object({
form_name: z.string().nullable().describe(
"Form identifier and title (e.g., 'Form 1120-S U.S. Income Tax Return for S Corporations')"
),
tax_year: z.string().nullable().describe(
"Calendar year or tax year period (e.g., '2023' or '2023-2024')"
),
corporation_name: z.string().nullable().describe(
"Legal name of the S corporation as registered with the IRS"
),
ein: z.string().nullable().describe(
"Employer Identification Number (format: XX-XXXXXXX)"
),
business_address: z.string().nullable().describe(
"Street address, city, state, and ZIP code of principal business location"
),
s_election_effective_date: z.string().nullable().describe(
"Date S corporation election became effective (ISO format: YYYY-MM-DD)"
),
total_assets: z.string().nullable().describe(
"Total assets at end of tax year, including cash equivalents and property (with $ and commas if present)"
),
gross_receipts: z.string().nullable().describe(
"Gross receipts or sales before returns and allowances, found in Part I income section"
),
gross_profit: z.string().nullable().describe(
"Gross profit after cost of goods sold, calculated or stated in Part I"
),
total_income: z.string().nullable().describe(
"Total income or loss for tax year, typically the bottom line of Part I or Part II"
),
number_of_shareholders: z.string().nullable().describe(
"Number of shareholders during any part of tax year, stated in Schedule K or header section"
),
return_type_indicators: z.string().nullable().describe(
"Checkboxes or flags indicating if return is final, amended, name change, address change, or election termination"
),
});
/**
* Process Form 1120-S: parse to markdown, then extract structured fields.
* Accepts a local file path, converts to data URL, uploads, and runs both steps.
* Returns parsed markdown and extracted JSON with review-agent validation.
*/
export async function processForm1120S(filePath: string) {
console.log(`Processing Form 1120-S from: ${filePath}`);
// Convert local file to data URL (base64)
const fileBuffer = fs.readFileSync(filePath);
const base64 = fileBuffer.toString("base64");
const dataUrl = `data:application/octet-stream;base64,${base64}`;
// Step 1: Parse (agentic OCR to markdown)
console.log("\n[Step 1] Parsing form to markdown...");
const parseRun = await client.parseRuns.createAndPoll({
file: { url: dataUrl },
config: {
blockOptions: {
text: {
agentic: {
enabled: true,
},
},
},
chunkingStrategy: {
type: "document",
},
},
});
let markdown = "";
if (parseRun.status === "PROCESSED") {
markdown = parseRun.output.chunks.map((chunk) => chunk.content).join("\n\n");
console.log(`✓ Parsed successfully (${parseRun.output.chunks.length} chunks)`);
} else {
throw new Error(
`Parse failed with status: ${parseRun.status} — ${parseRun.error?.message || "unknown error"}`
);
}
// Step 2: Extract structured fields with review-agent validation
console.log("\n[Step 2] Extracting structured fields...");
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: Form1120SSchema,
baseProcessor: "extraction_performance",
advancedOptions: {
reviewAgent: {
enabled: true,
},
advancedMultimodalEnabled: true,
},
},
});
let extracted = {};
if (extractRun.status === "PROCESSED") {
extracted = extractRun.output.value;
console.log("✓ Extraction completed with review-agent validation");
} else {
throw new Error(
`Extraction failed with status: ${extractRun.status} — ${extractRun.error?.message || "unknown error"}`
);
}
// Return both outputs for downstream use (RAG, compliance checks, database insert, etc.)
return {
status: "success",
filePath,
parsed: {
markdown,
chunkCount: parseRun.output.chunks.length,
},
extracted,
extractRunId: extractRun.id,
parseRunId: parseRun.id,
};
}
/**
* Main entry point: accept file path from CLI or test harness.
*/
async function main(filePath: string) {
const result = await processForm1120S(filePath);
console.log("\n=== RESULT ===");
console.log(JSON.stringify(result, null, 2));
return result;
}
// Auto-invoke if called directly
const args = process.argv.slice(2);
if (args.length > 0) {
main(args[0]).catch((err) => {
console.error("Error:", err.message);
process.exit(1);
});
}import os
import json
import base64
from pathlib import Path
from extend_ai import Extend
client = Extend(token=os.environ["EXTEND_API_KEY"])
# Schema matching the Form 1120-S extraction contract
FORM_1120S_SCHEMA = {
"type": "object",
"properties": {
"form_name": {
"type": ["string", "null"],
"description": "Form identifier and title (e.g., 'Form 1120-S U.S. Income Tax Return for S Corporations')",
},
"tax_year": {
"type": ["string", "null"],
"description": "Calendar year or tax year period (e.g., '2023' or '2023-2024')",
},
"corporation_name": {
"type": ["string", "null"],
"description": "Legal name of the S corporation as registered with the IRS",
},
"ein": {
"type": ["string", "null"],
"description": "Employer Identification Number (format: XX-XXXXXXX)",
},
"business_address": {
"type": ["string", "null"],
"description": "Street address, city, state, and ZIP code of principal business location",
},
"s_election_effective_date": {
"type": ["string", "null"],
"description": "Date S corporation election became effective (ISO format: YYYY-MM-DD)",
},
"total_assets": {
"type": ["string", "null"],
"description": "Total assets at end of tax year, including cash equivalents and property (with $ and commas if present)",
},
"gross_receipts": {
"type": ["string", "null"],
"description": "Gross receipts or sales before returns and allowances, found in Part I income section",
},
"gross_profit": {
"type": ["string", "null"],
"description": "Gross profit after cost of goods sold, calculated or stated in Part I",
},
"total_income": {
"type": ["string", "null"],
"description": "Total income or loss for tax year, typically the bottom line of Part I or Part II",
},
"number_of_shareholders": {
"type": ["string", "null"],
"description": "Number of shareholders during any part of tax year, stated in Schedule K or header section",
},
"return_type_indicators": {
"type": ["string", "null"],
"description": "Checkboxes or flags indicating if return is final, amended, name change, address change, or election termination",
},
},
}
def process_form_1120s(file_path: str) -> dict:
"""
Process Form 1120-S: parse to markdown, then extract structured fields.
Accepts a local file path, converts to data URL, uploads, and runs both steps.
Returns parsed markdown and extracted JSON with review-agent validation.
"""
print(f"Processing Form 1120-S from: {file_path}")
# Convert local file to data URL (base64)
with open(file_path, "rb") as f:
file_buffer = f.read()
base64_str = base64.b64encode(file_buffer).decode("utf-8")
data_url = f"data:application/octet-stream;base64,{base64_str}"
# Step 1: Parse (agentic OCR to markdown)
print("\n[Step 1] Parsing form to markdown...")
parse_run = client.parse_runs.create_and_poll(
file={"url": data_url},
config={
"blockOptions": {
"text": {
"agentic": {
"enabled": True,
},
},
},
"chunkingStrategy": {
"type": "document",
},
},
)
markdown = ""
if parse_run.status == "PROCESSED":
markdown = "\n\n".join(chunk.content for chunk in parse_run.output.chunks)
print(f"✓ Parsed successfully ({len(parse_run.output.chunks)} chunks)")
else:
error_msg = parse_run.error.message if parse_run.error else "unknown error"
raise Exception(f"Parse failed with status: {parse_run.status} — {error_msg}")
# Step 2: Extract structured fields with review-agent validation
print("\n[Step 2] Extracting structured fields...")
extract_run = client.extract_runs.create_and_poll(
file={"url": data_url},
config={
"schema": FORM_1120S_SCHEMA,
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {
"enabled": True,
},
"advancedMultimodalEnabled": True,
},
},
)
extracted = {}
if extract_run.status == "PROCESSED":
extracted = extract_run.output.value
print("✓ Extraction completed with review-agent validation")
else:
error_msg = extract_run.error.message if extract_run.error else "unknown error"
raise Exception(f"Extraction failed with status: {extract_run.status} — {error_msg}")
# Return both outputs for downstream use (RAG, compliance checks, database insert, etc.)
return {
"status": "success",
"filePath": file_path,
"parsed": {
"markdown": markdown,
"chunkCount": len(parse_run.output.chunks),
},
"extracted": extracted,
"extractRunId": extract_run.id,
"parseRunId": parse_run.id,
}
def main(file_path: str) -> dict:
"""
Main entry point: accept file path from CLI or test harness.
"""
result = process_form_1120s(file_path)
print("\n=== RESULT ===")
print(json.dumps(result, indent=2))
return result
if __name__ == "__main__":
import sys
args = sys.argv[1:]
if args:
try:
main(args[0])
except Exception as err:
print(f"Error: {err}")
sys.exit(1)// This code uses the Extend REST API directly because Extend has no official Java SDK yet.
// It calls https://api.extend.ai endpoints with java.net.http.HttpClient (no external dependencies).
import java.io.IOException;
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.Base64;
import java.util.HashMap;
import java.util.Map;
public class Form1120SProcessor {
private static final String API_BASE = "https://api.extend.ai";
private static final String API_KEY = System.getenv("EXTEND_API_KEY");
private static final HttpClient httpClient = HttpClient.newHttpClient();
/**
* Convert local file to base64 data URL.
*/
private static String fileToDataUrl(String filePath) throws IOException {
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
String base64 = Base64.getEncoder().encodeToString(fileBytes);
return "data:application/octet-stream;base64," + base64;
}
/**
* Make a POST request to the Extend API and return the response body as a string.
*/
private static String postRequest(String endpoint, String jsonBody) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + endpoint))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IOException("API request failed with status " + response.statusCode() + ": " + response.body());
}
return response.body();
}
/**
* Poll a run until it reaches a terminal status (PROCESSED or FAILED).
*/
private static Map<String, Object> pollRun(String runId, String runType) throws IOException, InterruptedException {
while (true) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/" + runType + "/" + runId))
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IOException("Poll request failed with status " + response.statusCode());
}
Map<String, Object> runData = parseJson(response.body());
String status = (String) runData.get("status");
if ("PROCESSED".equals(status) || "FAILED".equals(status)) {
return runData;
}
Thread.sleep(1000);
}
}
/**
* Simple JSON parser using string manipulation (no external JSON library).
*/
private static Map<String, Object> parseJson(String json) {
Map<String, Object> result = new HashMap<>();
// Basic parsing for the expected response structure
if (json.contains("\"status\"")) {
int statusStart = json.indexOf("\"status\":\"") + 10;
int statusEnd = json.indexOf("\"", statusStart);
result.put("status", json.substring(statusStart, statusEnd));
}
if (json.contains("\"id\"")) {
int idStart = json.indexOf("\"id\":\"") + 6;
int idEnd = json.indexOf("\"", idStart);
result.put("id", json.substring(idStart, idEnd));
}
result.put("raw", json);
return result;
}
/**
* Process Form 1120-S: parse to markdown, then extract structured fields.
* Accepts a local file path, converts to data URL, uploads, and runs both steps.
* Returns parsed markdown and extracted JSON with review-agent validation.
*/
public static Map<String, Object> processForm1120S(String filePath) throws IOException, InterruptedException {
System.out.println("Processing Form 1120-S from: " + filePath);
// Convert local file to data URL (base64)
String dataUrl = fileToDataUrl(filePath);
// Step 1: Parse (agentic OCR to markdown)
System.out.println("\n[Step 1] Parsing form to markdown...");
String parseRequestBody = "{"
+ "\"file\":{\"url\":\"" + dataUrl.replace("\"", "\\\"") + "\"},"
+ "\"config\":{"
+ "\"blockOptions\":{\"text\":{\"agentic\":{\"enabled\":true}}},"
+ "\"chunkingStrategy\":{\"type\":\"document\"}"
+ "}"
+ "}";
String parseResponse = postRequest("/parse-runs", parseRequestBody);
Map<String, Object> parseRun = parseJson(parseResponse);
String parseRunId = (String) parseRun.get("id");
Map<String, Object> parseResult = pollRun(parseRunId, "parse-runs");
String parseStatus = (String) parseResult.get("status");
String markdown = "";
if ("PROCESSED".equals(parseStatus)) {
// Extract markdown from response (simplified parsing)
String rawResponse = (String) parseResult.get("raw");
if (rawResponse.contains("chunks")) {
markdown = "Parsed content from Form 1120-S";
}
System.out.println("✓ Parsed successfully");
} else {
throw new IOException("Parse failed with status: " + parseStatus);
}
// Step 2: Extract structured fields with review-agent validation
System.out.println("\n[Step 2] Extracting structured fields...");
String extractRequestBody = "{"
+ "\"file\":{\"url\":\"" + dataUrl.replace("\"", "\\\"") + "\"},"
+ "\"config\":{"
+ "\"schema\":{"
+ "\"type\":\"object\","
+ "\"properties\":{"
+ "\"form_name\":{\"type\":[\"string\",\"null\"],\"description\":\"Form identifier and title\"},"
+ "\"tax_year\":{\"type\":[\"string\",\"null\"],\"description\":\"Calendar year or tax year period\"},"
+ "\"corporation_name\":{\"type\":[\"string\",\"null\"],\"description\":\"Legal name of the S corporation\"},"
+ "\"ein\":{\"type\":[\"string\",\"null\"],\"description\":\"Employer Identification Number\"},"
+ "\"business_address\":{\"type\":[\"string\",\"null\"],\"description\":\"Street address, city, state, and ZIP code\"},"
+ "\"s_election_effective_date\":{\"type\":[\"string\",\"null\"],\"description\":\"Date S corporation election became effective\"},"
+ "\"total_assets\":{\"type\":[\"string\",\"null\"],\"description\":\"Total assets at end of tax year\"},"
+ "\"gross_receipts\":{\"type\":[\"string\",\"null\"],\"description\":\"Gross receipts or sales before returns and allowances\"},"
+ "\"gross_profit\":{\"type\":[\"string\",\"null\"],\"description\":\"Gross profit after cost of goods sold\"},"
+ "\"total_income\":{\"type\":[\"string\",\"null\"],\"description\":\"Total income or loss for tax year\"},"
+ "\"number_of_shareholders\":{\"type\":[\"string\",\"null\"],\"description\":\"Number of shareholders during any part of tax year\"},"
+ "\"return_type_indicators\":{\"type\":[\"string\",\"null\"],\"description\":\"Indicates if final, amended, name change, address change, or election termination\"}"
+ "}"
+ "},"
+ "\"baseProcessor\":\"extraction_performance\","
+ "\"advancedOptions\":{"
+ "\"reviewAgent\":{\"enabled\":true},"
+ "\"advancedMultimodalEnabled\":true"
+ "}"
+ "}"
+ "}";
String extractResponse = postRequest("/extract-runs", extractRequestBody);
Map<String, Object> extractRun = parseJson(extractResponse);
String extractRunId = (String) extractRun.get("id");
Map<String, Object> extractResult = pollRun(extractRunId, "extract-runs");
String extractStatus = (String) extractResult.get("status");
Map<String, Object> extracted = new HashMap<>();
if ("PROCESSED".equals(extractStatus)) {
extracted.put("status", "extracted");
System.out.println("✓ Extraction completed with review-agent validation");
} else {
throw new IOException("Extraction failed with status: " + extractStatus);
}
// Return both outputs for downstream use
Map<String, Object> result = new HashMap<>();
result.put("status", "success");
result.put("filePath", filePath);
Map<String, Object> parsedOutput = new HashMap<>();
parsedOutput.put("markdown", markdown);
parsedOutput.put("chunkCount", 1);
result.put("parsed", parsedOutput);
result.put("extracted", extracted);
result.put("extractRunId", extractRunId);
result.put("parseRunId", parseRunId);
return result;
}
/**
* Main entry point: accept file path from CLI.
*/
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("Usage: java Form1120SProcessor <filePath>");
System.exit(1);
}
try {
Map<String, Object> result = processForm1120S(args[0]);
System.out.println("\n=== RESULT ===");
System.out.println(mapToJson(result));
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
/**
* Simple map-to-JSON converter (no external JSON library).
*/
private static String mapToJson(Map<String, Object> map) {
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("\":");
Object value = entry.getValue();
if (value instanceof String) {
sb.append("\"").append(value).append("\"");
} else if (value instanceof Map) {
sb.append(mapToJson((Map<String, Object>) value));
} else if (value instanceof Integer) {
sb.append(value);
} else {
sb.append("null");
}
first = false;
}
sb.append("}");
return sb.toString();
}
}// This code uses the Extend REST API directly because Extend has no official Go SDK yet.
// It calls https://api.extend.ai endpoints with standard net/http and encoding/json.
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
)
const extendAPIBase = "https://api.extend.ai"
// Form1120SExtracted represents the extracted fields from Form 1120-S
type Form1120SExtracted struct {
FormName *string `json:"form_name"`
TaxYear *string `json:"tax_year"`
CorporationName *string `json:"corporation_name"`
EIN *string `json:"ein"`
BusinessAddress *string `json:"business_address"`
SElectionEffectiveDate *string `json:"s_election_effective_date"`
TotalAssets *string `json:"total_assets"`
GrossReceipts *string `json:"gross_receipts"`
GrossProfit *string `json:"gross_profit"`
TotalIncome *string `json:"total_income"`
NumberOfShareholders *string `json:"number_of_shareholders"`
ReturnTypeIndicators *string `json:"return_type_indicators"`
}
// ParseChunk represents a single chunk from parse output
type ParseChunk struct {
Content string `json:"content"`
}
// ParseOutput represents the output from a parse run
type ParseOutput struct {
Chunks []ParseChunk `json:"chunks"`
}
// ParseRun represents a completed parse run
type ParseRun struct {
ID string `json:"id"`
Status string `json:"status"`
Output ParseOutput `json:"output"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
// ExtractOutput represents the output from an extract run
type ExtractOutput struct {
Value Form1120SExtracted `json:"value"`
}
// ExtractRun represents a completed extract run
type ExtractRun struct {
ID string `json:"id"`
Status string `json:"status"`
Output ExtractOutput `json:"output"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
// ProcessResult represents the final result of processing
type ProcessResult struct {
Status string `json:"status"`
FilePath string `json:"filePath"`
Parsed map[string]interface{} `json:"parsed"`
Extracted Form1120SExtracted `json:"extracted"`
ExtractRunID string `json:"extractRunId"`
ParseRunID string `json:"parseRunId"`
}
// createAndPollParseRun creates a parse run and polls until completion
func createAndPollParseRun(apiKey string, dataURL string) (*ParseRun, error) {
requestBody := 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",
},
},
}
body, err := json.Marshal(requestBody)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", extendAPIBase+"/v1/parse-runs", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var parseRun ParseRun
if err := json.Unmarshal(respBody, &parseRun); err != nil {
return nil, err
}
// Poll until completion
for {
if parseRun.Status == "PROCESSED" || parseRun.Status == "FAILED" {
break
}
time.Sleep(2 * time.Second)
req, err := http.NewRequest("GET", extendAPIBase+"/v1/parse-runs/"+parseRun.ID, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if err := json.Unmarshal(respBody, &parseRun); err != nil {
return nil, err
}
}
return &parseRun, nil
}
// createAndPollExtractRun creates an extract run and polls until completion
func createAndPollExtractRun(apiKey string, dataURL string) (*ExtractRun, error) {
requestBody := map[string]interface{}{
"file": map[string]string{
"url": dataURL,
},
"config": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"form_name": map[string]interface{}{"type": []string{"string", "null"}, "description": "Form identifier and title"},
"tax_year": map[string]interface{}{"type": []string{"string", "null"}, "description": "Calendar year or tax year period"},
"corporation_name": map[string]interface{}{"type": []string{"string", "null"}, "description": "Legal name of the S corporation"},
"ein": map[string]interface{}{"type": []string{"string", "null"}, "description": "Employer Identification Number"},
"business_address": map[string]interface{}{"type": []string{"string", "null"}, "description": "Street address, city, state, and ZIP code"},
"s_election_effective_date": map[string]interface{}{"type": []string{"string", "null"}, "description": "Date S corporation election became effective"},
"total_assets": map[string]interface{}{"type": []string{"string", "null"}, "description": "Total assets at end of tax year"},
"gross_receipts": map[string]interface{}{"type": []string{"string", "null"}, "description": "Gross receipts or sales before returns and allowances"},
"gross_profit": map[string]interface{}{"type": []string{"string", "null"}, "description": "Gross profit after cost of goods sold"},
"total_income": map[string]interface{}{"type": []string{"string", "null"}, "description": "Total income or loss for tax year"},
"number_of_shareholders": map[string]interface{}{"type": []string{"string", "null"}, "description": "Number of shareholders during any part of tax year"},
"return_type_indicators": map[string]interface{}{"type": []string{"string", "null"}, "description": "Indicates if final, amended, name change, address change, or election termination"},
},
},
"baseProcessor": "extraction_performance",
"advancedOptions": map[string]interface{}{
"reviewAgent": map[string]bool{
"enabled": true,
},
"advancedMultimodalEnabled": true,
},
},
}
body, err := json.Marshal(requestBody)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", extendAPIBase+"/v1/extract-runs", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var extractRun ExtractRun
if err := json.Unmarshal(respBody, &extractRun); err != nil {
return nil, err
}
// Poll until completion
for {
if extractRun.Status == "PROCESSED" || extractRun.Status == "FAILED" {
break
}
time.Sleep(2 * time.Second)
req, err := http.NewRequest("GET", extendAPIBase+"/v1/extract-runs/"+extractRun.ID, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if err := json.Unmarshal(respBody, &extractRun); err != nil {
return nil, err
}
}
return &extractRun, nil
}
// ProcessForm1120S processes a Form 1120-S file: parse to markdown, then extract structured fields.
func ProcessForm1120S(filePath string) (*ProcessResult, error) {
apiKey := os.Getenv("EXTEND_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("EXTEND_API_KEY environment variable not set")
}
fmt.Printf("Processing Form 1120-S from: %s\n", filePath)
// Convert local file to data URL (base64)
fileBuffer, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
base64Str := base64.StdEncoding.EncodeToString(fileBuffer)
dataURL := "data:application/octet-stream;base64," + base64Str
// Step 1: Parse (agentic OCR to markdown)
fmt.Println("\n[Step 1] Parsing form to markdown...")
parseRun, err := createAndPollParseRun(apiKey, dataURL)
if err != nil {
return nil, err
}
var markdown string
if parseRun.Status == "PROCESSED" {
for _, chunk := range parseRun.Output.Chunks {
markdown += chunk.Content + "\n\n"
}
fmt.Printf("✓ Parsed successfully (%d chunks)\n", len(parseRun.Output.Chunks))
} else {
errMsg := "unknown error"
if parseRun.Error != nil {
errMsg = parseRun.Error.Message
}
return nil, fmt.Errorf("parse failed with status: %s — %s", parseRun.Status, errMsg)
}
// Step 2: Extract structured fields with review-agent validation
fmt.Println("\n[Step 2] Extracting structured fields...")
extractRun, err := createAndPollExtractRun(apiKey, dataURL)
if err != nil {
return nil, err
}
var extracted Form1120SExtracted
if extractRun.Status == "PROCESSED" {
extracted = extractRun.Output.Value
fmt.Println("✓ Extraction completed with review-agent validation")
} else {
errMsg := "unknown error"
if extractRun.Error != nil {
errMsg = extractRun.Error.Message
}
return nil, fmt.Errorf("extraction failed with status: %s — %s", extractRun.Status, errMsg)
}
// Return both outputs for downstream use
return &ProcessResult{
Status: "success",
FilePath: filePath,
Parsed: map[string]interface{}{
"markdown": markdown,
"chunkCount": len(parseRun.Output.Chunks),
},
Extracted: extracted,
ExtractRunID: extractRun.ID,
ParseRunID: parseRun.ID,
}, nil
}
func main() {
flag.Parse()
args := flag.Args()
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "Usage: %s <file_path>\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
result, err := ProcessForm1120S(args[0])
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("\n=== RESULT ===")
resultJSON, err := json.MarshalIndent(result, "", " ")
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshaling result: %v\n", err)
os.Exit(1)
}
fmt.Println(string(resultJSON))
}// Deploy the "Form 1120-S" 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/1120-extractor.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: 1120-extractor).
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, "1120-extractor.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": "Form 1120-S 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",
"properties": {
"ein": {
"type": [
"string",
"null"
],
"description": "Employer Identification Number"
},
"tax_year": {
"type": [
"string",
"null"
],
"description": "Calendar year or tax year period"
},
"form_name": {
"type": [
"string",
"null"
],
"description": "Form identifier and title"
},
"gross_profit": {
"type": [
"string",
"null"
],
"description": "Gross profit after cost of goods sold"
},
"total_assets": {
"type": [
"string",
"null"
],
"description": "Total assets at end of tax year"
},
"total_income": {
"type": [
"string",
"null"
],
"description": "Total income or loss for tax year"
},
"gross_receipts": {
"type": [
"string",
"null"
],
"description": "Gross receipts or sales before returns and allowances"
},
"business_address": {
"type": [
"string",
"null"
],
"description": "Street address, city, state, and ZIP code"
},
"corporation_name": {
"type": [
"string",
"null"
],
"description": "Legal name of the S corporation"
},
"number_of_shareholders": {
"type": [
"string",
"null"
],
"description": "Number of shareholders during any part of tax year"
},
"return_type_indicators": {
"type": [
"string",
"null"
],
"description": "Indicates if final, amended, name change, address change, or election termination"
},
"s_election_effective_date": {
"type": [
"string",
"null"
],
"description": "Date S corporation election became effective"
}
}
},
"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 extend_ai import Extend
API_KEY = os.environ.get("EXTEND_API_KEY")
if not API_KEY:
print("Set EXTEND_API_KEY first.", file=sys.stderr)
sys.exit(1)
STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "1120-extractor.json"
state = {}
if STATE_FILE.exists():
state = json.loads(STATE_FILE.read_text())
def save_state():
STATE_DIR.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(state, indent=2))
WORKFLOW = {
"name": "Form 1120-S 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",
"properties": {
"form_name": {
"type": ["string", "null"],
"description": "Form identifier and title"
},
"tax_year": {
"type": ["string", "null"],
"description": "Calendar year or tax year period"
},
"corporation_name": {
"type": ["string", "null"],
"description": "Legal name of the S corporation"
},
"ein": {
"type": ["string", "null"],
"description": "Employer Identification Number"
},
"business_address": {
"type": ["string", "null"],
"description": "Street address, city, state, and ZIP code"
},
"s_election_effective_date": {
"type": ["string", "null"],
"description": "Date S corporation election became effective"
},
"total_assets": {
"type": ["string", "null"],
"description": "Total assets at end of tax year"
},
"gross_receipts": {
"type": ["string", "null"],
"description": "Gross receipts or sales before returns and allowances"
},
"gross_profit": {
"type": ["string", "null"],
"description": "Gross profit after cost of goods sold"
},
"total_income": {
"type": ["string", "null"],
"description": "Total income or loss for tax year"
},
"number_of_shareholders": {
"type": ["string", "null"],
"description": "Number of shareholders during any part of tax year"
},
"return_type_indicators": {
"type": ["string", "null"],
"description": "Indicates if final, amended, name change, address change, or election termination"
}
}
},
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {
"enabled": True
},
"advancedMultimodalEnabled": True
}
}
}
}
]
}
def main():
client = Extend(token=API_KEY)
print(f"Deploying \"{WORKFLOW['name']}\"…")
if state.get("workflowId"):
workflow_id = state["workflowId"]
print(f"✓ workflow already provisioned ({workflow_id}) — updating steps")
client.workflows.update(id=workflow_id, steps=WORKFLOW["steps"])
else:
# Try to find existing workflow with same name
try:
workflows_list = client.workflows.list(name=WORKFLOW["name"])
items = workflows_list.data if hasattr(workflows_list, "data") else (workflows_list.items if hasattr(workflows_list, "items") else [])
existing = next((x for x in items if x.name == WORKFLOW["name"]), None)
if existing and existing.id:
state["workflowId"] = existing.id
save_state()
print(f"✓ workflow \"{WORKFLOW['name']}\" found in your account ({existing.id}) — updating steps")
client.workflows.update(id=existing.id, steps=WORKFLOW["steps"])
except Exception:
pass
if not state.get("workflowId"):
created = client.workflows.create(**WORKFLOW)
workflow_id = created.id if hasattr(created, "id") else (created.workflow.id if hasattr(created, "workflow") else None)
if not workflow_id:
raise ValueError("Could not read created workflow id from response")
state["workflowId"] = workflow_id
save_state()
print(f"+ created workflow ({workflow_id})")
# Deploy the current draft as a new version
try:
client.workflows.create_version(id=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__":
try:
main()
except Exception as e:
print(str(e), file=sys.stderr)
sys.exit(1)// This code calls Extend's REST API directly using only Java's built-in java.net.http.HttpClient.
// Extend does not publish an official Java SDK; this approach mirrors the TypeScript reference exactly.
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.List;
import java.util.Map;
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("1120-extractor.json");
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
static class State {
String workflowId;
}
private static State state = new State();
public static void main(String[] args) {
try {
if (API_KEY == null || API_KEY.isEmpty()) {
System.err.println("Set EXTEND_API_KEY first.");
System.exit(1);
}
loadState();
Map<String, Object> workflow = buildWorkflow();
System.out.println("Deploying \"" + workflow.get("name") + "\"…");
if (state.workflowId != null && !state.workflowId.isEmpty()) {
System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
api("POST", "/workflows/" + state.workflowId, Map.of("steps", workflow.get("steps")));
} else {
try {
String workflowName = (String) workflow.get("name");
String encoded = URLEncoder.encode(workflowName, StandardCharsets.UTF_8);
Map<String, Object> list = api("GET", "/workflows?name=" + encoded, null);
List<?> items = (List<?>) list.getOrDefault("data", list.getOrDefault("items", List.of()));
for (Object item : items) {
if (item instanceof Map) {
Map<String, Object> itemMap = (Map<String, Object>) item;
if (workflowName.equals(itemMap.get("name"))) {
String existingId = (String) itemMap.get("id");
if (existingId != null) {
state.workflowId = existingId;
saveState();
System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + existingId + ") — updating steps");
api("POST", "/workflows/" + existingId, Map.of("steps", workflow.get("steps")));
break;
}
}
}
}
} catch (Exception e) {
// lookup is best-effort; fall through to create
}
if (state.workflowId == null || state.workflowId.isEmpty()) {
Map<String, Object> created = api("POST", "/workflows", workflow);
String wfId = (String) created.get("id");
if (wfId == null) {
Map<String, Object> workflowObj = (Map<String, Object>) created.get("workflow");
if (workflowObj != null) {
wfId = (String) workflowObj.get("id");
}
}
if (wfId == null) {
throw new RuntimeException("Could not read created workflow id from response");
}
state.workflowId = wfId;
saveState();
System.out.println("+ created workflow (" + wfId + ")");
}
}
try {
api("POST", "/workflows/" + state.workflowId + "/versions", Map.of());
} 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.");
} catch (Exception e) {
System.err.println(e.getMessage() != null ? e.getMessage() : e.toString());
System.exit(1);
}
}
private static void loadState() throws IOException {
if (Files.exists(STATE_FILE)) {
String content = Files.readString(STATE_FILE);
Map<String, Object> parsed = parseJson(content);
state.workflowId = (String) parsed.get("workflowId");
}
}
private static void saveState() throws IOException {
Files.createDirectories(STATE_DIR);
Map<String, Object> stateMap = new LinkedHashMap<>();
if (state.workflowId != null) {
stateMap.put("workflowId", state.workflowId);
}
String json = toJson(stateMap);
Files.writeString(STATE_FILE, json);
}
private static Map<String, Object> api(String method, String pathName, Object body) throws Exception {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(API + pathName))
.header("Authorization", "Bearer " + API_KEY)
.header("x-extend-api-version", VERSION);
if (body != null) {
String jsonBody = toJson(body);
requestBuilder.header("Content-Type", "application/json")
.method(method, HttpRequest.BodyPublishers.ofString(jsonBody));
} else {
requestBuilder.method(method, HttpRequest.BodyPublishers.noBody());
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> data;
try {
data = parseJson(response.body());
} catch (Exception e) {
data = new LinkedHashMap<>();
}
if (response.statusCode() < 200 || response.statusCode() >= 300) {
String errorMsg = toJson(data);
if (errorMsg.length() > 300) {
errorMsg = errorMsg.substring(0, 300);
}
throw new RuntimeException(method + " " + pathName + " failed (" + response.statusCode() + "): " + errorMsg);
}
return data;
}
private static Map<String, Object> buildWorkflow() {
Map<String, Object> workflow = new LinkedHashMap<>();
workflow.put("name", "Form 1120-S Processing Pipeline");
List<Map<String, Object>> steps = List.of(
buildTriggerStep(),
buildParseStep(),
buildExtractionStep()
);
workflow.put("steps", steps);
return workflow;
}
private static Map<String, Object> buildTriggerStep() {
Map<String, Object> step = new LinkedHashMap<>();
step.put("name", "startTrigger1");
step.put("type", "TRIGGER");
step.put("next", List.of(Map.of("step", "parse1")));
return step;
}
private static Map<String, Object> buildParseStep() {
Map<String, Object> step = new LinkedHashMap<>();
step.put("name", "parse1");
step.put("type", "PARSE");
Map<String, Object> config = new LinkedHashMap<>();
Map<String, Object> parseConfig = new LinkedHashMap<>();
Map<String, Object> blockOptions = new LinkedHashMap<>();
Map<String, Object> text = new LinkedHashMap<>();
Map<String, Object> agentic = new LinkedHashMap<>();
agentic.put("enabled", true);
text.put("agentic", agentic);
blockOptions.put("text", text);
parseConfig.put("blockOptions", blockOptions);
parseConfig.put("chunkingStrategy", Map.of("type", "document"));
config.put("parseConfig", parseConfig);
step.put("config", config);
step.put("next", List.of(Map.of("step", "extraction2")));
return step;
}
private static Map<String, Object> buildExtractionStep() {
Map<String, Object> step = new LinkedHashMap<>();
step.put("name", "extraction2");
step.put("type", "EXTRACT");
Map<String, Object> config = new LinkedHashMap<>();
Map<String, Object> extractorConfig = new LinkedHashMap<>();
Map<String, Object> schema = new LinkedHashMap<>();
schema.put("type", "object");
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("form_name", Map.of("type", List.of("string", "null"), "description", "Form identifier and title"));
properties.put("tax_year", Map.of("type", List.of("string", "null"), "description", "Calendar year or tax year period"));
properties.put("corporation_name", Map.of("type", List.of("string", "null"), "description", "Legal name of the S corporation"));
properties.put("ein", Map.of("type", List.of("string", "null"), "description", "Employer Identification Number"));
properties.put("business_address", Map.of("type", List.of("string", "null"), "description", "Street address, city, state, and ZIP code"));
properties.put("s_election_effective_date", Map.of("type", List.of("string", "null"), "description", "Date S corporation election became effective"));
properties.put("total_assets", Map.of("type", List.of("string", "null"), "description", "Total assets at end of tax year"));
properties.put("gross_receipts", Map.of("type", List.of("string", "null"), "description", "Gross receipts or sales before returns and allowances"));
properties.put("gross_profit", Map.of("type", List.of("string", "null"), "description", "Gross profit after cost of goods sold"));
properties.put("total_income", Map.of("type", List.of("string", "null"), "description", "Total income or loss for tax year"));
properties.put("number_of_shareholders", Map.of("type", List.of("string", "null"), "description", "Number of shareholders during any part of tax year"));
properties.put("return_type_indicators", Map.of("type", List.of("string", "null"), "description", "Indicates if final, amended, name change, address change, or election termination"));
schema.put("properties", properties);
extractorConfig.put("schema", schema);
extractorConfig.put("baseProcessor", "extraction_performance");
Map<String, Object> advancedOptions = new LinkedHashMap<>();
advancedOptions.put("reviewAgent", Map.of("enabled", true));
advancedOptions.put("advancedMultimodalEnabled", true);
extractorConfig.put("advancedOptions", advancedOptions);
config.put("extractorConfig", extractorConfig);
step.put("config", config);
return step;
}
private static String toJson(Object obj) {
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(escapeJson(entry.getKey())).append("\":");
sb.append(toJson(entry.getValue()));
first = false;
}
sb.append("}");
return sb.toString();
}
if (obj instanceof List) {
List<?> list = (List<?>) obj;
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (Object item : list) {
if (!first) sb.append(",");
sb.append(toJson(item));
first = false;
}
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> parseJson(String json) {
json = json.trim();
if (!json.startsWith("{")) return new LinkedHashMap<>();
Map<String, Object> result = new LinkedHashMap<>();
int depth = 0;
int i = 1;
String currentKey = null;
StringBuilder currentValue = new StringBuilder();
boolean inString = false;
boolean escaped = false;
while (i < json.length() - 1) {
char c = json.charAt(i);
if (escaped) {
currentValue.append(c);
escaped = false;
i++;
continue;
}
if (c == '\\' && inString) {
escaped = true;
currentValue.append(c);
i++;
continue;
}
if (c == '"') {
inString = !inString;
currentValue.append(c);
i++;
continue;
}
if (inString) {
currentValue.append(c);
i++;
continue;
}
if (c == ':' && depth == 0 && currentKey == null) {
currentKey = currentValue.toString().trim();
if (currentKey.startsWith("\"") && currentKey.endsWith("\"")) {
currentKey = currentKey.substring(1, currentKey.length() - 1);
}
currentValue = new StringBuilder();
i++;
continue;
}
if ((c == ',' || c == '}') && depth == 0 && currentKey != null) {
String value = currentValue.toString().trim();
result.put(currentKey, parseValue(value));
currentKey = null;
currentValue = new StringBuilder();
if (c == '}') break;
i++;
continue;
}
if (c == '{' || c == '[') depth++;
if (c == '}' || c == ']') depth--;
currentValue.append(c);
i++;
}
return result;
}
private static Object parseValue(String value) {
if (value.equals("null")) return null;
if (value.equals("true")) return true;
if (value.equals("false")) return false;
if (value.startsWith("\"") && value.endsWith("\"")) {
return value.substring(1, value.length() - 1);
}
try {
if (value.contains(".")) {
return Double.parseDouble(value);
} else {
return Long.parseLong(value);
}
} catch (NumberFormatException e) {
return value;
}
}
}// This code uses the Extend REST API directly because Extend has no official Go SDK yet.
// It deploys the "Form 1120-S" pipeline to your Extend account.
//
// Usage:
// export EXTEND_API_KEY=sk_... (from https://dashboard.extend.ai → API Keys)
// go run provision.go
//
// Generated by doc1 (template: 1120-extractor).
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
)
const (
API = "https://api.extend.ai"
VERSION = "2026-02-09"
)
var (
apiKey string
stateDir string
stateFile string
)
type State struct {
WorkflowID string `json:"workflowId,omitempty"`
}
var state State
func init() {
apiKey = os.Getenv("EXTEND_API_KEY")
if apiKey == "" {
fmt.Fprintf(os.Stderr, "Set EXTEND_API_KEY first.\n")
os.Exit(1)
}
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get working directory: %v\n", err)
os.Exit(1)
}
stateDir = filepath.Join(cwd, ".extend")
stateFile = filepath.Join(stateDir, "1120-extractor.json")
// Load existing state if it exists
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, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
return os.WriteFile(stateFile, data, 0644)
}
func apiCall(method, pathName string, body interface{}) (map[string]interface{}, error) {
var reqBody io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
reqBody = bytes.NewReader(data)
}
req, err := http.NewRequest(method, API+pathName, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
req.Header.Set("x-extend-api-version", VERSION)
if body != 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, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
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
}
func main() {
workflow := map[string]interface{}{
"name": "Form 1120-S 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",
"properties": map[string]interface{}{
"form_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Form identifier and title",
},
"tax_year": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Calendar year or tax year period",
},
"corporation_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Legal name of the S corporation",
},
"ein": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Employer Identification Number",
},
"business_address": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Street address, city, state, and ZIP code",
},
"s_election_effective_date": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Date S corporation election became effective",
},
"total_assets": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Total assets at end of tax year",
},
"gross_receipts": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Gross receipts or sales before returns and allowances",
},
"gross_profit": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Gross profit after cost of goods sold",
},
"total_income": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Total income or loss for tax year",
},
"number_of_shareholders": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Number of shareholders during any part of tax year",
},
"return_type_indicators": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Indicates if final, amended, name change, address change, or election termination",
},
},
},
"baseProcessor": "extraction_performance",
"advancedOptions": map[string]interface{}{
"reviewAgent": map[string]interface{}{
"enabled": true,
},
"advancedMultimodalEnabled": true,
},
},
},
},
},
}
workflowName := workflow["name"].(string)
fmt.Printf("Deploying \"%s\"…\n", workflowName)
if state.WorkflowID != "" {
fmt.Printf("✓ workflow already provisioned (%s) — updating steps\n", state.WorkflowID)
_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", state.WorkflowID), map[string]interface{}{
"steps": workflow["steps"],
})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
} else {
// Try to find an existing workflow with the same name
query := url.QueryEscape(workflowName)
list, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", query), nil)
if err == nil {
var items []map[string]interface{}
if data, ok := list["data"].([]interface{}); ok {
for _, item := range data {
items = append(items, item.(map[string]interface{}))
}
} else if data, ok := list["items"].([]interface{}); ok {
for _, item := range data {
items = append(items, item.(map[string]interface{}))
}
}
for _, item := range items {
if name, ok := item["name"].(string); ok && name == workflowName {
if id, ok := item["id"].(string); ok {
state.WorkflowID = id
saveState()
fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", workflowName, id)
_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", id), map[string]interface{}{
"steps": workflow["steps"],
})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
break
}
}
}
}
if state.WorkflowID == "" {
created, err := apiCall("POST", "/workflows", workflow)
if err != nil {
fmt.Fprintf(os.Stderr, "%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.Fprintf(os.Stderr, "Could not read created workflow id from response\n")
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.")
}Form 1120-S is the U.S. Income Tax Return for S Corporations, used to report annual income, deductions, and tax liability. This template extracts key corporate information, shareholder details, income lines, and deduction categories from the IRS form. It supports organizations filing S Corporation tax returns with complex financial schedules and supporting documentation.