Extracts shipment delivery confirmation details and recipient signatures from logistics documents.
A proof of delivery is a logistics document issued by a carrier or delivery service that records the receipt of goods at a destination, including shipment details, delivery timestamp, recipient confirmation, and quantity verification for supply chain tracking and delivery accountability. This template takes in Proof of Delivery and outputs markdown (.md) preserving the document's original layout and full text content, and JSON (.json) with structured delivery fields including receipt number, supplier, destination, goods details, quantities, delivery timestamp, and recipient information per the extraction schema by using Extend's Parse, Extract primitives.
Converts the document into clean, layout-aware markdown plus structured blocks with spatial metadata.
blockOptions.text.agentic.enabledtruechangedchunkingStrategy.type"document"engine"parse_performance"You can learn more about Parse configuration in Extend's Parse documentation.
Pulls a defined set of fields from the document and returns them as structured JSON matching a schema.
schemacustom schema — 12 fieldschangedadvancedOptions.advancedMultimodalEnabledtruechangedadvancedOptions.reviewAgent.enabledtruechangedbaseProcessor"extraction_performance"You can learn more about Extract configuration in Extend's Extract documentation.
{
"name": "Proof of Delivery Processing Pipeline",
"steps": [
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": [
{
"step": "parse1"
}
]
},
{
"name": "parse1",
"type": "PARSE",
"config": {
"parseConfig": {
"blockOptions": {
"text": {
"agentic": {
"enabled": true
}
}
},
"chunkingStrategy": {
"type": "document"
}
}
},
"next": [
{
"step": "extraction2"
}
]
},
{
"name": "extraction2",
"type": "EXTRACT",
"config": {
"extractorConfig": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"type": [
"string",
"null"
],
"description": "Name or details of the supplier/shipper"
},
"destination": {
"type": [
"string",
"null"
],
"description": "Delivery destination address"
},
"received_by": {
"type": [
"string",
"null"
],
"description": "Name and signature of the recipient"
},
"company_name": {
"type": [
"string",
"null"
],
"description": "Name of the logistics company issuing the proof of delivery"
},
"order_number": {
"type": [
"string",
"null"
],
"description": "Purchase or shipment order number"
},
"document_type": {
"type": [
"string",
"null"
],
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'"
},
"goods_details": {
"type": [
"array",
"null"
],
"description": "List of delivered items with descriptions"
},
"receipt_number": {
"type": [
"string",
"null"
],
"description": "Unique receipt or tracking number for the delivery"
},
"total_quantity": {
"type": [
"string",
"null"
],
"description": "Total quantity of items in the shipment"
},
"delivered_quantity": {
"type": [
"string",
"null"
],
"description": "Actual quantity of items delivered"
},
"delivery_date_time": {
"type": [
"string",
"null"
],
"description": "Date and time when the goods were delivered"
},
"delivering_agency_person": {
"type": [
"string",
"null"
],
"description": "Name and signature of the delivering agency representative"
}
}
},
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {
"enabled": true
},
"advancedMultimodalEnabled": true
}
}
}
}
]
}# Proof of Delivery Processing — Extend AI Skill
## What this pipeline does
This pipeline processes proof of delivery (POD) documents issued by logistics companies to extract and verify shipment receipt data. It parses the document into markdown using agentic OCR (handling handwriting, stamps, and complex layouts), then extracts 11 structured fields including supplier/destination details, delivery timestamps, quantity reconciliation, and recipient signatures into JSON for supply chain systems.
## When to use this
- **Last-mile delivery verification**: Automatically capture signed POD records and feed them into dispatch/tracking systems without manual data entry.
- **Discrepancy detection**: Compare `total_quantity` vs `delivered_quantity` to flag shortages or damage in real-time.
- **Compliance & audit**: Archive structured POD metadata (receipt number, delivery time, recipient name) for regulatory records.
- **Multi-carrier normalization**: Ingest PODs from multiple logistics providers (each with different layouts) and output uniform JSON.
- **Exception handling**: Flag documents where `received_by` signature is missing or illegible, routing them to manual review.
## Processor pipeline
### Step 1: Parse (agentic_ocr)
**Processor**: `parse_performance` with agentic OCR enabled
**Purpose**: Convert POD image/PDF to clean markdown, preserving layout structure and handling handwriting/stamps.
**Key config**:
```typescript
blockOptions: { text: { agentic: { enabled: true } } },
chunkingStrategy: { type: "document" }
```
**Why**: PODs often contain handwritten signatures, smudged timestamps, and non-standard layouts. Agentic OCR runs a vision model to recognize context (e.g., "this smudged mark is a signature field") rather than just raw text recognition. Document-level chunking keeps the full receipt structure intact for downstream extraction.
### Step 2: Extract (extraction_performance)
**Processor**: `extraction_performance` with review agent enabled
**Purpose**: Pull 11 fields (company name, receipt number, delivery timestamp, supplier, destination, goods, quantities, recipient/driver signatures) into typed JSON.
**Key config**:
```typescript
baseProcessor: "extraction_performance",
advancedOptions: {
reviewAgent: { enabled: true },
advancedMultimodalEnabled: true
}
```
**Why**: `extraction_performance` is tuned for dense, structured forms with many fields. The review agent double-checks critical fields (dates, quantities, signatures) before returning results. `advancedMultimodalEnabled` uses both text and visual cues (e.g., checkbox states, signature presence) to improve accuracy on printed + handwritten PODs.
---
## TypeScript implementation
---
## CLI equivalent
```bash
# Parse the POD to markdown
extend parse sample-pod.pdf \
--block-options '{"text":{"agentic":{"enabled":true}}}' \
--chunking-strategy document
# Extract structured fields
extend extract sample-pod.pdf \
--schema pod-schema.json \
--base-processor extraction_performance \
--review-agent-enabled true \
--advanced-multimodal-enabled true
```
### pod-schema.json (for CLI)
```json
{
"type": "object",
"properties": {
"company_name": {
"type": ["string", "null"],
"description": "Name of the logistics company issuing the proof of delivery document"
},
"document_type": {
"type": ["string", "null"],
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'"
},
"receipt_number": {
"type": ["string", "null"],
"description": "Unique receipt or tracking number that identifies this delivery"
},
"order_number": {
"type": ["string", "null"],
"description": "Purchase or shipment order number referenced on the POD"
},
"delivery_date_time": {
"type": ["string", "null"],
"description": "Date and time when the goods were physically delivered (ISO format)"
},
"supplier": {
"type": ["string", "null"],
"description": "Name or company details of the supplier or shipper who sent the goods"
},
"destination": {
"type": ["string", "null"],
"description": "Complete delivery destination address including street, city, postal code"
},
"goods_details": {
"type": ["array", "null"],
"items": {
"type": "object",
"properties": {
"description": {
"type": ["string", "null"],
"description": "Item name, SKU, or product description"
},
"quantity": {
"type": ["string", "null"],
"description": "Quantity of this line item shipped"
}
}
},
"description": "List of delivered items with descriptions and quantities"
},
"total_quantity": {
"type": ["string", "null"],
"description": "Total number of items/packages in the shipment according to the order"
},
"delivered_quantity": {
"type": ["string", "null"],
"description": "Actual quantity of items delivered; compare against total_quantity to flag shortages"
},
"received_by": {
"type": ["string", "null"],
"description": "Full name and signature details of the recipient who accepted the delivery"
},
"delivering_agency_person": {
"type": ["string", "null"],
"description": "Name and signature of the logistics driver or agency representative who performed the delivery"
}
}
}
```
---
## Schema
The extraction schema is a flat object with 11 fields plus a nested `goods_details` array:
| Field | Type | Description | Accuracy notes |
|-------|------|-------------|-----------------|
| `company_name` | string \| null | Logistics company name (header/footer) | Often printed, reliable. Null if handwritten or missing. |
| `document_type` | string \| null | "Proof of Delivery", "Shipment Receipt", etc. | Printed label. May vary by region/carrier. |
| `receipt_number` | string \| null | Unique tracking/receipt ID. | **Critical field**: Use for deduplication. Often printed barcode + text. |
| `order_number` | string \| null | Purchase order or shipment order ID. | Handwritten or printed. May reference external systems. |
| `delivery_date_time` | string (ISO 8601) | When goods arrived. | **Critical**: Validate against warehouse/dispatch logs. Format: `2024-01-15T14:30:00Z`. |
| `supplier` | string \| null | Shipper/vendor company. | May be abbreviated. Cross-reference with order system. |
| `destination` | string \| null | Full delivery address. | **Critical for logistics**: Compare against declared destination to flag misdeliveries. |
| `goods_details` | array of objects | Items with description + quantity each. | Handwritten line items. Array can be empty if not itemized. |
| `total_quantity` | string \| null | Total packages/units per order. | Source of truth for quantity reconciliation. |
| `delivered_quantity` | string \| null | Actual items handed over. | **Discrepancy detection**: If < total_quantity, flag shortage/damage. |
| `received_by` | string \| null | Recipient name + signature. | **Critical**: Verify signature is present and legible. Null = no one signed. |
| `delivering_agency_person` | string \| null | Driver/courier name + signature. | Identifies who delivered. Useful for support/tracking disputes. |
**goods_details array structure** (when present):
```json
{
"description": "Widget Part A, SKU 12345",
"quantity": "5"
}
```
---
## Accuracy tips
1. **Signature validation first**: After extraction, check if `received_by` and `delivering_agency_person` are non-null. If either is missing/illegible, route to manual review—unsigned PODs have zero legal standing.
2. **Quantity reconciliation logic**: Build a post-extraction check:
```typescript
if (totalQty > deliveredQty) {
alert(`SHORTAGE: ${totalQty - deliveredQty} unit(s) missing`);
}
```
This catches damage, theft, or packing errors at the instant of receipt.
3. **Date normalization**: PODs use varying date formats (MM/DD/YYYY, DD/MM/YYYY, text like "15th Jan 2024"). Describe `delivery_date_time` explicitly: "Date and time when goods were delivered (ISO format YYYY-MM-DD)" so the model standardizes on parsing. Always validate extracted date against delivery window (±1 day of dispatch date).
4. **Supplier/destination cross-reference**: Add a post-extraction step to validate extracted `supplier` and `destination` against your order database. Flagimport { ExtendClient, extendDate, extendSignature } from "extend-ai";
import { z } from "zod";
import fs from "fs";
import path from "path";
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
export async function processProofOfDelivery(filePath: string) {
console.log(`Processing Proof of Delivery: ${filePath}`);
// Convert local file to data URL for SDK
const fileBuffer = fs.readFileSync(filePath);
const dataUrl = `data:application/octet-stream;base64,${fileBuffer.toString("base64")}`;
// Step 1: Parse the POD document to markdown
console.log("\n[Step 1] Parsing Proof of Delivery document...");
const parseRun = await client.parseRuns.createAndPoll({
file: { url: dataUrl },
config: {
blockOptions: {
text: {
agentic: {
enabled: true, // Handle handwriting, stamps, smudged text
},
},
},
chunkingStrategy: {
type: "document", // Keep entire POD as one chunk
},
},
});
if (parseRun.status !== "PROCESSED") {
throw new Error(`Parse failed with status: ${parseRun.status}`);
}
const parsedContent = parseRun.output.chunks.map((c) => c.content).join("\n\n");
console.log(`✓ Parsed ${parseRun.output.chunks.length} chunk(s)`);
console.log(`Preview: ${parsedContent.substring(0, 200)}...`);
// Step 2: Extract structured fields using Zod schema
console.log("\n[Step 2] Extracting Proof of Delivery fields...");
const podSchema = z.object({
company_name: z
.string()
.nullable()
.describe(
"Name of the logistics company issuing the proof of delivery document"
),
document_type: z
.string()
.nullable()
.describe("Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'"),
receipt_number: z
.string()
.nullable()
.describe("Unique receipt or tracking number that identifies this delivery"),
order_number: z
.string()
.nullable()
.describe("Purchase or shipment order number referenced on the POD"),
delivery_date_time: extendDate()
.describe("Date and time when the goods were physically delivered (ISO format)"),
supplier: z
.string()
.nullable()
.describe("Name or company details of the supplier or shipper who sent the goods"),
destination: z
.string()
.nullable()
.describe("Complete delivery destination address including street, city, postal code"),
goods_details: z
.array(
z.object({
description: z
.string()
.nullable()
.describe("Item name, SKU, or product description"),
quantity: z
.string()
.nullable()
.describe("Quantity of this line item shipped"),
})
)
.nullable()
.describe("List of delivered items with descriptions and quantities"),
total_quantity: z
.string()
.nullable()
.describe(
"Total number of items/packages in the shipment according to the order"
),
delivered_quantity: z
.string()
.nullable()
.describe(
"Actual quantity of items delivered; compare against total_quantity to flag shortages"
),
received_by: z
.string()
.nullable()
.describe("Full name and signature details of the recipient who accepted the delivery"),
delivering_agency_person: z
.string()
.nullable()
.describe(
"Name and signature of the logistics driver or agency representative who performed the delivery"
),
});
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: podSchema,
},
});
if (extractRun.status !== "PROCESSED") {
throw new Error(`Extraction failed with status: ${extractRun.status}`);
}
const extractedData = extractRun.output.value;
console.log("✓ Extraction complete");
// Step 3: Validate and report
console.log("\n[Step 3] Validation & Reporting");
console.log("─────────────────────────────");
// Check for critical fields
const criticalFields = [
"receipt_number",
"delivery_date_time",
"destination",
"received_by",
];
const missingCritical = criticalFields.filter((f) => !extractedData[f]);
if (missingCritical.length > 0) {
console.warn(`⚠ Missing critical fields: ${missingCritical.join(", ")}`);
}
// Check quantity discrepancy
const totalQty = parseInt(extractedData.total_quantity ?? "0", 10);
const deliveredQty = parseInt(extractedData.delivered_quantity ?? "0", 10);
if (totalQty > 0 && deliveredQty < totalQty) {
console.warn(
`⚠ Quantity mismatch: ${deliveredQty}/${totalQty} items delivered`
);
}
// Output summary
console.log("\n📋 EXTRACTED PROOF OF DELIVERY DATA");
console.log("──────────────────────────────────");
console.log(`Company: ${extractedData.company_name || "(not found)"}`);
console.log(`Receipt #: ${extractedData.receipt_number || "(not found)"}`);
console.log(`Order #: ${extractedData.order_number || "(not found)"}`);
console.log(`Delivery Date/Time: ${extractedData.delivery_date_time || "(not found)"}`);
console.log(`Supplier: ${extractedData.supplier || "(not found)"}`);
console.log(`Destination: ${extractedData.destination || "(not found)"}`);
console.log(`Total Quantity: ${extractedData.total_quantity || "(not found)"}`);
console.log(`Delivered Quantity: ${extractedData.delivered_quantity || "(not found)"}`);
console.log(`Received By: ${extractedData.received_by || "(not found)"}`);
console.log(`Delivering Person: ${extractedData.delivering_agency_person || "(not found)"}`);
if (extractedData.goods_details && extractedData.goods_details.length > 0) {
console.log(`\nGoods Details (${extractedData.goods_details.length} items):`);
extractedData.goods_details.forEach((item, idx) => {
console.log(
` [${idx + 1}] ${item.description || "(no description)"} — Qty: ${item.quantity || "(unknown)"}`
);
});
}
console.log("\n✓ Processing complete");
return extractedData;
}
// Auto-invoke if run directly
if (require.main === module) {
const testFile = process.argv[2] || "./sample-pod.pdf";
processProofOfDelivery(testFile).catch(console.error);
}import os
from extend_ai import Extend
import sys
client = Extend(token=os.environ["EXTEND_API_KEY"])
def process_proof_of_delivery(file_path: str):
print(f"Processing Proof of Delivery: {file_path}")
# Convert local file to data URL for SDK
with open(file_path, "rb") as f:
file_buffer = f.read()
data_url = f"data:application/octet-stream;base64,{file_buffer.encode('utf-8').hex()}"
# Step 1: Parse the POD document to markdown
print("\n[Step 1] Parsing Proof of Delivery document...")
parse_run = client.parse_runs.create_and_poll(
file={"url": data_url},
config={
"blockOptions": {
"text": {
"agentic": {
"enabled": True, # Handle handwriting, stamps, smudged text
},
},
},
"chunkingStrategy": {
"type": "document", # Keep entire POD as one chunk
},
},
)
if parse_run.status != "PROCESSED":
raise Exception(f"Parse failed with status: {parse_run.status}")
parsed_content = "\n\n".join([c.get("content", "") for c in parse_run.output.get("chunks", [])])
print(f"✓ Parsed {len(parse_run.output.get('chunks', []))} chunk(s)")
print(f"Preview: {parsed_content[:200]}...")
# Step 2: Extract structured fields using schema
print("\n[Step 2] Extracting Proof of Delivery fields...")
pod_schema = {
"type": "object",
"properties": {
"company_name": {
"type": ["string", "null"],
"description": "Name of the logistics company issuing the proof of delivery document",
},
"document_type": {
"type": ["string", "null"],
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'",
},
"receipt_number": {
"type": ["string", "null"],
"description": "Unique receipt or tracking number that identifies this delivery",
},
"order_number": {
"type": ["string", "null"],
"description": "Purchase or shipment order number referenced on the POD",
},
"delivery_date_time": {
"type": ["string", "null"],
"description": "Date and time when the goods were physically delivered (ISO format)",
},
"supplier": {
"type": ["string", "null"],
"description": "Name or company details of the supplier or shipper who sent the goods",
},
"destination": {
"type": ["string", "null"],
"description": "Complete delivery destination address including street, city, postal code",
},
"goods_details": {
"type": ["array", "null"],
"description": "List of delivered items with descriptions and quantities",
"items": {
"type": "object",
"properties": {
"description": {
"type": ["string", "null"],
"description": "Item name, SKU, or product description",
},
"quantity": {
"type": ["string", "null"],
"description": "Quantity of this line item shipped",
},
},
},
},
"total_quantity": {
"type": ["string", "null"],
"description": "Total number of items/packages in the shipment according to the order",
},
"delivered_quantity": {
"type": ["string", "null"],
"description": "Actual quantity of items delivered; compare against total_quantity to flag shortages",
},
"received_by": {
"type": ["string", "null"],
"description": "Full name and signature details of the recipient who accepted the delivery",
},
"delivering_agency_person": {
"type": ["string", "null"],
"description": "Name and signature of the logistics driver or agency representative who performed the delivery",
},
},
}
extract_run = client.extract_runs.create_and_poll(
file={"url": data_url},
config={"schema": pod_schema},
)
if extract_run.status != "PROCESSED":
raise Exception(f"Extraction failed with status: {extract_run.status}")
extracted_data = extract_run.output.get("value", {})
print("✓ Extraction complete")
# Step 3: Validate and report
print("\n[Step 3] Validation & Reporting")
print("─────────────────────────────")
# Check for critical fields
critical_fields = [
"receipt_number",
"delivery_date_time",
"destination",
"received_by",
]
missing_critical = [f for f in critical_fields if not extracted_data.get(f)]
if missing_critical:
print(f"⚠ Missing critical fields: {', '.join(missing_critical)}")
# Check quantity discrepancy
total_qty = int(extracted_data.get("total_quantity") or "0")
delivered_qty = int(extracted_data.get("delivered_quantity") or "0")
if total_qty > 0 and delivered_qty < total_qty:
print(f"⚠ Quantity mismatch: {delivered_qty}/{total_qty} items delivered")
# Output summary
print("\n📋 EXTRACTED PROOF OF DELIVERY DATA")
print("──────────────────────────────────")
print(f"Company: {extracted_data.get('company_name') or '(not found)'}")
print(f"Receipt #: {extracted_data.get('receipt_number') or '(not found)'}")
print(f"Order #: {extracted_data.get('order_number') or '(not found)'}")
print(f"Delivery Date/Time: {extracted_data.get('delivery_date_time') or '(not found)'}")
print(f"Supplier: {extracted_data.get('supplier') or '(not found)'}")
print(f"Destination: {extracted_data.get('destination') or '(not found)'}")
print(f"Total Quantity: {extracted_data.get('total_quantity') or '(not found)'}")
print(f"Delivered Quantity: {extracted_data.get('delivered_quantity') or '(not found)'}")
print(f"Received By: {extracted_data.get('received_by') or '(not found)'}")
print(f"Delivering Person: {extracted_data.get('delivering_agency_person') or '(not found)'}")
goods_details = extracted_data.get("goods_details")
if goods_details and len(goods_details) > 0:
print(f"\nGoods Details ({len(goods_details)} items):")
for idx, item in enumerate(goods_details):
desc = item.get("description") or "(no description)"
qty = item.get("quantity") or "(unknown)"
print(f" [{idx + 1}] {desc} — Qty: {qty}")
print("\n✓ Processing complete")
return extracted_data
# Auto-invoke if run directly
if __name__ == "__main__":
test_file = sys.argv[1] if len(sys.argv) > 1 else "./sample-pod.pdf"
process_proof_of_delivery(test_file)import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
/**
* Proof of Delivery (POD) Extractor
*
* Uses the Extend REST API directly (https://api.extend.ai).
* No official Java SDK exists; this code calls the API endpoints directly
* using java.net.http.HttpClient with zero external dependencies.
*/
public class ProofOfDeliveryProcessor {
private static final String API_BASE = "https://api.extend.ai";
private static final String API_KEY = System.getenv("EXTEND_API_KEY");
private final HttpClient httpClient;
public ProofOfDeliveryProcessor() {
this.httpClient = HttpClient.newHttpClient();
}
/**
* Converts a local file to a base64 data URL.
*/
private String fileToDataUrl(String filePath) throws IOException {
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
String base64 = Base64.getEncoder().encodeToString(fileBytes);
return "data:application/octet-stream;base64," + base64;
}
/**
* Makes a POST request to the Extend API and returns the parsed JSON response.
*/
private Map<String, Object> apiPost(String path, String jsonBody) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + path))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("API request failed: " + response.statusCode() + " " + response.body());
}
return parseJson(response.body());
}
/**
* Polls an async operation until it reaches a terminal state.
*/
private Map<String, Object> pollUntilProcessed(String runId, String runType) throws IOException, InterruptedException {
long maxWaitMs = 120000; // 2 minutes
long startTime = System.currentTimeMillis();
long pollIntervalMs = 1000;
while (System.currentTimeMillis() - startTime < maxWaitMs) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/" + runType + "/" + runId))
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("Poll request failed: " + response.statusCode() + " " + response.body());
}
Map<String, Object> run = parseJson(response.body());
String status = (String) run.get("status");
if ("PROCESSED".equals(status)) {
return run;
} else if ("FAILED".equals(status) || "ERROR".equals(status)) {
throw new RuntimeException("Run failed with status: " + status);
}
Thread.sleep(pollIntervalMs);
}
throw new RuntimeException("Polling timeout exceeded");
}
/**
* Initiates a parse run and waits for completion.
*/
private Map<String, Object> parseDocument(String dataUrl) throws IOException, InterruptedException {
System.out.println("\n[Step 1] Parsing Proof of Delivery document...");
String parseRequestJson = buildJsonString(new Object[][] {
{"file", new Object[][] {{"url", dataUrl}}},
{"config", new Object[][] {
{"blockOptions", new Object[][] {
{"text", new Object[][] {
{"agentic", new Object[][] {{"enabled", true}}}
}}
}},
{"chunkingStrategy", new Object[][] {
{"type", "document"}
}}
}}
});
Map<String, Object> parseResponse = apiPost("/parse-runs", parseRequestJson);
String runId = (String) parseResponse.get("id");
Map<String, Object> completedRun = pollUntilProcessed(runId, "parse-runs");
if (!"PROCESSED".equals(completedRun.get("status"))) {
throw new RuntimeException("Parse failed with status: " + completedRun.get("status"));
}
System.out.println("✓ Parsing complete");
return completedRun;
}
/**
* Initiates an extraction run with the POD schema and waits for completion.
*/
private Map<String, Object> extractDocument(String dataUrl) throws IOException, InterruptedException {
System.out.println("\n[Step 2] Extracting Proof of Delivery fields...");
String schemaJson = buildPodSchema();
String extractRequestJson = buildJsonString(new Object[][] {
{"file", new Object[][] {{"url", dataUrl}}},
{"config", new Object[][] {
{"schema", parseJson(schemaJson)}
}}
});
Map<String, Object> extractResponse = apiPost("/extract-runs", extractRequestJson);
String runId = (String) extractResponse.get("id");
Map<String, Object> completedRun = pollUntilProcessed(runId, "extract-runs");
if (!"PROCESSED".equals(completedRun.get("status"))) {
throw new RuntimeException("Extraction failed with status: " + completedRun.get("status"));
}
System.out.println("✓ Extraction complete");
return completedRun;
}
/**
* Processes a Proof of Delivery file: parse → extract → validate.
*/
public Map<String, Object> processProofOfDelivery(String filePath) throws IOException, InterruptedException {
System.out.println("Processing Proof of Delivery: " + filePath);
// Convert file to data URL
String dataUrl = fileToDataUrl(filePath);
// Step 1: Parse
Map<String, Object> parseRun = parseDocument(dataUrl);
Map<String, Object> parseOutput = (Map<String, Object>) parseRun.get("output");
List<Map<String, Object>> chunks = (List<Map<String, Object>>) parseOutput.get("chunks");
String parsedContent = chunks.stream()
.map(c -> (String) c.get("content"))
.collect(Collectors.joining("\n\n"));
System.out.println("Preview: " + parsedContent.substring(0, Math.min(200, parsedContent.length())) + "...");
// Step 2: Extract
Map<String, Object> extractRun = extractDocument(dataUrl);
Map<String, Object> extractOutput = (Map<String, Object>) extractRun.get("output");
Map<String, Object> extractedData = (Map<String, Object>) extractOutput.get("value");
// Step 3: Validate and report
System.out.println("\n[Step 3] Validation & Reporting");
System.out.println("─────────────────────────────");
String[] criticalFields = {"receipt_number", "delivery_date_time", "destination", "received_by"};
List<String> missingCritical = new ArrayList<>();
for (String field : criticalFields) {
if (extractedData.get(field) == null) {
missingCritical.add(field);
}
}
if (!missingCritical.isEmpty()) {
System.out.println("⚠ Missing critical fields: " + String.join(", ", missingCritical));
}
// Check quantity discrepancy
String totalQtyStr = (String) extractedData.getOrDefault("total_quantity", "0");
String deliveredQtyStr = (String) extractedData.getOrDefault("delivered_quantity", "0");
int totalQty = Integer.parseInt(totalQtyStr.isEmpty() ? "0" : totalQtyStr);
int deliveredQty = Integer.parseInt(deliveredQtyStr.isEmpty() ? "0" : deliveredQtyStr);
if (totalQty > 0 && deliveredQty < totalQty) {
System.out.println("⚠ Quantity mismatch: " + deliveredQty + "/" + totalQty + " items delivered");
}
// Output summary
System.out.println("\n📋 EXTRACTED PROOF OF DELIVERY DATA");
System.out.println("──────────────────────────────────");
System.out.println("Company: " + (extractedData.get("company_name") != null ? extractedData.get("company_name") : "(not found)"));
System.out.println("Receipt #: " + (extractedData.get("receipt_number") != null ? extractedData.get("receipt_number") : "(not found)"));
System.out.println("Order #: " + (extractedData.get("order_number") != null ? extractedData.get("order_number") : "(not found)"));
System.out.println("Delivery Date/Time: " + (extractedData.get("delivery_date_time") != null ? extractedData.get("delivery_date_time") : "(not found)"));
System.out.println("Supplier: " + (extractedData.get("supplier") != null ? extractedData.get("supplier") : "(not found)"));
System.out.println("Destination: " + (extractedData.get("destination") != null ? extractedData.get("destination") : "(not found)"));
System.out.println("Total Quantity: " + (extractedData.get("total_quantity") != null ? extractedData.get("total_quantity") : "(not found)"));
System.out.println("Delivered Quantity: " + (extractedData.get("delivered_quantity") != null ? extractedData.get("delivered_quantity") : "(not found)"));
System.out.println("Received By: " + (extractedData.get("received_by") != null ? extractedData.get("received_by") : "(not found)"));
System.out.println("Delivering Person: " + (extractedData.get("delivering_agency_person") != null ? extractedData.get("delivering_agency_person") : "(not found)"));
List<Map<String, Object>> goodsDetails = (List<Map<String, Object>>) extractedData.get("goods_details");
if (goodsDetails != null && !goodsDetails.isEmpty()) {
System.out.println("\nGoods Details (" + goodsDetails.size() + " items):");
for (int i = 0; i < goodsDetails.size(); i++) {
Map<String, Object> item = goodsDetails.get(i);
String desc = (String) item.get("description");
String qty = (String) item.get("quantity");
System.out.println(" [" + (i + 1) + "] " + (desc != null ? desc : "(no description)") + " — Qty: " + (qty != null ? qty : "(unknown)"));
}
}
System.out.println("\n✓ Processing complete");
return extractedData;
}
/**
* Builds the Proof of Delivery JSON schema.
*/
private String buildPodSchema() {
return "{" +
"\"type\":\"object\"," +
"\"properties\":{" +
"\"company_name\":{\"type\":[\"string\",\"null\"],\"description\":\"Name of the logistics company\"}," +
"\"document_type\":{\"type\":[\"string\",\"null\"],\"description\":\"Type of document\"}," +
"\"receipt_number\":{\"type\":[\"string\",\"null\"],\"description\":\"Unique receipt or tracking number\"}," +
"\"order_number\":{\"type\":[\"string\",\"null\"],\"description\":\"Purchase or shipment order number\"}," +
"\"delivery_date_time\":{\"type\":[\"string\",\"null\"],\"description\":\"Date and time of delivery (ISO format)\"}," +
"\"supplier\":{\"type\":[\"string\",\"null\"],\"description\":\"Name or details of the supplier/shipper\"}," +
"\"destination\":{\"type\":[\"string\",\"null\"],\"description\":\"Delivery destination address\"}," +
"\"goods_details\":{\"type\":[\"array\",\"null\"],\"description\":\"List of delivered items\"}," +
"\"total_quantity\":{\"type\":[\"string\",\"null\"],\"description\":\"Total quantity of items in shipment\"}," +
"\"delivered_quantity\":{\"type\":[\"string\",\"null\"],\"description\":\"Actual quantity delivered\"}," +
"\"received_by\":{\"type\":[\"string\",\"null\"],\"description\":\"Name and signature of recipient\"}," +
"\"delivering_agency_person\":{\"type\":[\"string\",\"null\"],\"description\":\"Name and signature of delivering representative\"}" +
"}" +
"}";
}
/**
* Minimal JSON parser for response objects.
*/
private Map<String, Object> parseJson(String json) {
Map<String, Object> result = new LinkedHashMap<>();
json = json.trim();
if (!json.startsWith("{") || !json.endsWith("}")) {
return result;
}
json = json.substring(1, json.length() - 1);
int depth = 0;
int start = 0;
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '{' || c == '[') {
depth++;
} else if (c == '}' || c == ']') {
depth--;
} else if (c == ',' && depth == 0) {
processPair(result, json.substring(start, i).trim());
start = i + 1;
}
}
if (start < json.length()) {
processPair(result, json.substring(start).trim());
}
return result;
}
private void processPair(Map<String, Object> map, String pair) {
int colonIdx = pair.indexOf(":");
if (colonIdx <= 0) return;
String key = pair.substring(0, colonIdx).trim();
if (key.startsWith("\"")) {
key = key.substring(1, key.length() - 1);
}
String valueStr = pair.substring(colonIdx + 1).trim();
Object value;
if (valueStr.equals("null")) {
value = null;
} else if (valueStr.startsWith("\"")) {
value = valueStr.substring(1, valueStr.length() - 1);
} else if (valueStr.startsWith("{")) {
value = parseJson(valueStr);
} else if (valueStr.startsWith("[")) {
value = parseJsonArray(valueStr);
} else if (valueStr.equals("true")) {
value = true;
} else if (valueStr.equals("false")) {
value = false;
} else {
value = valueStr;
}
map.put(key, value);
}
private List<Object> parseJsonArray(String json) {
List<Object> result = new ArrayList<>();
json = json.substring(1, json.length() - 1).trim();
if (json.isEmpty()) return result;
int depth = 0;
int start = 0;
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '{' || c == '[') {
depth++;
} else if (c == '}' || c == ']') {
depth--;
} else if (c == ',' && depth == 0) {
String item = json.substring(start, i).trim();
result.add(parseValue(item));
start = i + 1;
}
}
if (start < json.length()) {
result.add(parseValue(json.substring(start).trim()));
}
return result;
}
private Object parseValue(String val) {
if (val.equals("null")) return null;
if (val.startsWith("\"")) return val.substring(1, val.length() - 1);
if (val.startsWith("{")) return parseJson(val);
if (val.startsWith("[")) return parseJsonArray(val);
if (val.equals("true")) return true;
if (val.equals("false")) return false;
return val;
}
/**
* Builds a JSON string from a 2D array of key-value pairs.
*/
private String buildJsonString(Object[][] pairs) {
StringBuilder sb = new StringBuilder("{");
for (int i = 0; i < pairs.length; i++) {
if (i > 0) sb.append(",");
Object[] pair = pairs[i];
String key = (String) pair[0];
Object val = pair[1];
sb.append("\"").append(key).append("\":");
sb.append(objectToJson(val));
}
sb.append("}");
return sb.toString();
}
private String objectToJson(Object obj) {
if (obj == null) {
return "null";
} else if (obj instanceof String) {
return "\"" + escapeJson((String) obj) + "\"";
} else if (obj instanceof Boolean) {
return obj.toString();
} else if (obj instanceof Number) {
return obj.toString();
} else if (obj instanceof Object[][]) {
return buildJsonString((Object[][]) obj);
} else if (obj instanceof Map) {
Map<String, Object> m = (Map<String, Object>) obj;
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> e : m.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(e.getKey()).append("\":").append(objectToJson(e.getValue()));
first = false;
}
sb.append("}");
return sb.toString();
} else if (obj instanceof List) {
List<?> l = (List<?>) obj;
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < l.size(); i++) {
if (i > 0) sb.append(",");
sb.append(objectToJson(l.get(i)));
}
sb.append("]");
return sb.toString();
}
return "null";
}
private String escapeJson(String s) {
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
}
public static void main(String[] args) throws IOException, InterruptedException {
String filePath = args.length > 0 ? args[0] : "./sample-pod.pdf";
ProofOfDeliveryProcessor processor = new ProofOfDeliveryProcessor();
processor.processProofOfDelivery(filePath);
}
}// This code uses the Extend REST API directly because Extend has no official Go SDK yet.
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
const extendBaseURL = "https://api.extend.ai"
type GoodsDetail struct {
Description *string `json:"description"`
Quantity *string `json:"quantity"`
}
type PODExtractedData struct {
CompanyName *string `json:"company_name"`
DocumentType *string `json:"document_type"`
ReceiptNumber *string `json:"receipt_number"`
OrderNumber *string `json:"order_number"`
DeliveryDateTime *string `json:"delivery_date_time"`
Supplier *string `json:"supplier"`
Destination *string `json:"destination"`
GoodsDetails []GoodsDetail `json:"goods_details"`
TotalQuantity *string `json:"total_quantity"`
DeliveredQuantity *string `json:"delivered_quantity"`
ReceivedBy *string `json:"received_by"`
DeliveringAgencyPerson *string `json:"delivering_agency_person"`
}
type ParseOutput struct {
Chunks []struct {
Content string `json:"content"`
} `json:"chunks"`
}
type ParseRunResponse struct {
Status string `json:"status"`
Output ParseOutput `json:"output"`
}
type ExtractOutput struct {
Value PODExtractedData `json:"value"`
}
type ExtractRunResponse struct {
Status string `json:"status"`
Output ExtractOutput `json:"output"`
}
type BlockOptions struct {
Text struct {
Agentic struct {
Enabled bool `json:"enabled"`
} `json:"agentic"`
} `json:"text"`
}
type ChunkingStrategy struct {
Type string `json:"type"`
}
type ParseConfig struct {
BlockOptions BlockOptions `json:"blockOptions"`
ChunkingStrategy ChunkingStrategy `json:"chunkingStrategy"`
}
type ExtractConfig struct {
Schema map[string]interface{} `json:"schema"`
}
func pollForCompletion(client *http.Client, apiKey, runID, endpoint string, maxAttempts int) (string, error) {
for attempt := 0; attempt < maxAttempts; attempt++ {
req, err := http.NewRequest("GET", fmt.Sprintf("%s%s/%s", extendBaseURL, endpoint, runID), nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
status, ok := result["status"].(string)
if !ok {
return "", fmt.Errorf("unexpected response format")
}
if status == "PROCESSED" {
return string(body), nil
}
if status == "FAILED" {
return "", fmt.Errorf("run failed with status: %s", status)
}
time.Sleep(2 * time.Second)
}
return "", fmt.Errorf("polling timeout after %d attempts", maxAttempts)
}
func processProofOfDelivery(filePath string) (PODExtractedData, error) {
apiKey := os.Getenv("EXTEND_API_KEY")
if apiKey == "" {
return PODExtractedData{}, fmt.Errorf("EXTEND_API_KEY environment variable not set")
}
fmt.Printf("Processing Proof of Delivery: %s\n", filePath)
// Read file and convert to data URL
fileBuffer, err := os.ReadFile(filePath)
if err != nil {
return PODExtractedData{}, err
}
dataURL := fmt.Sprintf("data:application/octet-stream;base64,%s", base64.StdEncoding.EncodeToString(fileBuffer))
client := &http.Client{Timeout: 30 * time.Second}
// Step 1: Parse the POD document to markdown
fmt.Println("\n[Step 1] Parsing Proof of Delivery document...")
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",
},
},
}
parseReqJSON, err := json.Marshal(parseReqBody)
if err != nil {
return PODExtractedData{}, err
}
parseReq, err := http.NewRequest("POST", fmt.Sprintf("%s/parse-runs", extendBaseURL), bytes.NewBuffer(parseReqJSON))
if err != nil {
return PODExtractedData{}, err
}
parseReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
parseReq.Header.Set("Content-Type", "application/json")
parseResp, err := client.Do(parseReq)
if err != nil {
return PODExtractedData{}, err
}
defer parseResp.Body.Close()
parseRespBody, err := io.ReadAll(parseResp.Body)
if err != nil {
return PODExtractedData{}, err
}
var parseInitResp map[string]interface{}
if err := json.Unmarshal(parseRespBody, &parseInitResp); err != nil {
return PODExtractedData{}, err
}
parseRunID, ok := parseInitResp["id"].(string)
if !ok {
return PODExtractedData{}, fmt.Errorf("parse run ID not found in response")
}
parseResultJSON, err := pollForCompletion(client, apiKey, parseRunID, "/parse-runs", 60)
if err != nil {
return PODExtractedData{}, err
}
var parseResult ParseRunResponse
if err := json.Unmarshal([]byte(parseResultJSON), &parseResult); err != nil {
return PODExtractedData{}, err
}
if parseResult.Status != "PROCESSED" {
return PODExtractedData{}, fmt.Errorf("parse failed with status: %s", parseResult.Status)
}
parsedContent := ""
for _, chunk := range parseResult.Output.Chunks {
parsedContent += chunk.Content + "\n\n"
}
fmt.Printf("✓ Parsed %d chunk(s)\n", len(parseResult.Output.Chunks))
if len(parsedContent) > 200 {
fmt.Printf("Preview: %s...\n", parsedContent[:200])
} else {
fmt.Printf("Preview: %s\n", parsedContent)
}
// Step 2: Extract structured fields
fmt.Println("\n[Step 2] Extracting Proof of Delivery fields...")
podSchema := map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"company_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name of the logistics company issuing the proof of delivery document",
},
"document_type": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'",
},
"receipt_number": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Unique receipt or tracking number that identifies this delivery",
},
"order_number": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Purchase or shipment order number referenced on the POD",
},
"delivery_date_time": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Date and time when the goods were physically delivered (ISO format)",
},
"supplier": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name or company details of the supplier or shipper who sent the goods",
},
"destination": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Complete delivery destination address including street, city, postal code",
},
"goods_details": map[string]interface{}{
"type": []string{"array", "null"},
"items": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"description": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Item name, SKU, or product description",
},
"quantity": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Quantity of this line item shipped",
},
},
},
"description": "List of delivered items with descriptions and quantities",
},
"total_quantity": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Total number of items/packages in the shipment according to the order",
},
"delivered_quantity": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Actual quantity of items delivered; compare against total_quantity to flag shortages",
},
"received_by": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Full name and signature details of the recipient who accepted the delivery",
},
"delivering_agency_person": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name and signature of the logistics driver or agency representative who performed the delivery",
},
},
}
extractReqBody := map[string]interface{}{
"file": map[string]string{
"url": dataURL,
},
"config": map[string]interface{}{
"schema": podSchema,
},
}
extractReqJSON, err := json.Marshal(extractReqBody)
if err != nil {
return PODExtractedData{}, err
}
extractReq, err := http.NewRequest("POST", fmt.Sprintf("%s/extract-runs", extendBaseURL), bytes.NewBuffer(extractReqJSON))
if err != nil {
return PODExtractedData{}, err
}
extractReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
extractReq.Header.Set("Content-Type", "application/json")
extractResp, err := client.Do(extractReq)
if err != nil {
return PODExtractedData{}, err
}
defer extractResp.Body.Close()
extractRespBody, err := io.ReadAll(extractResp.Body)
if err != nil {
return PODExtractedData{}, err
}
var extractInitResp map[string]interface{}
if err := json.Unmarshal(extractRespBody, &extractInitResp); err != nil {
return PODExtractedData{}, err
}
extractRunID, ok := extractInitResp["id"].(string)
if !ok {
return PODExtractedData{}, fmt.Errorf("extract run ID not found in response")
}
extractResultJSON, err := pollForCompletion(client, apiKey, extractRunID, "/extract-runs", 60)
if err != nil {
return PODExtractedData{}, err
}
var extractResult ExtractRunResponse
if err := json.Unmarshal([]byte(extractResultJSON), &extractResult); err != nil {
return PODExtractedData{}, err
}
if extractResult.Status != "PROCESSED" {
return PODExtractedData{}, fmt.Errorf("extraction failed with status: %s", extractResult.Status)
}
extractedData := extractResult.Output.Value
fmt.Println("✓ Extraction complete")
// Step 3: Validate and report
fmt.Println("\n[Step 3] Validation & Reporting")
fmt.Println("─────────────────────────────")
// Check for critical fields
criticalFields := map[string]*string{
"receipt_number": extractedData.ReceiptNumber,
"delivery_date_time": extractedData.DeliveryDateTime,
"destination": extractedData.Destination,
"received_by": extractedData.ReceivedBy,
}
var missingCritical []string
for field, val := range criticalFields {
if val == nil || *val == "" {
missingCritical = append(missingCritical, field)
}
}
if len(missingCritical) > 0 {
fmt.Printf("⚠ Missing critical fields: %s\n", strings.Join(missingCritical, ", "))
}
// Check quantity discrepancy
totalQty := 0
deliveredQty := 0
if extractedData.TotalQuantity != nil && *extractedData.TotalQuantity != "" {
totalQty, _ = strconv.Atoi(*extractedData.TotalQuantity)
}
if extractedData.DeliveredQuantity != nil && *extractedData.DeliveredQuantity != "" {
deliveredQty, _ = strconv.Atoi(*extractedData.DeliveredQuantity)
}
if totalQty > 0 && deliveredQty < totalQty {
fmt.Printf("⚠ Quantity mismatch: %d/%d items delivered\n", deliveredQty, totalQty)
}
// Output summary
fmt.Println("\n📋 EXTRACTED PROOF OF DELIVERY DATA")
fmt.Println("──────────────────────────────────")
fmt.Printf("Company: %s\n", strVal(extractedData.CompanyName))
fmt.Printf("Receipt #: %s\n", strVal(extractedData.ReceiptNumber))
fmt.Printf("Order #: %s\n", strVal(extractedData.OrderNumber))
fmt.Printf("Delivery Date/Time: %s\n", strVal(extractedData.DeliveryDateTime))
fmt.Printf("Supplier: %s\n", strVal(extractedData.Supplier))
fmt.Printf("Destination: %s\n", strVal(extractedData.Destination))
fmt.Printf("Total Quantity: %s\n", strVal(extractedData.TotalQuantity))
fmt.Printf("Delivered Quantity: %s\n", strVal(extractedData.DeliveredQuantity))
fmt.Printf("Received By: %s\n", strVal(extractedData.ReceivedBy))
fmt.Printf("Delivering Person: %s\n", strVal(extractedData.DeliveringAgencyPerson))
if len(extractedData.GoodsDetails) > 0 {
fmt.Printf("\nGoods Details (%d items):\n", len(extractedData.GoodsDetails))
for idx, item := range extractedData.GoodsDetails {
desc := "(no description)"
if item.Description != nil {
desc = *item.Description
}
qty := "(unknown)"
if item.Quantity != nil {
qty = *item.Quantity
}
fmt.Printf(" [%d] %s — Qty: %s\n", idx+1, desc, qty)
}
}
fmt.Println("\n✓ Processing complete")
return extractedData, nil
}
func strVal(s *string) string {
if s == nil || *s == "" {
return "(not found)"
}
return *s
}
func main() {
flag.Parse()
testFile := "./sample-pod.pdf"
if flag.NArg() > 0 {
testFile = flag.Arg(0)
}
_, err := processProofOfDelivery(testFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}// Deploy the "Proof of Delivery" 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/proof-of-delivery.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: proof-of-delivery).
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, "proof-of-delivery.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": "Proof of Delivery Processing Pipeline",
"steps": [
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": [
{
"step": "parse1"
}
]
},
{
"name": "parse1",
"type": "PARSE",
"config": {
"parseConfig": {
"blockOptions": {
"text": {
"agentic": {
"enabled": true
}
}
},
"chunkingStrategy": {
"type": "document"
}
}
},
"next": [
{
"step": "extraction2"
}
]
},
{
"name": "extraction2",
"type": "EXTRACT",
"config": {
"extractorConfig": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"type": [
"string",
"null"
],
"description": "Name or details of the supplier/shipper"
},
"destination": {
"type": [
"string",
"null"
],
"description": "Delivery destination address"
},
"received_by": {
"type": [
"string",
"null"
],
"description": "Name and signature of the recipient"
},
"company_name": {
"type": [
"string",
"null"
],
"description": "Name of the logistics company issuing the proof of delivery"
},
"order_number": {
"type": [
"string",
"null"
],
"description": "Purchase or shipment order number"
},
"document_type": {
"type": [
"string",
"null"
],
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'"
},
"goods_details": {
"type": [
"array",
"null"
],
"description": "List of delivered items with descriptions"
},
"receipt_number": {
"type": [
"string",
"null"
],
"description": "Unique receipt or tracking number for the delivery"
},
"total_quantity": {
"type": [
"string",
"null"
],
"description": "Total quantity of items in the shipment"
},
"delivered_quantity": {
"type": [
"string",
"null"
],
"description": "Actual quantity of items delivered"
},
"delivery_date_time": {
"type": [
"string",
"null"
],
"description": "Date and time when the goods were delivered"
},
"delivering_agency_person": {
"type": [
"string",
"null"
],
"description": "Name and signature of the delivering agency representative"
}
}
},
"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 "Proof of Delivery" 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/proof-of-delivery.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: proof-of-delivery).
"""
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, Optional
from extend_ai import Extend
API_KEY = os.environ.get("EXTEND_API_KEY")
if not API_KEY:
print("Error: Set EXTEND_API_KEY first.", file=sys.stderr)
sys.exit(1)
STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "proof-of-delivery.json"
STATE: Dict[str, Any] = {}
def load_state() -> None:
"""Load state from disk if it exists."""
global STATE
if STATE_FILE.exists():
STATE = json.loads(STATE_FILE.read_text(encoding="utf-8"))
def save_state() -> None:
"""Save state to disk."""
STATE_DIR.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(STATE, indent=2), encoding="utf-8")
WORKFLOW = {
"name": "Proof of Delivery Processing Pipeline",
"steps": [
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": [{"step": "parse1"}],
},
{
"name": "parse1",
"type": "PARSE",
"config": {
"parseConfig": {
"blockOptions": {
"text": {
"agentic": {
"enabled": True,
}
}
},
"chunkingStrategy": {
"type": "document",
},
}
},
"next": [{"step": "extraction2"}],
},
{
"name": "extraction2",
"type": "EXTRACT",
"config": {
"extractorConfig": {
"schema": {
"type": "object",
"properties": {
"supplier": {
"type": ["string", "null"],
"description": "Name or details of the supplier/shipper",
},
"destination": {
"type": ["string", "null"],
"description": "Delivery destination address",
},
"received_by": {
"type": ["string", "null"],
"description": "Name and signature of the recipient",
},
"company_name": {
"type": ["string", "null"],
"description": "Name of the logistics company issuing the proof of delivery",
},
"order_number": {
"type": ["string", "null"],
"description": "Purchase or shipment order number",
},
"document_type": {
"type": ["string", "null"],
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'",
},
"goods_details": {
"type": ["array", "null"],
"description": "List of delivered items with descriptions",
},
"receipt_number": {
"type": ["string", "null"],
"description": "Unique receipt or tracking number for the delivery",
},
"total_quantity": {
"type": ["string", "null"],
"description": "Total quantity of items in the shipment",
},
"delivered_quantity": {
"type": ["string", "null"],
"description": "Actual quantity of items delivered",
},
"delivery_date_time": {
"type": ["string", "null"],
"description": "Date and time when the goods were delivered",
},
"delivering_agency_person": {
"type": ["string", "null"],
"description": "Name and signature of the delivering agency representative",
},
},
},
"baseProcessor": "extraction_performance",
"advancedOptions": {
"reviewAgent": {
"enabled": True,
},
"advancedMultimodalEnabled": True,
},
}
},
},
],
}
def main() -> None:
"""Main provisioning routine."""
load_state()
client = Extend(token=API_KEY)
print(f'Deploying "{WORKFLOW["name"]}…"')
workflow_id: Optional[str] = STATE.get("workflowId")
if workflow_id:
print(f"✓ workflow already provisioned ({workflow_id}) — updating steps")
client.workflows.update(
id=workflow_id,
steps=WORKFLOW["steps"],
)
else:
# Try to find an existing workflow with the same name.
try:
workflows_list = client.workflows.list(name=WORKFLOW["name"])
items = workflows_list.data if hasattr(workflows_list, "data") else []
existing = None
for item in items:
if getattr(item, "name", None) == WORKFLOW["name"]:
existing = item
break
if existing and getattr(existing, "id", None):
workflow_id = existing.id
STATE["workflowId"] = workflow_id
save_state()
print(
f'✓ workflow "{WORKFLOW["name"]}" found in your account ({workflow_id}) — updating steps'
)
client.workflows.update(
id=workflow_id,
steps=WORKFLOW["steps"],
)
except Exception:
# Lookup is best-effort; fall through to create.
pass
if not workflow_id:
created = client.workflows.create(**WORKFLOW)
workflow_id = created.id
if not workflow_id:
raise ValueError("Could not read created workflow id from response")
STATE["workflowId"] = workflow_id
save_state()
print(f"+ created workflow ({workflow_id})")
# Deploy the current draft as a new version so the workflow is runnable —
# best-effort: some accounts/plans may not require this explicit step.
try:
client.workflows.create_version(id=workflow_id)
except Exception:
pass
api_endpoint = "https://api.extend.ai"
print("\nDone. Run documents through it with:")
print(
f' POST {api_endpoint}/workflow_runs {{ workflow: {{ id: "{workflow_id}" }}, 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}", file=sys.stderr)
sys.exit(1)// This code uses the Extend REST API directly because there is no official Java SDK.
// All endpoints, request bodies, and response fields mirror the TypeScript reference.
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class ProofOfDeliveryProvisioning {
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 String STATE_DIR = Paths.get(System.getProperty("user.dir"), ".extend").toString();
private static final String STATE_FILE = Paths.get(STATE_DIR, "proof-of-delivery.json").toString();
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
static class State {
String workflowId;
}
private static State state = new State();
public static void main(String[] args) {
try {
if (API_KEY == null || API_KEY.isEmpty()) {
System.err.println("Set EXTEND_API_KEY first.");
System.exit(1);
}
loadState();
Map<String, Object> workflow = buildWorkflow();
String workflowName = (String) workflow.get("name");
System.out.println("Deploying \"" + workflowName + "\"…");
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) workflow.get("steps");
if (state.workflowId != null && !state.workflowId.isEmpty()) {
System.out.println("✓ workflow already provisioned (" + state.workflowId + ") — updating steps");
Map<String, Object> updateBody = new HashMap<>();
updateBody.put("steps", steps);
api("POST", "/workflows/" + state.workflowId, updateBody);
} else {
try {
String encodedName = URLEncoder.encode(workflowName, StandardCharsets.UTF_8);
Map<String, Object> listResponse = api("GET", "/workflows?name=" + encodedName, null);
List<Map<String, Object>> items = new ArrayList<>();
if (listResponse.containsKey("data")) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> data = (List<Map<String, Object>>) listResponse.get("data");
items = data;
} else if (listResponse.containsKey("items")) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> itemsList = (List<Map<String, Object>>) listResponse.get("items");
items = itemsList;
}
for (Map<String, Object> item : items) {
if (workflowName.equals(item.get("name"))) {
String existingId = (String) item.get("id");
if (existingId != null) {
state.workflowId = existingId;
saveState();
System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + existingId + ") — updating steps");
Map<String, Object> updateBody = new HashMap<>();
updateBody.put("steps", steps);
api("POST", "/workflows/" + existingId, updateBody);
}
break;
}
}
} catch (Exception e) {
// lookup is best-effort; fall through to create
}
if (state.workflowId == null || state.workflowId.isEmpty()) {
Map<String, Object> created = api("POST", "/workflows", workflow);
String wfId = (String) created.get("id");
if (wfId == null) {
@SuppressWarnings("unchecked")
Map<String, Object> workflowObj = (Map<String, Object>) created.get("workflow");
if (workflowObj != null) {
wfId = (String) workflowObj.get("id");
}
}
if (wfId == null) {
throw new Exception("Could not read created workflow id from response");
}
state.workflowId = wfId;
saveState();
System.out.println("+ created workflow (" + wfId + ")");
}
}
try {
Map<String, Object> versionBody = new HashMap<>();
api("POST", "/workflows/" + state.workflowId + "/versions", versionBody);
} catch (Exception e) {
// best-effort: some accounts/plans may not require this explicit step
}
System.out.println("\nDone. Run documents through it with:");
System.out.println(" POST " + API + "/workflow_runs { workflow: { id: \"" + state.workflowId + "\" }, file: { url: \"https://…\" } }");
System.out.println("Or open the workflow in the Extend dashboard to review and deploy it.");
} catch (Exception e) {
System.err.println(e.getMessage() != null ? e.getMessage() : e.toString());
System.exit(1);
}
}
private static Map<String, Object> api(String method, String pathName, Map<String, Object> body) throws Exception {
String url = API + pathName;
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.method(method, body != null ? HttpRequest.BodyPublishers.ofString(toJson(body)) : HttpRequest.BodyPublishers.noBody())
.header("Authorization", "Bearer " + API_KEY)
.header("x-extend-api-version", VERSION);
if (body != null) {
requestBuilder.header("Content-Type", "application/json");
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> data = parseJson(response.body());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
String errorMsg = toJson(data);
if (errorMsg.length() > 300) {
errorMsg = errorMsg.substring(0, 300);
}
throw new Exception(method + " " + pathName + " failed (" + response.statusCode() + "): " + errorMsg);
}
return data;
}
private static void loadState() throws IOException {
Path stateFilePath = Paths.get(STATE_FILE);
if (Files.exists(stateFilePath)) {
String content = Files.readString(stateFilePath);
@SuppressWarnings("unchecked")
Map<String, Object> parsed = (Map<String, Object>) parseJson(content);
if (parsed.containsKey("workflowId")) {
state.workflowId = (String) parsed.get("workflowId");
}
}
}
private static void saveState() throws IOException {
Files.createDirectories(Paths.get(STATE_DIR));
Map<String, Object> stateMap = new HashMap<>();
if (state.workflowId != null) {
stateMap.put("workflowId", state.workflowId);
}
Files.writeString(Paths.get(STATE_FILE), toJson(stateMap));
}
private static Map<String, Object> buildWorkflow() {
Map<String, Object> workflow = new LinkedHashMap<>();
workflow.put("name", "Proof of Delivery Processing Pipeline");
workflow.put("steps", buildSteps());
return workflow;
}
private static List<Map<String, Object>> buildSteps() {
List<Map<String, Object>> steps = new ArrayList<>();
// Trigger step
Map<String, Object> trigger = new LinkedHashMap<>();
trigger.put("name", "startTrigger1");
trigger.put("type", "TRIGGER");
List<Map<String, Object>> triggerNext = new ArrayList<>();
Map<String, Object> triggerNextItem = new LinkedHashMap<>();
triggerNextItem.put("step", "parse1");
triggerNext.add(triggerNextItem);
trigger.put("next", triggerNext);
steps.add(trigger);
// Parse step
Map<String, Object> parse = new LinkedHashMap<>();
parse.put("name", "parse1");
parse.put("type", "PARSE");
Map<String, Object> parseConfig = new LinkedHashMap<>();
Map<String, Object> blockOptions = new LinkedHashMap<>();
Map<String, Object> textOptions = new LinkedHashMap<>();
Map<String, Object> agenticOptions = new LinkedHashMap<>();
agenticOptions.put("enabled", true);
textOptions.put("agentic", agenticOptions);
blockOptions.put("text", textOptions);
parseConfig.put("blockOptions", blockOptions);
Map<String, Object> chunkingStrategy = new LinkedHashMap<>();
chunkingStrategy.put("type", "document");
parseConfig.put("chunkingStrategy", chunkingStrategy);
Map<String, Object> parseConfigWrapper = new LinkedHashMap<>();
parseConfigWrapper.put("parseConfig", parseConfig);
parse.put("config", parseConfigWrapper);
List<Map<String, Object>> parseNext = new ArrayList<>();
Map<String, Object> parseNextItem = new LinkedHashMap<>();
parseNextItem.put("step", "extraction2");
parseNext.add(parseNextItem);
parse.put("next", parseNext);
steps.add(parse);
// Extraction step
Map<String, Object> extraction = new LinkedHashMap<>();
extraction.put("name", "extraction2");
extraction.put("type", "EXTRACT");
Map<String, Object> extractConfig = new LinkedHashMap<>();
Map<String, Object> extractorConfig = new LinkedHashMap<>();
extractorConfig.put("schema", buildSchema());
extractorConfig.put("baseProcessor", "extraction_performance");
Map<String, Object> advancedOptions = new LinkedHashMap<>();
Map<String, Object> reviewAgent = new LinkedHashMap<>();
reviewAgent.put("enabled", true);
advancedOptions.put("reviewAgent", reviewAgent);
advancedOptions.put("advancedMultimodalEnabled", true);
extractorConfig.put("advancedOptions", advancedOptions);
extractConfig.put("extractorConfig", extractorConfig);
extraction.put("config", extractConfig);
steps.add(extraction);
return steps;
}
private static Map<String, Object> buildSchema() {
Map<String, Object> schema = new LinkedHashMap<>();
schema.put("type", "object");
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("supplier", buildProperty("Name or details of the supplier/shipper"));
properties.put("destination", buildProperty("Delivery destination address"));
properties.put("received_by", buildProperty("Name and signature of the recipient"));
properties.put("company_name", buildProperty("Name of the logistics company issuing the proof of delivery"));
properties.put("order_number", buildProperty("Purchase or shipment order number"));
properties.put("document_type", buildProperty("Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'"));
properties.put("goods_details", buildArrayProperty("List of delivered items with descriptions"));
properties.put("receipt_number", buildProperty("Unique receipt or tracking number for the delivery"));
properties.put("total_quantity", buildProperty("Total quantity of items in the shipment"));
properties.put("delivered_quantity", buildProperty("Actual quantity of items delivered"));
properties.put("delivery_date_time", buildProperty("Date and time when the goods were delivered"));
properties.put("delivering_agency_person", buildProperty("Name and signature of the delivering agency representative"));
schema.put("properties", properties);
return schema;
}
private static Map<String, Object> buildProperty(String description) {
Map<String, Object> prop = new LinkedHashMap<>();
List<String> types = new ArrayList<>();
types.add("string");
types.add("null");
prop.put("type", types);
prop.put("description", description);
return prop;
}
private static Map<String, Object> buildArrayProperty(String description) {
Map<String, Object> prop = new LinkedHashMap<>();
List<String> types = new ArrayList<>();
types.add("array");
types.add("null");
prop.put("type", types);
prop.put("description", description);
return prop;
}
private static String toJson(Object obj) {
if (obj == null) {
return "null";
}
if (obj instanceof String) {
return "\"" + escapeJson((String) obj) + "\"";
}
if (obj instanceof Number) {
return obj.toString();
}
if (obj instanceof Boolean) {
return obj.toString();
}
if (obj instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) obj;
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(escapeJson(entry.getKey())).append("\":");
sb.append(toJson(entry.getValue()));
first = false;
}
sb.append("}");
return sb.toString();
}
if (obj instanceof List) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) obj;
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (Object item : list) {
if (!first) sb.append(",");
sb.append(toJson(item));
first = false;
}
sb.append("]");
return sb.toString();
}
return obj.toString();
}
private static String escapeJson(String s) {
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
@SuppressWarnings("unchecked")
private static Map<String, Object> parseJson(String json) {
json = json.trim();
if (json.isEmpty() || !json.startsWith("{")) {
return new HashMap<>();
}
return (Map<String, Object>) parseValue(json, new int[]{0});
}
private static Object parseValue(String json, int[] pos) {
skipWhitespace(json, pos);
if (pos[0] >= json.length()) {
return null;
}
char c = json.charAt(pos[0]);
if (c == '"') {
return parseString(json, pos);
} else if (c == '{') {
return parseObject(json, pos);
} else if (c == '[') {
return parseArray(json, pos);
} else if (c == 't' || c == 'f') {
return parseBoolean(json, pos);
} else if (c == 'n') {
return parseNull(json, pos);
} else {
return parseNumber(json, pos);
}
}
private static String parseString(String json, int[] pos) {
pos[0]++; // skip opening quote
StringBuilder sb = new StringBuilder();
while (pos[0] < json.length()) {
char c = json.charAt(pos[0]);
if (c == '"') {
pos[0]++;
return sb.toString();
} else if (c == '\\') {
pos[0]++;
if (pos[0] < json.length()) {
char escaped = json.charAt(pos[0]);
switch (escaped) {
case '"': sb.append('"'); break;
case '\\': sb.append('\\'); break;
case '/': sb.append('/'); break;
case 'b': sb.append('\b'); break;
case 'f': sb.append('\f'); break;
case 'n': sb.append('\n'); break;
case 'r': sb.append('\r'); break;
case 't': sb.append('\t'); break;
default: sb.append(escaped);
}
pos[0]++;
}
} else {
sb.append(c);
pos[0]++;
}
}
return sb.toString();
}
private static Map<String, Object> parseObject(String json, int[] pos) {
Map<String, Object> obj = new LinkedHashMap<>();
pos[0]++; // skip opening brace
skipWhitespace(json, pos);
if (pos[0] < json.length() && json.charAt(pos[0]) == '}') {
pos[0]++;
return obj;
}
while (pos[0] < json.length()) {
skipWhitespace(json, pos);
String key = parseString(json, pos);
skipWhitespace(json, pos);
if (pos[0] < json.length() && json.charAt(pos[0]) == ':') {
pos[0]++;
Object value = parseValue(json, pos);
obj.put(key, value);
}
skipWhitespace(json, pos);
if (pos[0] < json.length()) {
char c = json.charAt(pos[0]);
if (c == ',') {
pos[0]++;
} else if (c == '}') {
pos[0]++;
break;
}
}
}
return obj;
}
private static List<Object> parseArray(String json, int[] pos) {
List<Object> arr = new ArrayList<>();
pos[0]++; // skip opening bracket
skipWhitespace(json, pos);
if (pos[0] < json.length() && json.charAt(pos[0]) == ']') {
pos[0]++;
return arr;
}
while (pos[0] < json.length()) {
Object value = parseValue(json, pos);
arr.add(value);
skipWhitespace(json, pos);
if (pos[0] < json.length()) {
char c = json.charAt(pos[0]);
if (c == ',') {
pos[0]++;
} else if (c == ']') {
pos[0]++;
break;
}
}
}
return arr;
}
private static Boolean parseBoolean(String json, int[] pos) {
if (json.startsWith("true", pos[0])) {
pos[0] += 4;
return true;
} else if (json.startsWith("false", pos[0])) {
pos[0] += 5;
return false;
}
return null;
}
private static Object parseNull(String json, int[] pos) {
if (json.startsWith("null", pos[0])) {
pos[0] += 4;
}
return null;
}
private static Number parseNumber(String json, int[] pos) {
int start = pos[0];
if (pos[0] < json.length() && json.charAt(pos[0]) == '-') {
pos[0]++;
}
while (pos[0] < json.length() && Character.isDigit(json.charAt(pos[0]))) {
pos[0]++;
}
if (pos[0] < json.length() && json.charAt(pos[0]) == '.') {
pos[0]++;
while (pos[0] < json.length() && Character.isDigit(json.charAt(pos[0]))) {
pos[0]++;
}
}
if (pos[0] < json.length() && (json.charAt(pos[0]) == 'e' || json.charAt(pos[0]) == 'E')) {
pos[0]++;
if (pos[0] < json.length() && (json.charAt(pos[0]) == '+' || json.charAt(pos[0]) == '-')) {
pos[0]++;
}
while (pos[0] < json.length() && Character.isDigit(json.charAt(pos[0]))) {
pos[0]++;
}
}
String numStr = json.substring(start, pos[0]);
if (numStr.contains(".") || numStr.contains("e") || numStr.contains("E")) {
return Double.parseDouble(numStr);
} else {
return Long.parseLong(numStr);
}
}
private static void skipWhitespace(String json, int[] pos) {
while (pos[0] < json.length() && Character.isWhitespace(json.charAt(pos[0]))) {
pos[0]++;
}
}
}// This uses the REST API directly because Extend has no official Go SDK yet.
// The Extend TypeScript SDK is a thin wrapper over these same endpoints.
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 WorkflowItem struct {
Name string `json:"name"`
ID string `json:"id"`
}
type WorkflowListResponse struct {
Data []WorkflowItem `json:"data"`
Items []WorkflowItem `json:"items"`
}
type WorkflowCreateResponse struct {
ID string `json:"id"`
Workflow struct {
ID string `json:"id"`
} `json:"workflow"`
}
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 current directory: %v\n", err)
os.Exit(1)
}
stateDir = filepath.Join(cwd, ".extend")
stateFile = filepath.Join(stateDir, "proof-of-delivery.json")
if data, err := os.ReadFile(stateFile); err == nil {
json.Unmarshal(data, &state)
}
}
func saveState() error {
if err := os.MkdirAll(stateDir, 0755); err != nil {
return err
}
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
return os.WriteFile(stateFile, data, 0644)
}
func apiCall(method, pathName string, body interface{}) (json.RawMessage, error) {
var reqBody io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
reqBody = bytes.NewReader(data)
}
req, err := http.NewRequest(method, API+pathName, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
req.Header.Set("x-extend-api-version", VERSION)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
msg := string(respBody)
if len(msg) > 300 {
msg = msg[:300]
}
return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, msg)
}
return respBody, nil
}
func getWorkflow() map[string]interface{} {
return map[string]interface{}{
"name": "Proof of Delivery Processing Pipeline",
"steps": []map[string]interface{}{
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": []map[string]interface{}{
{"step": "parse1"},
},
},
{
"name": "parse1",
"type": "PARSE",
"config": map[string]interface{}{
"parseConfig": map[string]interface{}{
"blockOptions": map[string]interface{}{
"text": map[string]interface{}{
"agentic": map[string]interface{}{
"enabled": true,
},
},
},
"chunkingStrategy": map[string]interface{}{
"type": "document",
},
},
},
"next": []map[string]interface{}{
{"step": "extraction2"},
},
},
{
"name": "extraction2",
"type": "EXTRACT",
"config": map[string]interface{}{
"extractorConfig": map[string]interface{}{
"schema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"supplier": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name or details of the supplier/shipper",
},
"destination": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Delivery destination address",
},
"received_by": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name and signature of the recipient",
},
"company_name": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name of the logistics company issuing the proof of delivery",
},
"order_number": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Purchase or shipment order number",
},
"document_type": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Type of document, e.g., 'Proof of Delivery' or 'Shipment Delivery Receipt'",
},
"goods_details": map[string]interface{}{
"type": []string{"array", "null"},
"description": "List of delivered items with descriptions",
},
"receipt_number": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Unique receipt or tracking number for the delivery",
},
"total_quantity": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Total quantity of items in the shipment",
},
"delivered_quantity": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Actual quantity of items delivered",
},
"delivery_date_time": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Date and time when the goods were delivered",
},
"delivering_agency_person": map[string]interface{}{
"type": []string{"string", "null"},
"description": "Name and signature of the delivering agency representative",
},
},
},
"baseProcessor": "extraction_performance",
"advancedOptions": map[string]interface{}{
"reviewAgent": map[string]interface{}{
"enabled": true,
},
"advancedMultimodalEnabled": true,
},
},
},
},
},
}
}
func main() {
workflow := getWorkflow()
workflowName := workflow["name"].(string)
workflowSteps := workflow["steps"]
fmt.Printf("Deploying \"%s\"…\n", workflowName)
if state.WorkflowID != nil && *state.WorkflowID != "" {
fmt.Printf("✓ workflow already provisioned (%s) — updating steps\n", *state.WorkflowID)
_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", *state.WorkflowID), map[string]interface{}{
"steps": workflowSteps,
})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
} else {
// Try to find existing workflow with same name
query := url.QueryEscape(workflowName)
respData, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", query), nil)
if err == nil {
var listResp WorkflowListResponse
json.Unmarshal(respData, &listResp)
var items []WorkflowItem
if len(listResp.Data) > 0 {
items = listResp.Data
} else {
items = listResp.Items
}
var existingID string
for _, item := range items {
if item.Name == workflowName {
existingID = item.ID
break
}
}
if existingID != "" {
state.WorkflowID = &existingID
saveState()
fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", workflowName, existingID)
_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", existingID), map[string]interface{}{
"steps": workflowSteps,
})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
}
// Create new workflow if not found
if state.WorkflowID == nil || *state.WorkflowID == "" {
respData, err := apiCall("POST", "/workflows", workflow)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
var created WorkflowCreateResponse
if err := json.Unmarshal(respData, &created); err != nil {
fmt.Fprintf(os.Stderr, "Failed to parse response: %v\n", err)
os.Exit(1)
}
wfID := created.ID
if wfID == "" {
wfID = created.Workflow.ID
}
if wfID == "" {
fmt.Fprintf(os.Stderr, "Could not read created workflow id from response\n")
os.Exit(1)
}
state.WorkflowID = &wfID
saveState()
fmt.Printf("+ created workflow (%s)\n", wfID)
}
}
// Deploy as new version — best-effort
if state.WorkflowID != nil {
apiCall("POST", fmt.Sprintf("/workflows/%s/versions", *state.WorkflowID), map[string]interface{}{})
}
fmt.Println("\nDone. Run documents through it with:")
if state.WorkflowID != nil {
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.")
}This template captures proof of delivery documentation used by logistics companies to record shipment receipts. It extracts supplier and destination information, delivery timestamps, goods details with quantities, and recipient signatures. Essential for supply chain tracking and delivery verification.