Extracts customs clearance details, imported goods, and associated duty charges.
A customs clearance bill is an official document issued by customs authorities that itemizes imported goods, specifies their origin and destination, and details all applicable customs charges including duties, fees, VAT, and inspection costs. This template takes in Customs Clearance Bills and outputs markdown (.md) capturing the document's full text and layout, and JSON (.json) with structured fields including goods, parties, charges, and payment status 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 — 13 fieldschangedadvancedOptions.advancedMultimodalEnabledtruechangedadvancedOptions.reviewAgent.enabledtruechangedbaseProcessor"extraction_performance"You can learn more about Extract configuration in Extend's Extract documentation.
{
"name": "Custom Clearance Bill 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": [
"goods",
"invoice_date",
"shipper_name",
"consignee_name",
"invoice_number",
"origin_country",
"payment_status",
"customs_charges",
"customs_agent_name",
"destination_country",
"customs_agent_address",
"total_customs_charges",
"customs_declaration_number"
],
"properties": {
"goods": {
"type": "array",
"items": {
"type": "object",
"required": [
"quantity",
"description"
],
"properties": {
"quantity": {
"type": [
"number",
"null"
],
"description": "The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures."
},
"description": {
"type": [
"string",
"null"
],
"description": "A description of the good or product, such as its type, model, or material. May include product codes or trade names."
}
},
"additionalProperties": false
},
"description": "A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail."
},
"invoice_date": {
"type": [
"string",
"null"
],
"description": "The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar.",
"extend:type": "date"
},
"shipper_name": {
"type": [
"string",
"null"
],
"description": "The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction."
},
"consignee_name": {
"type": [
"string",
"null"
],
"description": "The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient."
},
"invoice_number": {
"type": [
"string",
"null"
],
"description": "The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary."
},
"origin_country": {
"type": [
"string",
"null"
],
"description": "The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar."
},
"payment_status": {
"type": [
"string",
"null"
],
"description": "The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp."
},
"customs_charges": {
"type": "array",
"items": {
"type": "object",
"required": [
"charge_type",
"charge_amount"
],
"properties": {
"charge_type": {
"type": [
"string",
"null"
],
"description": "The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge."
},
"charge_amount": {
"type": "object",
"required": [
"amount",
"iso_4217_currency_code"
],
"properties": {
"amount": {
"type": [
"number",
"null"
]
},
"iso_4217_currency_code": {
"type": [
"string",
"null"
]
}
},
"description": "The monetary amount for this specific customs charge.",
"extend:type": "currency",
"additionalProperties": false
}
},
"additionalProperties": false
},
"description": "A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge."
},
"customs_agent_name": {
"type": [
"string",
"null"
],
"description": "The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar."
},
"destination_country": {
"type": [
"string",
"null"
],
"description": "The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar."
},
"customs_agent_address": {
"type": [
"string",
"null"
],
"description": "The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details."
},
"total_customs_charges": {
"type": "object",
"required": [
"amount",
"iso_4217_currency_code"
],
"properties": {
"amount": {
"type": [
"number",
"null"
]
},
"iso_4217_currency_code": {
"type": [
"string",
"null"
]
}
},
"description": "The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar.",
"extend:type": "currency",
"additionalProperties": false
},
"customs_declaration_number": {
"type": [
"string",
"null"
],
"description": "The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar."
}
},
"additionalProperties": false
},
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {
"enabled": true
},
"advancedMultimodalEnabled": true
}
}
}
}
]
}# Custom Clearance Bill Processing — Extend AI Skill
## What this pipeline does
Converts Turkish customs clearance bills (sea cargo) from PDF to structured JSON. The pipeline parses the document to markdown using agentic OCR (handles stamps, handwriting, complex layouts), then extracts 13 required fields including shipper/consignee, itemized goods with quantities, 8+ customs charges (duties, VAT, fees), payment status, and declaration numbers. Output is production-ready for compliance systems, payment processing, and trade finance automation.
## When to use this
- **Customs clearance automation**: Ingesting hundreds of Turkish port bills daily to auto-populate ERP or trade management systems
- **Payment reconciliation**: Extracting total customs charges and payment status to match against bank records and accounting ledgers
- **Trade compliance**: Building an audit trail of origin/destination, goods descriptions, and declaration numbers for regulatory reporting
- **Logistics coordination**: Feeding consignee/shipper details and goods lists into warehouse management and shipping systems
- **Financial analytics**: Analyzing duty/VAT/fee breakdowns across shipments to forecast landed costs and negotiate better rates
## Processor pipeline
### Step 1: Parse (agentic_ocr mode)
**Processor**: `parse_performance` with agentic text extraction enabled.
**Purpose**: Convert the PDF bill into clean markdown, preserving table structure and handling Turkish customs forms that may include hand-stamped approval marks, watermarks, or non-standard layouts.
**Key config**:
- `engine: "parse_performance"` — optimized for speed and accuracy on forms/invoices
- `blockOptions.text.agentic.enabled: true` — uses Claude to understand customs terminology and semantic structure, not just OCR pixels
- `chunkingStrategy.type: "document"` — treats the entire bill as one logical unit (no cross-document chunking), preserving invoice-level context
**Why**: Customs bills are semi-structured; tables may have merged cells, logos, or multilingual headers. Agentic mode understands customs jargon (e.g., "Gümrük Vergileri" = customs duties) and reconstructs cargo line items even if OCR would struggle.
### Step 2: Extract (extraction_performance + review agent)
**Processor**: `extraction_performance` with `reviewAgent` enabled and `advancedMultimodalEnabled: true`.
**Purpose**: Pull 13 required fields into typed JSON: goods array (description + quantity), dates, party names, charges breakdown, and totals.
**Key config**:
- `baseProcessor: "extraction_performance"` — high-accuracy mode; worth the latency (5–15s) for financial documents where errors cost money
- `schema`: 13 required fields with detailed descriptions; currency amounts are objects with `amount` (number) and `iso_4217_currency_code` (e.g., "TRY"); goods is an array of `{description, quantity}` pairs
- `reviewAgent.enabled: true` — after extraction, an agentic review step checks for logical consistency (e.g., sum of line charges ≈ total, quantity > 0) and flags confidence issues
- `advancedMultimodalEnabled: true` — uses visual layout clues (table position, font weight, italics) in addition to text to disambiguate which number is the total vs. a subtotal
**Why**: Customs bills have multiple numeric fields (base value, duties, VAT, fees, grand total). Review agent prevents extracting the wrong total. Advanced multimodal catches totals printed in bold or highlighted cells that plain text extraction might misplace.
## TypeScript implementation
```typescript
import { ExtendClient, extendCurrency, extendDate } from "extend-ai";
import { z } from "zod";
import fs from "fs";
// Define schema using Zod with Extend helpers for currency and date
const customsClearanceBillSchema = z.object({
// Core identifiers and dates
invoice_number: z.string().nullable().describe(
"The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary."
),
invoice_date: extendDate().describe(
"The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar."
),
customs_declaration_number: z.string().nullable().describe(
"The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar."
),
// Parties involved
shipper_name: z.string().nullable().describe(
"The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction."
),
consignee_name: z.string().nullable().describe(
"The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient."
),
// Locations
origin_country: z.string().nullable().describe(
"The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar."
),
destination_country: z.string().nullable().describe(
"The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar."
),
// Customs agent details
customs_agent_name: z.string().nullable().describe(
"The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar."
),
customs_agent_address: z.string().nullable().describe(
"The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details."
),
// Goods being imported/exported
goods: z.array(
z.object({
description: z.string().nullable().describe(
"A description of the good or product, such as its type, model, or material. May include product codes or trade names."
),
quantity: z.number().nullable().describe(
"The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures."
),
})
).describe(
"A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail."
),
// Customs charges breakdown
customs_charges: z.array(
z.object({
charge_type: z.string().nullable().describe(
"The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge."
),
charge_amount: extendCurrency().describe(
"The monetary amount for this specific customs charge."
),
})
).describe(
"A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge."
),
// Total and payment status
total_customs_charges: extendCurrency().describe(
"The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar."
),
payment_status: z.string().nullable().describe(
"The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp."
),
});
// Main processing function
export async function processCustomClearanceBill(filePath: string) {
// Initialize client from environment
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
// Convert local file to data URL (base64)
const fileBuffer = fs.readFileSync(filePath);
const dataUrl = `data:application/octet-stream;base64,${fileBuffer.toString("base64")}`;
console.log("Starting Custom Clearance Bill processing pipeline...");
console.log(`Input file: ${filePath}`);
try {
// Step 1: Parse the document to markdown
console.log("\n[Step 1] Parsing document with agentic OCR...");
const parseRun = await client.parseRuns.createAndPoll({
file: { url: dataUrl },
config: {
blockOptions: {
text: {
agentic: {
enabled: true,
},
},
},
chunkingStrategy: {
type: "document",
},
},
});
if (parseRun.status !== "PROCESSED") {
throw new Error(`Parse failed with status: ${parseRun.status}`);
}
console.log(`✓ Parsing complete. ${parseRun.output.chunks.length} chunks extracted.`);
// Optionally log the markdown for inspection
const markdown = parseRun.output.chunks.map((c) => c.content).join("\n\n");
console.log("\n--- Parsed Markdown (first 500 chars) ---");
console.log(markdown.substring(0, 500));
console.log("--- End preview ---\n");
// Step 2: Extract structured data using Zod schema and review agent
console.log("[Step 2] Extracting structured fields with review agent...");
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: customsClearanceBillSchema,
baseProcessor: "extraction_performance",
advancedOptions: {
reviewAgent: {
enabled: true,
},
advancedMultimodalEnabled: true,
},
},
});
if (extractRun.status !== "PROCESSED") {
throw new Error(`Extraction failed with status: ${extractRun.status}`);
}
console.log("✓ Extraction complete with review agent validation.\n");
// Extract and type-check the result
const billData = extractRun.output.value;
// Display extracted data
console.log("=== EXTRACTED CUSTOMS CLEARANCE BILL DATA ===\n");
console.log("Document Identifiers:");
console.log(` Invoice Number: ${billData.invoice_number}`);
console.log(` Invoice Date: ${billData.invoice_date}`);
console.log(` Declaration Number: ${billData.customs_declaration_number}`);
console.log("\nParties:");
console.log(` Shipper: ${billData.shipper_name}`);
console.log(` Consignee: ${billData.consignee_name}`);
console.log("\nLocations:");
console.log(` Origin: ${billData.origin_country}`);
console.log(` Destination: ${billData.destination_country}`);
console.log("\nCustoms Agent:");
console.log(` Name: ${billData.customs_agent_name}`);
console.log(` Address: ${billData.customs_agent_address}`);
console.log("\nGoods:");
if (billData.goods && billData.goods.length > 0) {
billData.goods.forEach((item, idx) => {
console.log(` [${idx + 1}] ${item.description} (Qty: ${item.quantity})`);
});
} else {
console.log(" (No goods extracted)");
}
console.log("\nCustoms Charges:");
if (billData.customs_charges && billData.customs_charges.length > 0) {
billData.customs_charges.forEach((charge) => {
const amt = charge.charge_amount?.amount ?? "N/A";
const curr = charge.charge_amount?.iso_4217_currency_code ?? "N/A";
console.log(` ${charge.charge_type}: ${amt} ${curr}`);
});
} else {
console.log(" (No charges extracted)");
}
console.log("\nFinancial Summary:");
const totalAmt = billData.total_customs_charges?.amount ?? "N/A";
const totalCurr = billData.total_customs_charges?.iso_4217_currency_code ?? "N/A";
console.log(` Total Customs Charges: ${totalAmt} ${totalCurr}`);
console.log(` Payment Status: ${billData.payment_status}`);
// Return structured result for further processing
return {
success: true,
billData,
parseOutput: parseRun.output,
extractOutput: extractRun.output,
};
} catch (error) {
console.error("\n✗ Pipeline failed:", error);
throw error;
}
}
// Invoke if run directly (for testing)
if (require.main === module) {
const filePath = process.argv[2] || "./sample_clearance_bill.pdf";
processCustomClearanceBill(filePath)
.then((result) => {
console.log("\n✓ Processing complete. Data ready for downstream systems.");
process.exit(0);
})
.catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});
}
```
## CLI equivalent
```bash
# Step 1: Parse the document with agentic OCR
extend parse sample_clearance_bill.pdf \
--engine parse_performance \
--agentic-text true \
--chunking-strategy document
# Step 2: Extract structured fields with the schema and review agent
extend extract sample_clearance_bill.pdf \
--schema clearance_bill_schema.json \
--base-processor extraction_performance \
--enable-review-agent \
--enable-advanced-multimodal
```
Or in one workflow (if pre-registered):
```bash
extend run workflow_custom_clearance_bill_processing \
--file sample_clearance_bill.pdf
```
## Schema
Below is the complete JSON Schema for the extraction. Save this as `clearance_bill_schema.json` for CLI use.
```json
{
"type": "object",
"properties": {
"invoice_number": {
"type": ["string", "null"],
"description": "The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary."
},
"invoice_date": {
"type": ["string", "null"],
"extend:type": "date",
"description": "The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar. Format: ISO 8601 (YYYY-MM-DD)."
},
"customs_declaration_number": {
"type": ["string", "null"],
"description": "The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar."
},
"shipper_name": {
"type": ["string", "null"],
"description": "The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction."
},
"consignee_name": {
"type": ["string", "null"],
"description": "The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient."
},
"origin_country": {
"type": ["string", "null"],
"description": "The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'import { ExtendClient, extendCurrency, extendDate } from "extend-ai";
import { z } from "zod";
import fs from "fs";
// Define schema using Zod with Extend helpers for currency and date
const customsClearanceBillSchema = z.object({
// Core identifiers and dates
invoice_number: z.string().nullable().describe(
"The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary."
),
invoice_date: extendDate().describe(
"The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar."
),
customs_declaration_number: z.string().nullable().describe(
"The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar."
),
// Parties involved
shipper_name: z.string().nullable().describe(
"The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction."
),
consignee_name: z.string().nullable().describe(
"The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient."
),
// Locations
origin_country: z.string().nullable().describe(
"The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar."
),
destination_country: z.string().nullable().describe(
"The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar."
),
// Customs agent details
customs_agent_name: z.string().nullable().describe(
"The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar."
),
customs_agent_address: z.string().nullable().describe(
"The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details."
),
// Goods being imported/exported
goods: z.array(
z.object({
description: z.string().nullable().describe(
"A description of the good or product, such as its type, model, or material. May include product codes or trade names."
),
quantity: z.number().nullable().describe(
"The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures."
),
})
).describe(
"A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail."
),
// Customs charges breakdown
customs_charges: z.array(
z.object({
charge_type: z.string().nullable().describe(
"The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge."
),
charge_amount: extendCurrency().describe(
"The monetary amount for this specific customs charge."
),
})
).describe(
"A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge."
),
// Total and payment status
total_customs_charges: extendCurrency().describe(
"The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar."
),
payment_status: z.string().nullable().describe(
"The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp."
),
});
// Main processing function
export async function processCustomClearanceBill(filePath: string) {
// Initialize client from environment
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
// Convert local file to data URL (base64)
const fileBuffer = fs.readFileSync(filePath);
const dataUrl = `data:application/octet-stream;base64,${fileBuffer.toString("base64")}`;
console.log("Starting Custom Clearance Bill processing pipeline...");
console.log(`Input file: ${filePath}`);
try {
// Step 1: Parse the document to markdown
console.log("\n[Step 1] Parsing document with agentic OCR...");
const parseRun = await client.parseRuns.createAndPoll({
file: { url: dataUrl },
config: {
blockOptions: {
text: {
agentic: {
enabled: true,
},
},
},
chunkingStrategy: {
type: "document",
},
},
});
if (parseRun.status !== "PROCESSED") {
throw new Error(`Parse failed with status: ${parseRun.status}`);
}
console.log(`✓ Parsing complete. ${parseRun.output.chunks.length} chunks extracted.`);
// Optionally log the markdown for inspection
const markdown = parseRun.output.chunks.map((c) => c.content).join("\n\n");
console.log("\n--- Parsed Markdown (first 500 chars) ---");
console.log(markdown.substring(0, 500));
console.log("--- End preview ---\n");
// Step 2: Extract structured data using Zod schema and review agent
console.log("[Step 2] Extracting structured fields with review agent...");
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: customsClearanceBillSchema,
baseProcessor: "extraction_performance",
advancedOptions: {
reviewAgent: {
enabled: true,
},
advancedMultimodalEnabled: true,
},
},
});
if (extractRun.status !== "PROCESSED") {
throw new Error(`Extraction failed with status: ${extractRun.status}`);
}
console.log("✓ Extraction complete with review agent validation.\n");
// Extract and type-check the result
const billData = extractRun.output.value;
// Display extracted data
console.log("=== EXTRACTED CUSTOMS CLEARANCE BILL DATA ===\n");
console.log("Document Identifiers:");
console.log(` Invoice Number: ${billData.invoice_number}`);
console.log(` Invoice Date: ${billData.invoice_date}`);
console.log(` Declaration Number: ${billData.customs_declaration_number}`);
console.log("\nParties:");
console.log(` Shipper: ${billData.shipper_name}`);
console.log(` Consignee: ${billData.consignee_name}`);
console.log("\nLocations:");
console.log(` Origin: ${billData.origin_country}`);
console.log(` Destination: ${billData.destination_country}`);
console.log("\nCustoms Agent:");
console.log(` Name: ${billData.customs_agent_name}`);
console.log(` Address: ${billData.customs_agent_address}`);
console.log("\nGoods:");
if (billData.goods && billData.goods.length > 0) {
billData.goods.forEach((item, idx) => {
console.log(` [${idx + 1}] ${item.description} (Qty: ${item.quantity})`);
});
} else {
console.log(" (No goods extracted)");
}
console.log("\nCustoms Charges:");
if (billData.customs_charges && billData.customs_charges.length > 0) {
billData.customs_charges.forEach((charge) => {
const amt = charge.charge_amount?.amount ?? "N/A";
const curr = charge.charge_amount?.iso_4217_currency_code ?? "N/A";
console.log(` ${charge.charge_type}: ${amt} ${curr}`);
});
} else {
console.log(" (No charges extracted)");
}
console.log("\nFinancial Summary:");
const totalAmt = billData.total_customs_charges?.amount ?? "N/A";
const totalCurr = billData.total_customs_charges?.iso_4217_currency_code ?? "N/A";
console.log(` Total Customs Charges: ${totalAmt} ${totalCurr}`);
console.log(` Payment Status: ${billData.payment_status}`);
// Return structured result for further processing
return {
success: true,
billData,
parseOutput: parseRun.output,
extractOutput: extractRun.output,
};
} catch (error) {
console.error("\n✗ Pipeline failed:", error);
throw error;
}
}
// Invoke if run directly (for testing)
if (require.main === module) {
const filePath = process.argv[2] || "./sample_clearance_bill.pdf";
processCustomClearanceBill(filePath)
.then((result) => {
console.log("\n✓ Processing complete. Data ready for downstream systems.");
process.exit(0);
})
.catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});
}import os
import base64
from typing import Optional, Any
from dataclasses import dataclass
from extend_ai import Extend
@dataclass
class CurrencyAmount:
amount: Optional[float] = None
iso_4217_currency_code: Optional[str] = None
@dataclass
class Good:
description: Optional[str] = None
quantity: Optional[float] = None
@dataclass
class CustomsCharge:
charge_type: Optional[str] = None
charge_amount: Optional[CurrencyAmount] = None
@dataclass
class CustomsClearanceBillData:
invoice_number: Optional[str] = None
invoice_date: Optional[str] = None
customs_declaration_number: Optional[str] = None
shipper_name: Optional[str] = None
consignee_name: Optional[str] = None
origin_country: Optional[str] = None
destination_country: Optional[str] = None
customs_agent_name: Optional[str] = None
customs_agent_address: Optional[str] = None
goods: list[Good] = None
customs_charges: list[CustomsCharge] = None
total_customs_charges: Optional[CurrencyAmount] = None
payment_status: Optional[str] = None
# Define schema as a dictionary matching the extraction schema
customs_clearance_bill_schema = {
"type": "object",
"properties": {
"invoice_number": {
"type": ["string", "null"],
"description": "The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary."
},
"invoice_date": {
"type": ["string", "null"],
"extend:type": "date",
"description": "The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar."
},
"customs_declaration_number": {
"type": ["string", "null"],
"description": "The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar."
},
"shipper_name": {
"type": ["string", "null"],
"description": "The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction."
},
"consignee_name": {
"type": ["string", "null"],
"description": "The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient."
},
"origin_country": {
"type": ["string", "null"],
"description": "The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar."
},
"destination_country": {
"type": ["string", "null"],
"description": "The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar."
},
"customs_agent_name": {
"type": ["string", "null"],
"description": "The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar."
},
"customs_agent_address": {
"type": ["string", "null"],
"description": "The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details."
},
"goods": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {
"type": ["string", "null"],
"description": "A description of the good or product, such as its type, model, or material. May include product codes or trade names."
},
"quantity": {
"type": ["number", "null"],
"description": "The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures."
}
},
"required": ["description", "quantity"],
"additionalProperties": False
},
"description": "A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail."
},
"customs_charges": {
"type": "array",
"items": {
"type": "object",
"properties": {
"charge_type": {
"type": ["string", "null"],
"description": "The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge."
},
"charge_amount": {
"type": "object",
"properties": {
"amount": {
"type": ["number", "null"]
},
"iso_4217_currency_code": {
"type": ["string", "null"]
}
},
"required": ["amount", "iso_4217_currency_code"],
"additionalProperties": False,
"extend:type": "currency",
"description": "The monetary amount for this specific customs charge."
}
},
"required": ["charge_type", "charge_amount"],
"additionalProperties": False
},
"description": "A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge."
},
"total_customs_charges": {
"type": "object",
"properties": {
"amount": {
"type": ["number", "null"]
},
"iso_4217_currency_code": {
"type": ["string", "null"]
}
},
"required": ["amount", "iso_4217_currency_code"],
"additionalProperties": False,
"extend:type": "currency",
"description": "The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar."
},
"payment_status": {
"type": ["string", "null"],
"description": "The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp."
}
},
"required": [
"invoice_number",
"invoice_date",
"customs_declaration_number",
"shipper_name",
"consignee_name",
"origin_country",
"destination_country",
"customs_agent_name",
"customs_agent_address",
"goods",
"customs_charges",
"total_customs_charges",
"payment_status"
],
"additionalProperties": False
}
async def process_custom_clearance_bill(file_path: str) -> dict[str, Any]:
"""Process a customs clearance bill through Parse → Extract pipeline."""
# Initialize client from environment
client = Extend(token=os.environ["EXTEND_API_KEY"])
# Convert local file to data URL (base64)
with open(file_path, "rb") as f:
file_buffer = f.read()
data_url = f"data:application/octet-stream;base64,{base64.b64encode(file_buffer).decode('utf-8')}"
print("Starting Custom Clearance Bill processing pipeline...")
print(f"Input file: {file_path}")
try:
# Step 1: Parse the document to markdown
print("\n[Step 1] Parsing document with agentic OCR...")
parse_run = await client.parse_runs.create_and_poll(
file={"url": data_url},
config={
"block_options": {
"text": {
"agentic": {
"enabled": True,
},
},
},
"chunking_strategy": {
"type": "document",
},
},
)
if parse_run.status != "PROCESSED":
raise Exception(f"Parse failed with status: {parse_run.status}")
print(f"✓ Parsing complete. {len(parse_run.output.chunks)} chunks extracted.")
# Optionally log the markdown for inspection
markdown = "\n\n".join([c.content for c in parse_run.output.chunks])
print("\n--- Parsed Markdown (first 500 chars) ---")
print(markdown[:500])
print("--- End preview ---\n")
# Step 2: Extract structured data using schema and review agent
print("[Step 2] Extracting structured fields with review agent...")
extract_run = await client.extract_runs.create_and_poll(
file={"url": data_url},
config={
"schema": customs_clearance_bill_schema,
"base_processor": "extraction_performance",
"advanced_options": {
"review_agent": {
"enabled": True,
},
"advanced_multimodal_enabled": True,
},
},
)
if extract_run.status != "PROCESSED":
raise Exception(f"Extraction failed with status: {extract_run.status}")
print("✓ Extraction complete with review agent validation.\n")
# Extract and display the result
bill_data = extract_run.output.value
# Display extracted data
print("=== EXTRACTED CUSTOMS CLEARANCE BILL DATA ===\n")
print("Document Identifiers:")
print(f" Invoice Number: {bill_data.get('invoice_number')}")
print(f" Invoice Date: {bill_data.get('invoice_date')}")
print(f" Declaration Number: {bill_data.get('customs_declaration_number')}")
print("\nParties:")
print(f" Shipper: {bill_data.get('shipper_name')}")
print(f" Consignee: {bill_data.get('consignee_name')}")
print("\nLocations:")
print(f" Origin: {bill_data.get('origin_country')}")
print(f" Destination: {bill_data.get('destination_country')}")
print("\nCustoms Agent:")
print(f" Name: {bill_data.get('customs_agent_name')}")
print(f" Address: {bill_data.get('customs_agent_address')}")
print("\nGoods:")
goods = bill_data.get('goods', [])
if goods and len(goods) > 0:
for idx, item in enumerate(goods):
print(f" [{idx + 1}] {item.get('description')} (Qty: {item.get('quantity')})")
else:
print(" (No goods extracted)")
print("\nCustoms Charges:")
charges = bill_data.get('customs_charges', [])
if charges and len(charges) > 0:
for charge in charges:
charge_amt = charge.get('charge_amount', {})
amt = charge_amt.get('amount', 'N/A')
curr = charge_amt.get('iso_4217_currency_code', 'N/A')
print(f" {charge.get('charge_type')}: {amt} {curr}")
else:
print(" (No charges extracted)")
print("\nFinancial Summary:")
total_charges = bill_data.get('total_customs_charges', {})
total_amt = total_charges.get('amount', 'N/A')
total_curr = total_charges.get('iso_4217_currency_code', 'N/A')
print(f" Total Customs Charges: {total_amt} {total_curr}")
print(f" Payment Status: {bill_data.get('payment_status')}")
# Return structured result for further processing
return {
"success": True,
"bill_data": bill_data,
"parse_output": parse_run.output,
"extract_output": extract_run.output,
}
except Exception as error:
print(f"\n✗ Pipeline failed: {error}")
raise
if __name__ == "__main__":
import asyncio
import sys
file_path = sys.argv[1] if len(sys.argv) > 1 else "./sample_clearance_bill.pdf"
try:
result = asyncio.run(process_custom_clearance_bill(file_path))
print("\n✓ Processing complete. Data ready for downstream systems.")
except Exception as err:
print(f"Fatal error: {err}")
sys.exit(1)// NOTE: This code uses the Extend REST API directly (https://api.extend.ai) because
// Extend does not publish an official Java SDK. It uses only java.net.http.HttpClient
// and built-in JSON parsing—no third-party dependencies needed.
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CustomsClearanceBillProcessor {
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();
// Data classes matching the extraction schema
static class CurrencyValue {
public Double amount;
public String iso_4217_currency_code;
public CurrencyValue(Double amount, String iso_4217_currency_code) {
this.amount = amount;
this.iso_4217_currency_code = iso_4217_currency_code;
}
@Override
public String toString() {
return amount + " " + iso_4217_currency_code;
}
}
static class Good {
public String description;
public Double quantity;
}
static class CustomsCharge {
public String charge_type;
public CurrencyValue charge_amount;
}
static class CustomsClearanceBillData {
public String invoice_number;
public String invoice_date;
public String customs_declaration_number;
public String shipper_name;
public String consignee_name;
public String origin_country;
public String destination_country;
public String customs_agent_name;
public String customs_agent_address;
public List<Good> goods;
public List<CustomsCharge> customs_charges;
public CurrencyValue total_customs_charges;
public String payment_status;
}
static class ParseResponse {
public String status;
public OutputData output;
static class OutputData {
public List<Chunk> chunks;
}
static class Chunk {
public String content;
}
}
static class ExtractResponse {
public String status;
public ValueWrapper output;
static class ValueWrapper {
public CustomsClearanceBillData value;
}
}
// Simple JSON parsing helper (minimal, without external libraries)
private static String extractJsonField(String json, String fieldName) {
String pattern = "\"" + fieldName + "\":";
int startIndex = json.indexOf(pattern);
if (startIndex == -1) return null;
startIndex += pattern.length();
while (startIndex < json.length() && Character.isWhitespace(json.charAt(startIndex))) {
startIndex++;
}
if (startIndex >= json.length()) return null;
if (json.charAt(startIndex) == '"') {
int endIndex = startIndex + 1;
while (endIndex < json.length() && json.charAt(endIndex) != '"') {
if (json.charAt(endIndex) == '\\') endIndex++;
endIndex++;
}
return json.substring(startIndex + 1, endIndex);
} else if (json.charAt(startIndex) == 'n') {
return null;
}
return null;
}
// Parse document using the parse endpoint
private static String parseDocument(String dataUrl) throws IOException, InterruptedException {
String schema =
"{"
+ "\"blockOptions\":{"
+ "\"text\":{\"agentic\":{\"enabled\":true}}"
+ "},"
+ "\"chunkingStrategy\":{\"type\":\"document\"}"
+ "}";
String parseRequestBody =
"{"
+ "\"file\":{\"url\":\""
+ dataUrl.replace("\"", "\\\"")
+ "\"},"
+ "\"config\":"
+ schema
+ "}";
HttpRequest parseRequest =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/parse-runs"))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(parseRequestBody))
.build();
HttpResponse<String> parseResponse = httpClient.send(parseRequest, HttpResponse.BodyHandlers.ofString());
String runId = extractJsonField(parseResponse.body(), "id");
// Poll for completion
while (true) {
HttpRequest statusRequest =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/parse-runs/" + runId))
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> statusResponse = httpClient.send(statusRequest, HttpResponse.BodyHandlers.ofString());
String status = extractJsonField(statusResponse.body(), "status");
if ("PROCESSED".equals(status)) {
return statusResponse.body();
} else if ("PROCESSING".equals(status)) {
Thread.sleep(1000);
} else {
throw new RuntimeException("Parse failed with status: " + status);
}
}
}
// Extract structured data using the extract endpoint
private static String extractStructuredData(String dataUrl) throws IOException, InterruptedException {
String schema =
"{\"type\":\"object\",\"properties\":{"
+ "\"invoice_number\":{\"type\":[\"string\",\"null\"]},"
+ "\"invoice_date\":{\"type\":[\"string\",\"null\"],\"extend:type\":\"date\"},"
+ "\"customs_declaration_number\":{\"type\":[\"string\",\"null\"]},"
+ "\"shipper_name\":{\"type\":[\"string\",\"null\"]},"
+ "\"consignee_name\":{\"type\":[\"string\",\"null\"]},"
+ "\"origin_country\":{\"type\":[\"string\",\"null\"]},"
+ "\"destination_country\":{\"type\":[\"string\",\"null\"]},"
+ "\"customs_agent_name\":{\"type\":[\"string\",\"null\"]},"
+ "\"customs_agent_address\":{\"type\":[\"string\",\"null\"]},"
+ "\"goods\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{"
+ "\"description\":{\"type\":[\"string\",\"null\"]},\"quantity\":{\"type\":[\"number\",\"null\"]}"
+ "}}},"
+ "\"customs_charges\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{"
+ "\"charge_type\":{\"type\":[\"string\",\"null\"]},"
+ "\"charge_amount\":{\"type\":\"object\",\"properties\":{"
+ "\"amount\":{\"type\":[\"number\",\"null\"]},\"iso_4217_currency_code\":{\"type\":[\"string\",\"null\"]}"
+ "},\"extend:type\":\"currency\"}"
+ "}}},"
+ "\"total_customs_charges\":{\"type\":\"object\",\"properties\":{"
+ "\"amount\":{\"type\":[\"number\",\"null\"]},\"iso_4217_currency_code\":{\"type\":[\"string\",\"null\"]}"
+ "},\"extend:type\":\"currency\"},"
+ "\"payment_status\":{\"type\":[\"string\",\"null\"]}"
+ "}}";
String extractConfig =
"{\"baseProcessor\":\"extraction_performance\",\"advancedOptions\":{"
+ "\"reviewAgent\":{\"enabled\":true},\"advancedMultimodalEnabled\":true"
+ "},\"schema\":"
+ schema
+ "}";
String extractRequestBody =
"{"
+ "\"file\":{\"url\":\""
+ dataUrl.replace("\"", "\\\"")
+ "\"},"
+ "\"config\":"
+ extractConfig
+ "}";
HttpRequest extractRequest =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/extract-runs"))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(extractRequestBody))
.build();
HttpResponse<String> extractResponse = httpClient.send(extractRequest, HttpResponse.BodyHandlers.ofString());
String runId = extractJsonField(extractResponse.body(), "id");
// Poll for completion
while (true) {
HttpRequest statusRequest =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/extract-runs/" + runId))
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> statusResponse = httpClient.send(statusRequest, HttpResponse.BodyHandlers.ofString());
String status = extractJsonField(statusResponse.body(), "status");
if ("PROCESSED".equals(status)) {
return statusResponse.body();
} else if ("PROCESSING".equals(status)) {
Thread.sleep(1000);
} else {
throw new RuntimeException("Extraction failed with status: " + status);
}
}
}
// Parse simple JSON response into CustomsClearanceBillData object (minimal parsing)
private static CustomsClearanceBillData parseExtractedData(String jsonResponse) {
CustomsClearanceBillData data = new CustomsClearanceBillData();
data.invoice_number = extractJsonField(jsonResponse, "invoice_number");
data.invoice_date = extractJsonField(jsonResponse, "invoice_date");
data.customs_declaration_number = extractJsonField(jsonResponse, "customs_declaration_number");
data.shipper_name = extractJsonField(jsonResponse, "shipper_name");
data.consignee_name = extractJsonField(jsonResponse, "consignee_name");
data.origin_country = extractJsonField(jsonResponse, "origin_country");
data.destination_country = extractJsonField(jsonResponse, "destination_country");
data.customs_agent_name = extractJsonField(jsonResponse, "customs_agent_name");
data.customs_agent_address = extractJsonField(jsonResponse, "customs_agent_address");
data.payment_status = extractJsonField(jsonResponse, "payment_status");
data.goods = new ArrayList<>();
data.customs_charges = new ArrayList<>();
data.total_customs_charges = new CurrencyValue(null, null);
return data;
}
public static Map<String, Object> processCustomClearanceBill(String filePath)
throws IOException, InterruptedException {
// Read file and convert to data URL
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
String base64 = Base64.getEncoder().encodeToString(fileBytes);
String dataUrl = "data:application/octet-stream;base64," + base64;
System.out.println("Starting Custom Clearance Bill processing pipeline...");
System.out.println("Input file: " + filePath);
try {
// Step 1: Parse the document
System.out.println("\n[Step 1] Parsing document with agentic OCR...");
String parseResult = parseDocument(dataUrl);
System.out.println("✓ Parsing complete.");
// Step 2: Extract structured data
System.out.println("[Step 2] Extracting structured fields with review agent...");
String extractResult = extractStructuredData(dataUrl);
System.out.println("✓ Extraction complete with review agent validation.\n");
// Parse extracted data
CustomsClearanceBillData billData = parseExtractedData(extractResult);
// Display extracted data
System.out.println("=== EXTRACTED CUSTOMS CLEARANCE BILL DATA ===\n");
System.out.println("Document Identifiers:");
System.out.println(" Invoice Number: " + billData.invoice_number);
System.out.println(" Invoice Date: " + billData.invoice_date);
System.out.println(" Declaration Number: " + billData.customs_declaration_number);
System.out.println("\nParties:");
System.out.println(" Shipper: " + billData.shipper_name);
System.out.println(" Consignee: " + billData.consignee_name);
System.out.println("\nLocations:");
System.out.println(" Origin: " + billData.origin_country);
System.out.println(" Destination: " + billData.destination_country);
System.out.println("\nCustoms Agent:");
System.out.println(" Name: " + billData.customs_agent_name);
System.out.println(" Address: " + billData.customs_agent_address);
System.out.println("\nGoods:");
if (billData.goods != null && !billData.goods.isEmpty()) {
for (int i = 0; i < billData.goods.size(); i++) {
Good item = billData.goods.get(i);
System.out.println(" [" + (i + 1) + "] " + item.description + " (Qty: " + item.quantity + ")");
}
} else {
System.out.println(" (No goods extracted)");
}
System.out.println("\nCustoms Charges:");
if (billData.customs_charges != null && !billData.customs_charges.isEmpty()) {
for (CustomsCharge charge : billData.customs_charges) {
String amt = charge.charge_amount != null && charge.charge_amount.amount != null
? charge.charge_amount.amount.toString()
: "N/A";
String curr = charge.charge_amount != null && charge.charge_amount.iso_4217_currency_code != null
? charge.charge_amount.iso_4217_currency_code
: "N/A";
System.out.println(" " + charge.charge_type + ": " + amt + " " + curr);
}
} else {
System.out.println(" (No charges extracted)");
}
System.out.println("\nFinancial Summary:");
String totalAmt = billData.total_customs_charges != null && billData.total_customs_charges.amount != null
? billData.total_customs_charges.amount.toString()
: "N/A";
String totalCurr = billData.total_customs_charges != null && billData.total_customs_charges.iso_4217_currency_code != null
? billData.total_customs_charges.iso_4217_currency_code
: "N/A";
System.out.println(" Total Customs Charges: " + totalAmt + " " + totalCurr);
System.out.println(" Payment Status: " + billData.payment_status);
// Return structured result
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("billData", billData);
return result;
} catch (Exception error) {
System.err.println("\n✗ Pipeline failed: " + error.getMessage());
throw error;
}
}
public static void main(String[] args) throws IOException, InterruptedException {
String filePath = args.length > 0 ? args[0] : "./sample_clearance_bill.pdf";
try {
Map<String, Object> result = processCustomClearanceBill(filePath);
System.out.println("\n✓ Processing complete. Data ready for downstream systems.");
} catch (Exception err) {
System.err.println("Fatal error: " + err.getMessage());
err.printStackTrace();
System.exit(1);
}
}
}// This code uses the Extend REST API directly because Extend has no official Go SDK yet.
// It calls https://api.extend.ai endpoints with Bearer token authentication.
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
// Schema types for customs clearance bill extraction
type Good struct {
Description *string `json:"description"`
Quantity *float64 `json:"quantity"`
}
type Currency struct {
Amount *float64 `json:"amount"`
ISO4217Code *string `json:"iso_4217_currency_code"`
}
type CustomsCharge struct {
ChargeType *string `json:"charge_type"`
ChargeAmount *Currency `json:"charge_amount"`
}
type CustomsClearanceBillData struct {
InvoiceNumber *string `json:"invoice_number"`
InvoiceDate *string `json:"invoice_date"`
CustomsDeclarationNumber *string `json:"customs_declaration_number"`
ShipperName *string `json:"shipper_name"`
ConsigneeName *string `json:"consignee_name"`
OriginCountry *string `json:"origin_country"`
DestinationCountry *string `json:"destination_country"`
CustomsAgentName *string `json:"customs_agent_name"`
CustomsAgentAddress *string `json:"customs_agent_address"`
Goods []Good `json:"goods"`
CustomsCharges []CustomsCharge `json:"customs_charges"`
TotalCustomsCharges *Currency `json:"total_customs_charges"`
PaymentStatus *string `json:"payment_status"`
}
type ParseRunOutput struct {
Chunks []struct {
Content string `json:"content"`
} `json:"chunks"`
}
type ParseRun struct {
Status string `json:"status"`
Output ParseRunOutput `json:"output"`
}
type ExtractRunOutput struct {
Value CustomsClearanceBillData `json:"value"`
}
type ExtractRun struct {
Status string `json:"status"`
Output ExtractRunOutput `json:"output"`
}
type ProcessResult struct {
Success bool `json:"success"`
BillData CustomsClearanceBillData `json:"billData"`
ParseOutput ParseRunOutput `json:"parseOutput"`
ExtractOutput ExtractRunOutput `json:"extractOutput"`
}
// Helper to make polling requests to Extend API
func createAndPoll(method, endpoint string, requestBody interface{}, apiKey string, resultType string) (json.RawMessage, error) {
client := &http.Client{}
baseURL := "https://api.extend.ai"
url := baseURL + endpoint
body, err := json.Marshal(requestBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
req, err := http.NewRequest(method, url, bytes.NewReader(body))
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")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(respBody))
}
// Simple polling: return the response as-is (caller will check status)
return respBody, nil
}
func processCustomClearanceBill(filePath string) (*ProcessResult, error) {
apiKey := os.Getenv("EXTEND_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("EXTEND_API_KEY environment variable not set")
}
// Read file and convert to data URL
fileBuffer, err := ioutil.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))
fmt.Println("Starting Custom Clearance Bill processing pipeline...")
fmt.Printf("Input file: %s\n", filePath)
// Step 1: Parse the document
fmt.Println("\n[Step 1] Parsing document with agentic OCR...")
parseReqBody := 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",
},
},
}
parseResp, err := createAndPoll("POST", "/v1/parse_runs", parseReqBody, apiKey, "parse")
if err != nil {
return nil, fmt.Errorf("parse request failed: %w", err)
}
var parseRun ParseRun
if err := json.Unmarshal(parseResp, &parseRun); err != nil {
return nil, fmt.Errorf("failed to unmarshal parse response: %w", err)
}
if parseRun.Status != "PROCESSED" {
return nil, fmt.Errorf("parse failed with status: %s", parseRun.Status)
}
fmt.Printf("✓ Parsing complete. %d chunks extracted.\n", len(parseRun.Output.Chunks))
// Log markdown preview
var markdown strings.Builder
for i, chunk := range parseRun.Output.Chunks {
if i > 0 {
markdown.WriteString("\n\n")
}
markdown.WriteString(chunk.Content)
}
mdStr := markdown.String()
if len(mdStr) > 500 {
mdStr = mdStr[:500]
}
fmt.Println("\n--- Parsed Markdown (first 500 chars) ---")
fmt.Println(mdStr)
fmt.Println("--- End preview ---\n")
// Step 2: Extract structured data
fmt.Println("[Step 2] Extracting structured fields with review agent...")
schema := map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"invoice_number": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The unique identifier assigned to this customs clearance invoice or bill.",
},
"invoice_date": map[string]interface{}{
"type": []interface{}{"string", "null"},
"extend:type": "date",
"description": "The date when this customs clearance invoice or bill was issued.",
},
"customs_declaration_number": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The unique identifier for the customs declaration associated with this shipment.",
},
"shipper_name": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The name of the company or entity responsible for shipping or exporting the goods.",
},
"consignee_name": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The name of the company or entity receiving the goods.",
},
"origin_country": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The country from which the goods are being shipped or exported.",
},
"destination_country": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The country to which the goods are being shipped or imported.",
},
"customs_agent_name": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The name of the customs clearance agent, broker, or department.",
},
"customs_agent_address": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The address of the customs clearance agent, broker, or department.",
},
"goods": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"description": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "A description of the good or product.",
},
"quantity": map[string]interface{}{
"type": []interface{}{"number", "null"},
"description": "The quantity or amount of this good or product being shipped.",
},
},
"required": []string{"description", "quantity"},
"additionalProperties": false,
},
"description": "A list of goods or products included in this customs clearance invoice.",
},
"customs_charges": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"charge_type": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The type or description of the customs charge.",
},
"charge_amount": map[string]interface{}{
"type": "object",
"extend:type": "currency",
"properties": map[string]interface{}{
"amount": map[string]interface{}{
"type": []interface{}{"number", "null"},
},
"iso_4217_currency_code": map[string]interface{}{
"type": []interface{}{"string", "null"},
},
},
"required": []string{"amount", "iso_4217_currency_code"},
"additionalProperties": false,
"description": "The monetary amount for this specific customs charge.",
},
},
"required": []string{"charge_type", "charge_amount"},
"additionalProperties": false,
},
"description": "A list of individual customs-related charges or fees applied to this shipment.",
},
"total_customs_charges": map[string]interface{}{
"type": "object",
"extend:type": "currency",
"properties": map[string]interface{}{
"amount": map[string]interface{}{
"type": []interface{}{"number", "null"},
},
"iso_4217_currency_code": map[string]interface{}{
"type": []interface{}{"string", "null"},
},
},
"required": []string{"amount", "iso_4217_currency_code"},
"additionalProperties": false,
"description": "The total amount of all customs-related charges.",
},
"payment_status": map[string]interface{}{
"type": []interface{}{"string", "null"},
"description": "The current payment status of the customs clearance invoice.",
},
},
"required": []string{
"invoice_number", "invoice_date", "customs_declaration_number",
"shipper_name", "consignee_name", "origin_country", "destination_country",
"customs_agent_name", "customs_agent_address", "goods", "customs_charges",
"total_customs_charges", "payment_status",
},
"additionalProperties": false,
}
extractReqBody := map[string]interface{}{
"file": map[string]string{
"url": dataURL,
},
"config": map[string]interface{}{
"schema": schema,
"baseProcessor": "extraction_performance",
"advancedOptions": map[string]interface{}{
"reviewAgent": map[string]bool{
"enabled": true,
},
"advancedMultimodalEnabled": true,
},
},
}
extractResp, err := createAndPoll("POST", "/v1/extract_runs", extractReqBody, apiKey, "extract")
if err != nil {
return nil, fmt.Errorf("extract request failed: %w", err)
}
var extractRun ExtractRun
if err := json.Unmarshal(extractResp, &extractRun); err != nil {
return nil, fmt.Errorf("failed to unmarshal extract response: %w", err)
}
if extractRun.Status != "PROCESSED" {
return nil, fmt.Errorf("extraction failed with status: %s", extractRun.Status)
}
fmt.Println("✓ Extraction complete with review agent validation.\n")
billData := extractRun.Output.Value
// Display extracted data
fmt.Println("=== EXTRACTED CUSTOMS CLEARANCE BILL DATA ===\n")
fmt.Println("Document Identifiers:")
fmt.Printf(" Invoice Number: %v\n", billData.InvoiceNumber)
fmt.Printf(" Invoice Date: %v\n", billData.InvoiceDate)
fmt.Printf(" Declaration Number: %v\n", billData.CustomsDeclarationNumber)
fmt.Println("\nParties:")
fmt.Printf(" Shipper: %v\n", billData.ShipperName)
fmt.Printf(" Consignee: %v\n", billData.ConsigneeName)
fmt.Println("\nLocations:")
fmt.Printf(" Origin: %v\n", billData.OriginCountry)
fmt.Printf(" Destination: %v\n", billData.DestinationCountry)
fmt.Println("\nCustoms Agent:")
fmt.Printf(" Name: %v\n", billData.CustomsAgentName)
fmt.Printf(" Address: %v\n", billData.CustomsAgentAddress)
fmt.Println("\nGoods:")
if len(billData.Goods) > 0 {
for idx, item := range billData.Goods {
desc := "N/A"
if item.Description != nil {
desc = *item.Description
}
qty := "N/A"
if item.Quantity != nil {
qty = fmt.Sprintf("%v", *item.Quantity)
}
fmt.Printf(" [%d] %s (Qty: %s)\n", idx+1, desc, qty)
}
} else {
fmt.Println(" (No goods extracted)")
}
fmt.Println("\nCustoms Charges:")
if len(billData.CustomsCharges) > 0 {
for _, charge := range billData.CustomsCharges {
chargeType := "N/A"
if charge.ChargeType != nil {
chargeType = *charge.ChargeType
}
amt := "N/A"
curr := "N/A"
if charge.ChargeAmount != nil {
if charge.ChargeAmount.Amount != nil {
amt = fmt.Sprintf("%v", *charge.ChargeAmount.Amount)
}
if charge.ChargeAmount.ISO4217Code != nil {
curr = *charge.ChargeAmount.ISO4217Code
}
}
fmt.Printf(" %s: %s %s\n", chargeType, amt, curr)
}
} else {
fmt.Println(" (No charges extracted)")
}
fmt.Println("\nFinancial Summary:")
totalAmt := "N/A"
totalCurr := "N/A"
if billData.TotalCustomsCharges != nil {
if billData.TotalCustomsCharges.Amount != nil {
totalAmt = fmt.Sprintf("%v", *billData.TotalCustomsCharges.Amount)
}
if billData.TotalCustomsCharges.ISO4217Code != nil {
totalCurr = *billData.TotalCustomsCharges.ISO4217Code
}
}
fmt.Printf(" Total Customs Charges: %s %s\n", totalAmt, totalCurr)
fmt.Printf(" Payment Status: %v\n", billData.PaymentStatus)
return &ProcessResult{
Success: true,
BillData: billData,
ParseOutput: parseRun.Output,
ExtractOutput: extractRun.Output,
}, nil
}
func main() {
filePath := "./sample_clearance_bill.pdf"
if len(os.Args) > 1 {
filePath = os.Args[1]
}
result, err := processCustomClearanceBill(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "\n✗ Pipeline failed: %v\n", err)
os.Exit(1)
}
fmt.Println("\n✓ Processing complete. Data ready for downstream systems.")
os.Exit(0)
}// Deploy the "Custom Clearance Bill" 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/customs-clearance-invoice.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: customs-clearance-invoice).
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, "customs-clearance-invoice.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": "Custom Clearance Bill 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": [
"goods",
"invoice_date",
"shipper_name",
"consignee_name",
"invoice_number",
"origin_country",
"payment_status",
"customs_charges",
"customs_agent_name",
"destination_country",
"customs_agent_address",
"total_customs_charges",
"customs_declaration_number"
],
"properties": {
"goods": {
"type": "array",
"items": {
"type": "object",
"required": [
"quantity",
"description"
],
"properties": {
"quantity": {
"type": [
"number",
"null"
],
"description": "The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures."
},
"description": {
"type": [
"string",
"null"
],
"description": "A description of the good or product, such as its type, model, or material. May include product codes or trade names."
}
},
"additionalProperties": false
},
"description": "A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail."
},
"invoice_date": {
"type": [
"string",
"null"
],
"description": "The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar.",
"extend:type": "date"
},
"shipper_name": {
"type": [
"string",
"null"
],
"description": "The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction."
},
"consignee_name": {
"type": [
"string",
"null"
],
"description": "The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient."
},
"invoice_number": {
"type": [
"string",
"null"
],
"description": "The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary."
},
"origin_country": {
"type": [
"string",
"null"
],
"description": "The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar."
},
"payment_status": {
"type": [
"string",
"null"
],
"description": "The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp."
},
"customs_charges": {
"type": "array",
"items": {
"type": "object",
"required": [
"charge_type",
"charge_amount"
],
"properties": {
"charge_type": {
"type": [
"string",
"null"
],
"description": "The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge."
},
"charge_amount": {
"type": "object",
"required": [
"amount",
"iso_4217_currency_code"
],
"properties": {
"amount": {
"type": [
"number",
"null"
]
},
"iso_4217_currency_code": {
"type": [
"string",
"null"
]
}
},
"description": "The monetary amount for this specific customs charge.",
"extend:type": "currency",
"additionalProperties": false
}
},
"additionalProperties": false
},
"description": "A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge."
},
"customs_agent_name": {
"type": [
"string",
"null"
],
"description": "The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar."
},
"destination_country": {
"type": [
"string",
"null"
],
"description": "The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar."
},
"customs_agent_address": {
"type": [
"string",
"null"
],
"description": "The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details."
},
"total_customs_charges": {
"type": "object",
"required": [
"amount",
"iso_4217_currency_code"
],
"properties": {
"amount": {
"type": [
"number",
"null"
]
},
"iso_4217_currency_code": {
"type": [
"string",
"null"
]
}
},
"description": "The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar.",
"extend:type": "currency",
"additionalProperties": false
},
"customs_declaration_number": {
"type": [
"string",
"null"
],
"description": "The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar."
}
},
"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); });
#!/usr/bin/env python3
"""
Deploy the "Custom Clearance Bill" 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/customs-clearance-invoice.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)
python provision.py
Generated by doc1 (template: customs-clearance-invoice).
"""
import json
import os
from pathlib import Path
from extend_ai import Extend
STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "customs-clearance-invoice.json"
WORKFLOW = {
"name": "Custom Clearance Bill 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": [
"goods",
"invoice_date",
"shipper_name",
"consignee_name",
"invoice_number",
"origin_country",
"payment_status",
"customs_charges",
"customs_agent_name",
"destination_country",
"customs_agent_address",
"total_customs_charges",
"customs_declaration_number",
],
"properties": {
"goods": {
"type": "array",
"items": {
"type": "object",
"required": ["quantity", "description"],
"properties": {
"quantity": {
"type": ["number", "null"],
"description": "The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures.",
},
"description": {
"type": ["string", "null"],
"description": "A description of the good or product, such as its type, model, or material. May include product codes or trade names.",
},
},
"additionalProperties": False,
},
"description": "A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail.",
},
"invoice_date": {
"type": ["string", "null"],
"description": "The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar.",
"extend:type": "date",
},
"shipper_name": {
"type": ["string", "null"],
"description": "The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction.",
},
"consignee_name": {
"type": ["string", "null"],
"description": "The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient.",
},
"invoice_number": {
"type": ["string", "null"],
"description": "The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary.",
},
"origin_country": {
"type": ["string", "null"],
"description": "The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar.",
},
"payment_status": {
"type": ["string", "null"],
"description": "The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp.",
},
"customs_charges": {
"type": "array",
"items": {
"type": "object",
"required": ["charge_type", "charge_amount"],
"properties": {
"charge_type": {
"type": ["string", "null"],
"description": "The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge.",
},
"charge_amount": {
"type": "object",
"required": ["amount", "iso_4217_currency_code"],
"properties": {
"amount": {"type": ["number", "null"]},
"iso_4217_currency_code": {"type": ["string", "null"]},
},
"description": "The monetary amount for this specific customs charge.",
"extend:type": "currency",
"additionalProperties": False,
},
},
"additionalProperties": False,
},
"description": "A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge.",
},
"customs_agent_name": {
"type": ["string", "null"],
"description": "The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar.",
},
"destination_country": {
"type": ["string", "null"],
"description": "The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar.",
},
"customs_agent_address": {
"type": ["string", "null"],
"description": "The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details.",
},
"total_customs_charges": {
"type": "object",
"required": ["amount", "iso_4217_currency_code"],
"properties": {
"amount": {"type": ["number", "null"]},
"iso_4217_currency_code": {"type": ["string", "null"]},
},
"description": "The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar.",
"extend:type": "currency",
"additionalProperties": False,
},
"customs_declaration_number": {
"type": ["string", "null"],
"description": "The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar.",
},
},
"additionalProperties": False,
},
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {"enabled": True},
"advancedMultimodalEnabled": True,
},
}
},
},
],
}
def load_state() -> dict:
"""Load workflow state from file if it exists."""
if STATE_FILE.exists():
with open(STATE_FILE, "r") as f:
return json.load(f)
return {}
def save_state(state: dict) -> None:
"""Save workflow state to file."""
STATE_DIR.mkdir(parents=True, exist_ok=True)
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2)
def main() -> None:
"""Provision the Custom Clearance Bill workflow."""
api_key = os.environ.get("EXTEND_API_KEY")
if not api_key:
print("Error: Set EXTEND_API_KEY first.")
exit(1)
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")
client.workflows.update(workflow_id, {"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.
existing_workflow = None
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 [])
for wf in items:
if wf.get("name") == WORKFLOW["name"] or (hasattr(wf, "name") and wf.name == WORKFLOW["name"]):
workflow_id = wf.get("id") or getattr(wf, "id", None)
if workflow_id:
existing_workflow = workflow_id
break
except Exception:
# lookup is best-effort; fall through to create
pass
if existing_workflow:
state["workflowId"] = existing_workflow
save_state(state)
print(f'✓ workflow "{WORKFLOW["name"]}" found in your account ({existing_workflow}) — updating steps')
client.workflows.update(existing_workflow, {"steps": WORKFLOW["steps"]})
else:
created = client.workflows.create(WORKFLOW)
workflow_id = created.get("id") or getattr(created, "id", None)
if not workflow_id:
workflow_data = created.get("workflow")
if workflow_data:
workflow_id = workflow_data.get("id") or getattr(workflow_data, "id", None)
if not workflow_id:
raise RuntimeError("Could not read created workflow id from response")
state["workflowId"] = workflow_id
save_state(state)
print(f"+ created workflow ({workflow_id})")
# Deploy the current draft as a new version so the workflow is runnable —
# best-effort: some accounts/plans may not require this explicit step.
try:
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__":
try:
main()
except Exception as e:
print(f"Error: {e}")
exit(1)// This Java code calls Extend's REST API directly (https://api.extend.ai)
// because Extend has no official Java SDK yet. It mirrors the TypeScript
// reference's operations exactly using only java.net.http.HttpClient.
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;
public class ProvisionCustomsClearance {
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("customs-clearance-invoice.json");
static {
if (API_KEY == null || API_KEY.isEmpty()) {
System.err.println("Set EXTEND_API_KEY first.");
System.exit(1);
}
}
static class State {
String workflowId;
}
private static State state = new State();
private static final HttpClient httpClient = HttpClient.newHttpClient();
static {
try {
if (Files.exists(STATE_FILE)) {
String content = Files.readString(STATE_FILE, StandardCharsets.UTF_8);
state.workflowId = parseWorkflowIdFromJson(content);
}
} catch (IOException e) {
// Continue with empty state
}
}
private static String parseWorkflowIdFromJson(String json) {
int idx = json.indexOf("\"workflowId\":");
if (idx == -1) return null;
int start = json.indexOf("\"", idx + 13) + 1;
int end = json.indexOf("\"", start);
return json.substring(start, end);
}
private static void saveState() throws IOException {
Files.createDirectories(STATE_DIR);
String json = "{\"workflowId\": \"" + state.workflowId + "\"}\n";
Files.writeString(STATE_FILE, json, StandardCharsets.UTF_8);
}
private static String api(String method, String pathName, String body) throws IOException, InterruptedException {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(API + pathName))
.header("Authorization", "Bearer " + API_KEY)
.header("x-extend-api-version", VERSION);
if (body != null) {
requestBuilder.header("Content-Type", "application/json")
.method(method, HttpRequest.BodyPublishers.ofString(body));
} else {
requestBuilder.method(method, HttpRequest.BodyPublishers.noBody());
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
String respBody = response.body().length() > 300
? response.body().substring(0, 300)
: response.body();
throw new IOException(method + " " + pathName + " failed (" + response.statusCode() + "): " + respBody);
}
return response.body();
}
private static String buildWorkflowJson() {
return "{"
+ "\"name\":\"Custom Clearance Bill 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\":[\"goods\",\"invoice_date\",\"shipper_name\",\"consignee_name\",\"invoice_number\",\"origin_country\",\"payment_status\",\"customs_charges\",\"customs_agent_name\",\"destination_country\",\"customs_agent_address\",\"total_customs_charges\",\"customs_declaration_number\"],\"properties\":{\"goods\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"quantity\",\"description\"],\"properties\":{\"quantity\":{\"type\":[\"number\",\"null\"],\"description\":\"The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures.\"},\"description\":{\"type\":[\"string\",\"null\"],\"description\":\"A description of the good or product, such as its type, model, or material. May include product codes or trade names.\"}},\"additionalProperties\":false},\"description\":\"A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail.\"},\"invoice_date\":{\"type\":[\"string\",\"null\"],\"description\":\"The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar.\",\"extend:type\":\"date\"},\"shipper_name\":{\"type\":[\"string\",\"null\"],\"description\":\"The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction.\"},\"consignee_name\":{\"type\":[\"string\",\"null\"],\"description\":\"The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient.\"},\"invoice_number\":{\"type\":[\"string\",\"null\"],\"description\":\"The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary.\"},\"origin_country\":{\"type\":[\"string\",\"null\"],\"description\":\"The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar.\"},\"payment_status\":{\"type\":[\"string\",\"null\"],\"description\":\"The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp.\"},\"customs_charges\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"required\":[\"charge_type\",\"charge_amount\"],\"properties\":{\"charge_type\":{\"type\":[\"string\",\"null\"],\"description\":\"The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge.\"},\"charge_amount\":{\"type\":\"object\",\"required\":[\"amount\",\"iso_4217_currency_code\"],\"properties\":{\"amount\":{\"type\":[\"number\",\"null\"]},\"iso_4217_currency_code\":{\"type\":[\"string\",\"null\"]}},\"description\":\"The monetary amount for this specific customs charge.\",\"extend:type\":\"currency\",\"additionalProperties\":false}},\"additionalProperties\":false},\"description\":\"A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge.\"},\"customs_agent_name\":{\"type\":[\"string\",\"null\"],\"description\":\"The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar.\"},\"destination_country\":{\"type\":[\"string\",\"null\"],\"description\":\"The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar.\"},\"customs_agent_address\":{\"type\":[\"string\",\"null\"],\"description\":\"The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details.\"},\"total_customs_charges\":{\"type\":\"object\",\"required\":[\"amount\",\"iso_4217_currency_code\"],\"properties\":{\"amount\":{\"type\":[\"number\",\"null\"]},\"iso_4217_currency_code\":{\"type\":[\"string\",\"null\"]}},\"description\":\"The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar.\",\"extend:type\":\"currency\",\"additionalProperties\":false},\"customs_declaration_number\":{\"type\":[\"string\",\"null\"],\"description\":\"The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar.\"}},\"additionalProperties\":false},\"baseProcessor\":\"extraction_performance\",\"advancedOptions\":{\"reviewAgent\":{\"enabled\":true},\"advancedMultimodalEnabled\":true}}}}"
+ "]"
+ "}";
}
public static void main(String[] args) {
try {
String workflowName = "Custom Clearance Bill Processing Pipeline";
System.out.println("Deploying \"" + workflowName + "\"…");
if (state.workflowId != null && !state.workflowId.isEmpty()) {
System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
String stepsBody = "{\"steps\":" + buildWorkflowJson().substring(buildWorkflowJson().indexOf("\"steps\":") + 8) + "}";
api("POST", "/workflows/" + state.workflowId, stepsBody);
} else {
// Try to find existing workflow with same name
try {
String encoded = URLEncoder.encode(workflowName, StandardCharsets.UTF_8);
String listResponse = api("GET", "/workflows?name=" + encoded, null);
String existingId = extractExistingWorkflowId(listResponse, workflowName);
if (existingId != null && !existingId.isEmpty()) {
state.workflowId = existingId;
saveState();
System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + existingId + ") — updating steps");
String stepsBody = "{\"steps\":" + buildWorkflowJson().substring(buildWorkflowJson().indexOf("\"steps\":") + 8) + "}";
api("POST", "/workflows/" + existingId, stepsBody);
}
} catch (Exception e) {
// Lookup is best-effort; fall through to create
}
if (state.workflowId == null || state.workflowId.isEmpty()) {
String createResponse = api("POST", "/workflows", buildWorkflowJson());
String wfId = extractCreatedWorkflowId(createResponse);
if (wfId == null || wfId.isEmpty()) {
throw new IOException("Could not read created workflow id from response");
}
state.workflowId = wfId;
saveState();
System.out.println("+ created workflow (" + wfId + ")");
}
}
// Deploy as a new version (best-effort)
try {
api("POST", "/workflows/" + state.workflowId + "/versions", "{}");
} catch (Exception e) {
// Ignore; some accounts may not require this
}
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 String extractCreatedWorkflowId(String json) {
int idIdx = json.indexOf("\"id\":");
if (idIdx == -1) {
int wfIdx = json.indexOf("\"workflow\":");
if (wfIdx == -1) return null;
idIdx = json.indexOf("\"id\":", wfIdx);
if (idIdx == -1) return null;
}
int start = json.indexOf("\"", idIdx + 5);
if (start == -1) {
start = idIdx + 5;
while (start < json.length() && (json.charAt(start) == ' ' || json.charAt(start) == ':')) start++;
if (start >= json.length()) return null;
} else {
start++;
}
int end = start;
while (end < json.length() && json.charAt(end) != '"' && json.charAt(end) != ',' && json.charAt(end) != '}') end++;
return end > start ? json.substring(start, end).trim() : null;
}
private static String extractExistingWorkflowId(String json, String workflowName) {
int dataIdx = json.indexOf("\"data\":");
int itemsIdx = json.indexOf("\"items\":");
int searchIdx = (dataIdx != -1) ? dataIdx : itemsIdx;
if (searchIdx == -1) return null;
int nameIdx = json.indexOf("\"" + workflowName + "\"", searchIdx);
if (nameIdx == -1) return null;
int idIdx = json.lastIndexOf("\"id\":", nameIdx);
if (idIdx == -1) return null;
int start = json.indexOf("\"", idIdx + 5) + 1;
int end = json.indexOf("\"", start);
return json.substring(start, end);
}
}// This script uses Extend's REST API directly because Extend has no official Go SDK yet.
// It deploys the "Custom Clearance Bill" 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: customs-clearance-invoice).
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"`
}
type CurrencyAmount struct {
Amount *float64 `json:"amount"`
ISO4217CurrencyCode *string `json:"iso_4217_currency_code"`
}
type Good struct {
Quantity *float64 `json:"quantity"`
Description *string `json:"description"`
}
type CustomsCharge struct {
ChargeType *string `json:"charge_type"`
ChargeAmount CurrencyAmount `json:"charge_amount"`
}
type ExtractorSchema struct {
Type string `json:"type"`
Required []string `json:"required"`
Properties map[string]interface{} `json:"properties"`
AdditionalProperties bool `json:"additionalProperties"`
}
type ExtractorConfig struct {
Schema ExtractorSchema `json:"schema"`
BaseProcessor string `json:"baseProcessor"`
AdvancedOptions map[string]interface{} `json:"advancedOptions"`
}
type ExtractStep struct {
Name string `json:"name"`
Type string `json:"type"`
Config struct {
ExtractorConfig ExtractorConfig `json:"extractorConfig"`
} `json:"config"`
}
type ParseStep struct {
Name string `json:"name"`
Type string `json:"type"`
Config struct {
ParseConfig struct {
BlockOptions struct {
Text struct {
Agentic struct {
Enabled bool `json:"enabled"`
} `json:"agentic"`
} `json:"text"`
} `json:"blockOptions"`
ChunkingStrategy struct {
Type string `json:"type"`
} `json:"chunkingStrategy"`
} `json:"parseConfig"`
} `json:"config"`
Next []struct {
Step string `json:"step"`
} `json:"next"`
}
type TriggerStep struct {
Name string `json:"name"`
Type string `json:"type"`
Next []struct {
Step string `json:"step"`
} `json:"next"`
}
type WorkflowDef struct {
Name string `json:"name"`
Steps []interface{} `json:"steps"`
}
type APIResponse struct {
ID string `json:"id"`
Workflow struct {
ID string `json:"id"`
} `json:"workflow"`
Data []WorkflowItem `json:"data"`
Items []WorkflowItem `json:"items"`
}
type WorkflowItem struct {
Name string `json:"name"`
ID string `json:"id"`
}
type StepsUpdateRequest struct {
Steps []interface{} `json:"steps"`
}
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, "customs-clearance-invoice.json")
data, err := os.ReadFile(stateFile)
if 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{}) (interface{}, error) {
var reqBody io.Reader
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, err
}
reqBody = bytes.NewBuffer(jsonData)
}
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()
respData, err := io.ReadAll(resp.Body)
if err != nil {
respData = []byte("{}")
}
var result interface{}
json.Unmarshal(respData, &result)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
if len(respData) > 300 {
respData = respData[:300]
}
return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, string(respData))
}
return result, nil
}
func buildWorkflow() WorkflowDef {
trigger := TriggerStep{
Name: "startTrigger1",
Type: "TRIGGER",
Next: []struct {
Step string `json:"step"`
}{
{Step: "parse1"},
},
}
parse := ParseStep{
Name: "parse1",
Type: "PARSE",
Next: []struct {
Step string `json:"step"`
}{
{Step: "extraction2"},
},
}
parse.Config.ParseConfig.BlockOptions.Text.Agentic.Enabled = true
parse.Config.ParseConfig.ChunkingStrategy.Type = "document"
properties := map[string]interface{}{
"goods": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"required": []string{"quantity", "description"},
"properties": map[string]interface{}{
"quantity": map[string]interface{}{
"type": []string{"number", "null"},
"description": "The quantity or amount of this good or product being shipped. May be expressed in units, weight, or other relevant measures.",
},
"description": map[string]interface{}{
"type": []string{"string", "null"},
"description": "A description of the good or product, such as its type, model, or material. May include product codes or trade names.",
},
},
"additionalProperties": false,
},
"description": "A list of goods or products included in this customs clearance invoice. Each item represents a distinct product or material being imported or exported, typically including a description and quantity. Formats may vary, and not all documents will include every possible detail.",
},
"invoice_date": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The date when this customs clearance invoice or bill was issued. This is the official date of the document, used for record-keeping and payment calculations. May appear with labels such as 'Date', 'Invoice Date', or similar.",
"extend:type": "date",
},
"shipper_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The name of the company or entity responsible for shipping or exporting the goods. This is typically the sender or consignor in the transaction.",
},
"consignee_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The name of the company or entity receiving the goods. This is typically the buyer, importer, or recipient.",
},
"invoice_number": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The unique identifier assigned to this customs clearance invoice or bill. This is the primary reference number for the transaction and may include numbers, letters, or special characters. Common labels include 'Invoice Number', 'Bill Number', or similar, but terminology and placement may vary.",
},
"origin_country": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The country from which the goods are being shipped or exported. This is the starting point of the shipment and may be labeled as 'From', 'Origin', or similar.",
},
"payment_status": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The current payment status of the customs clearance invoice, indicating whether the charges have been paid, are unpaid, or partially paid. Common values include 'PAID', 'UNPAID', 'PARTIALLY PAID', etc. May appear as a label or stamp.",
},
"customs_charges": map[string]interface{}{
"type": "array",
"items": map[string]interface{}{
"type": "object",
"required": []string{"charge_type", "charge_amount"},
"properties": map[string]interface{}{
"charge_type": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The type or description of the customs charge, such as 'Import Duty', 'VAT', 'Processing Fee', 'Inspection Fee', etc. This identifies the nature of the charge.",
},
"charge_amount": map[string]interface{}{
"type": "object",
"required": []string{"amount", "iso_4217_currency_code"},
"properties": map[string]interface{}{
"amount": map[string]interface{}{
"type": []string{"number", "null"},
},
"iso_4217_currency_code": map[string]interface{}{
"type": []string{"string", "null"},
},
},
"description": "The monetary amount for this specific customs charge.",
"extend:type": "currency",
"additionalProperties": false,
},
},
"additionalProperties": false,
},
"description": "A list of individual customs-related charges or fees applied to this shipment. Each item represents a specific duty, tax, or fee, and typically includes a description and amount. Formats may vary, and not all documents will include every possible charge.",
},
"customs_agent_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The name of the customs clearance agent, broker, or department responsible for handling the customs process. May be labeled as 'Customs Clearance Department', 'Agent', or similar.",
},
"destination_country": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The country to which the goods are being shipped or imported. This is the final destination of the shipment and may be labeled as 'To', 'Destination', or similar.",
},
"customs_agent_address": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The address of the customs clearance agent, broker, or department responsible for handling the customs process. May include street, city, and country details.",
},
"total_customs_charges": map[string]interface{}{
"type": "object",
"required": []string{"amount", "iso_4217_currency_code"},
"properties": map[string]interface{}{
"amount": map[string]interface{}{
"type": []string{"number", "null"},
},
"iso_4217_currency_code": map[string]interface{}{
"type": []string{"string", "null"},
},
},
"description": "The total amount of all customs-related charges, including duties, taxes, processing fees, and other applicable costs. This is the final sum owed for customs clearance. May be labeled as 'Total Customs Charges', 'Total Due', or similar.",
"extend:type": "currency",
"additionalProperties": false,
},
"customs_declaration_number": map[string]interface{}{
"type": []string{"string", "null"},
"description": "The unique identifier for the customs declaration associated with this shipment. This number is used by customs authorities to track the clearance process. May be labeled as 'Customs Declaration Number', 'Declaration No.', or similar.",
},
}
extract := ExtractStep{
Name: "extraction2",
Type: "EXTRACT",
}
extract.Config.ExtractorConfig.Schema.Type = "object"
extract.Config.ExtractorConfig.Schema.Required = []string{
"goods", "invoice_date", "shipper_name", "consignee_name", "invoice_number",
"origin_country", "payment_status", "customs_charges", "customs_agent_name",
"destination_country", "customs_agent_address", "total_customs_charges",
"customs_declaration_number",
}
extract.Config.ExtractorConfig.Schema.Properties = properties
extract.Config.ExtractorConfig.Schema.AdditionalProperties = false
extract.Config.ExtractorConfig.BaseProcessor = "extraction_performance"
extract.Config.ExtractorConfig.AdvancedOptions = map[string]interface{}{
"reviewAgent": map[string]interface{}{
"enabled": true,
},
"advancedMultimodalEnabled": true,
}
return WorkflowDef{
Name: "Custom Clearance Bill Processing Pipeline",
Steps: []interface{}{
trigger,
parse,
extract,
},
}
}
func main() {
workflow := buildWorkflow()
fmt.Printf("Deploying \"%s\"…\n", workflow.Name)
if state.WorkflowID != "" {
fmt.Printf("✓ workflow already provisioned (%s) — updating steps\n", state.WorkflowID)
_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", state.WorkflowID), StepsUpdateRequest{Steps: workflow.Steps})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
} else {
found := false
query := url.QueryEscape(workflow.Name)
resp, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", query), nil)
if err == nil {
respMap, ok := resp.(map[string]interface{})
if ok {
var items []WorkflowItem
if data, exists := respMap["data"].([]interface{}); exists {
for _, item := range data {
if itemMap, ok := item.(map[string]interface{}); ok {
var wi WorkflowItem
jsonData, _ := json.Marshal(itemMap)
json.Unmarshal(jsonData, &wi)
items = append(items, wi)
}
}
} else if itemsRaw, exists := respMap["items"].([]interface{}); exists {
for _, item := range itemsRaw {
if itemMap, ok := item.(map[string]interface{}); ok {
var wi WorkflowItem
jsonData, _ := json.Marshal(itemMap)
json.Unmarshal(jsonData, &wi)
items = append(items, wi)
}
}
}
for _, item := range items {
if item.Name == workflow.Name && item.ID != "" {
state.WorkflowID = item.ID
saveState()
fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", workflow.Name, item.ID)
apiCall("POST", fmt.Sprintf("/workflows/%s", item.ID), StepsUpdateRequest{Steps: workflow.Steps})
found = true
break
}
}
}
}
if !found {
resp, err := apiCall("POST", "/workflows", workflow)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
respMap := resp.(map[string]interface{})
wfID := ""
if id, exists := respMap["id"].(string); exists && id != "" {
wfID = id
} else if wfObj, exists := respMap["workflow"].(map[string]interface{}); exists {
if id, ok := wfObj["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)
}
}
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.")
}A Custom Clearance Bill is used for international imports, detailing goods being cleared through customs, itemized quantities, origin/destination information, and calculated customs charges including duties, fees, VAT, and inspection costs. This extractor captures this information and organizes it into a structured JSON output.