Extracts freight rates, carrier details, and delivery appointments from logistics confirmations.
A freight rate confirmation is a carrier-issued document that details carrier information, shipment specifications, pricing charges, and scheduled pickup and delivery appointments for a trucking shipment. This template takes in Freight Rate Confirmation and outputs markdown (.md) capturing the document's full text and layout, and JSON (.json) with structured carrier, shipment, rate, and appointment fields per the extraction schema by using Extend's Parse 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.
{
"name": "Rate Confirmation Processing Pipeline",
"steps": [
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": [
{
"step": "parse1"
}
]
},
{
"name": "parse1",
"type": "PARSE",
"config": {
"parseConfig": {
"blockOptions": {
"text": {
"agentic": {
"enabled": true
}
}
},
"chunkingStrategy": {
"type": "document"
}
}
}
}
]
}# Rate Confirmation Processing — Extend AI Skill
## What this pipeline does
Converts freight transportation rate confirmations into structured logistics data. The pipeline parses the document into machine-readable markdown, then extracts carrier information, shipment specifications, pricing breakdown, and multi-stop appointment scheduling into a typed JSON object. Designed for refrigerated trucking workflows where PRO numbers, temperature ranges, and precise appointment windows are critical for compliance and operational planning.
## When to use this
- **Freight broker intake**: Automatically log incoming rate confirmations into your TMS (transportation management system) without manual data entry
- **Appointment scheduling**: Extract pickup and delivery windows to auto-sync with calendar systems and driver notifications
- **Rate auditing**: Capture linehaul rates and additional charges to detect pricing discrepancies or anomalies across carriers
- **Compliance documentation**: Parse and archive carrier contact, shipment weight, and temperature requirements for DOT and food safety audits
- **Multi-leg routing**: Extract stops sequentially from confirmations with multiple pickups/deliveries to validate route optimization
## Processor pipeline
### Step 1: Parse (agentic OCR → markdown)
**Processor**: `parse_performance` with agentic text extraction
**Purpose**: Convert scanned or digital rate confirmations into clean, machine-readable markdown while preserving table structure and appointment details.
**Key config**:
- `engine: "parse_performance"` — optimized for forms and structured layouts common in logistics documents
- `blockOptions.text.agentic.enabled: true` — enable vision-based field detection for non-standard formatting, handwritten annotations, and varying carrier templates
- `chunkingStrategy.type: "document"` — keep entire confirmation as one chunk (single-page or short multi-page documents)
**Why**: Rate confirmations are often filled PDFs or scanned copies with carrier-specific layouts. Agentic OCR handles logo artifacts, watermarks, and handwritten notes (e.g., "rush delivery" or carrier initials) that would confuse simple text extraction. Document-level chunking preserves context between carrier header and appointment details.
### Step 2: Extract (structured fields → JSON)
**Processor**: `extraction_performance` with review agent and multimodal
**Purpose**: Pull 12+ fields (PRO number, carrier contact, shipment specs, rates, appointments) into strongly-typed JSON ready for database insertion or workflow routing.
**Key config**:
- `baseProcessor: "extraction_performance"` — uses semantic understanding to map carrier-specific field names ("Confirmation #" vs. "Reference") to canonical fields
- `advancedOptions.reviewAgent.enabled: true` — post-process extraction to catch missing or malformed appointment dates, validate phone numbers, flag null critical fields
- `advancedOptions.advancedMultimodalEnabled: true` — leverage both text and visual cues (e.g., bold text for totals, colored cells for special charges)
**Why**: Appointment times and rate totals are business-critical; a missing delivery window or typo in PRO number breaks downstream logistics. Review agent catches these before they reach your database.
## TypeScript implementation
```typescript
import { ExtendClient, extendDate, extendCurrency } from "extend-ai";
import { z } from "zod";
import fs from "fs";
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
/**
* Process a rate confirmation document:
* 1. Parse to markdown (agentic OCR for form fields)
* 2. Extract structured fields (carrier, shipment, rates, appointments)
* 3. Return typed JSON ready for TMS insertion
*/
export async function processRateConfirmation(filePath: string) {
console.log(`[RateConfirmation] Processing: ${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 rate confirmation to markdown
console.log("[1/2] Parsing rate confirmation...");
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: ${parseRun.status}`);
}
const markdownContent = parseRun.output.chunks
.map((chunk) => chunk.content)
.join("\n\n");
console.log(`[Parse] Extracted ${markdownContent.length} characters of markdown`);
// Step 2: Extract structured fields
console.log("[2/2] Extracting rate confirmation fields...");
const rateConfirmationSchema = z.object({
pro_number: z
.string()
.nullable()
.describe(
"PRO number identifier for the rate confirmation. Look for 'PRO', 'Reference', 'Confirmation #', or similar headers."
),
carrier_name: z
.string()
.nullable()
.describe(
"Full legal name of the carrier company issuing the rate confirmation."
),
carrier_contact: z
.string()
.nullable()
.describe(
"Primary contact person name at carrier (e.g., 'Account Manager: John Smith'). Extract just the name if possible."
),
carrier_phone: z
.string()
.nullable()
.describe(
"Carrier phone number in any format. Include area code and extension if present."
),
shipper_name: z
.string()
.nullable()
.describe(
"Legal company name of the shipper or origin account. Usually at the top left of the confirmation."
),
shipment_details: z
.object({
truck_size_type: z
.string()
.nullable()
.describe(
"Type and size of truck (e.g., '53ft Reefer', 'Straight Truck', 'Pup Trailer'). Critical for equipment matching."
),
cargo_description: z
.string()
.nullable()
.describe(
"What is being shipped (e.g., 'Frozen Poultry', 'Fresh Produce', 'Dairy Products'). Required for hazmat and customs."
),
weight_lbs: z
.string()
.nullable()
.describe(
"Total shipment weight in pounds (e.g., '40000'). May include units; extract numeric + 'lbs'."
),
pieces: z
.string()
.nullable()
.describe(
"Number of pallets, cases, or units (e.g., '48 pallets' or '240 cases'). Used for unload planning."
),
miles: z
.string()
.nullable()
.describe(
"Estimated distance in miles. May be labeled 'miles', 'distance', or as a multiplier for rate calculation."
),
temperature_range: z
.string()
.nullable()
.describe(
"Required reefer or climate control temperature (e.g., '-10 to 0°F', '32-36°F'). Critical for refrigerated loads."
),
})
.nullable()
.describe(
"Object containing all shipment specifications. Must be present for TMS routing and equipment allocation."
),
rate_charges: z
.object({
line_haul_rate: z
.string()
.nullable()
.describe(
"Base linehaul rate, typically in format '$X.XX per mile' or total '$X.XX'. Core billing component."
),
additional_charges: z
.string()
.nullable()
.describe(
"Extra fees (e.g., 'Reefer Fuel Surcharge: $125', 'Layover: $50'). Often comma-separated or in a table."
),
total_rate: z
.string()
.nullable()
.describe(
"Final total amount due for this shipment (e.g., '$2,450.00'). Must match linehaul + additional charges."
),
})
.nullable()
.describe(
"Pricing breakdown. Essential for cost allocation and margin analysis."
),
pickup_location: z
.string()
.nullable()
.describe(
"Pickup address and facility details (e.g., '123 Industrial Blvd, Chicago, IL 60601 - Loading Dock A'). Include dock/gate info if present."
),
pickup_appointment: z
.string()
.nullable()
.describe(
"Pickup appointment date and time (e.g., '2024-12-15 08:00 AM - 12:00 PM' or '12/15/24 08:00-12:00'). Convert to ISO 8601 if possible."
),
delivery_location: z
.string()
.nullable()
.describe(
"Delivery address and facility details (e.g., '456 Warehouse Way, Atlanta, GA 30303 - Receiving 2'). Include dock/gate info."
),
delivery_appointment: z
.string()
.nullable()
.describe(
"Delivery appointment date and time. Convert to ISO 8601 format (YYYY-MM-DD HH:MM) if possible. Time window is critical for customer compliance."
),
confirmation_date: z
.string()
.nullable()
.describe(
"Date and time this rate confirmation was issued (e.g., '2024-12-10 14:30'). Used to track quote expiration and SLA compliance."
),
});
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: rateConfirmationSchema,
advancedOptions: {
reviewAgent: {
enabled: true,
},
advancedMultimodalEnabled: true,
},
},
});
if (extractRun.status !== "PROCESSED") {
throw new Error(`Extraction failed: ${extractRun.status}`);
}
const extractedData = extractRun.output.value;
console.log("[Extract] Successfully extracted rate confirmation fields");
// Log results
console.log("\n=== RATE CONFIRMATION EXTRACTION RESULTS ===");
console.log(JSON.stringify(extractedData, null, 2));
return {
status: "success",
pro_number: extractedData.pro_number,
carrier: {
name: extractedData.carrier_name,
contact: extractedData.carrier_contact,
phone: extractedData.carrier_phone,
},
shipper: {
name: extractedData.shipper_name,
},
shipment: extractedData.shipment_details,
pricing: extractedData.rate_charges,
logistics: {
pickup: {
location: extractedData.pickup_location,
appointment: extractedData.pickup_appointment,
},
delivery: {
location: extractedData.delivery_location,
appointment: extractedData.delivery_appointment,
},
},
confirmation_date: extractedData.confirmation_date,
raw_extraction: extractedData,
};
}
// Allow direct execution: node script.ts <filePath>
const filePath = process.argv[2] || "./rate_confirmation.pdf";
processRateConfirmation(filePath)
.then((result) => {
console.log("\n✓ Pipeline complete");
process.exit(0);
})
.catch((err) => {
console.error("✗ Pipeline error:", err.message);
process.exit(1);
});
```
## CLI equivalent
```bash
# Step 1: Parse rate confirmation to markdown
extend parse rate_confirmation.pdf \
--engine parse_performance \
--agentic-text true \
--chunk-strategy document
# Step 2: Extract structured fields (using the schema below)
extend extract rate_confirmation.pdf \
--schema rate_confirmation_schema.json \
--processor extraction_performance \
--review-agent true \
--multimodal true
```
**rate_confirmation_schema.json:**
```json
{
"type": "object",
"properties": {
"pro_number": {
"type": ["string", "null"],
"description": "PRO number identifier for the rate confirmation"
},
"carrier_name": {
"type": ["string", "null"],
"description": "Name of the carrier company"
},
"carrier_contact": {
"type": ["string", "null"],
"description": "Primary contact person name at carrier"
},
"carrier_phone": {
"type": ["string", "null"],
"description": "Carrier phone number"
},
"shipper_name": {
"type": ["string", "null"],
"description": "Name of the shipper company"
},
"shipment_details": {
"type": ["object", "null"],
"description": "Object containing shipment specifications",
"properties": {
"truck_size_type": { "type": ["string", "null"] },
"cargo_description": { "type": ["string", "null"] },
"weight_lbs": { "type": ["string", "null"] },
"pieces": { "type": ["string", "null"] },
"miles": { "type": ["string", "null"] },
"temperature_range": { "type": ["string", "null"] }
}
},
"rate_charges": {
"type": ["object", "null"],
"description": "Breakdown of rate charges",
"properties": {
"line_haul_rate": { "type": ["string", "null"] },
"additional_charges": { "type": ["string", "null"] },
"total_rate": { "type": ["string", "null"] }
}
},
"pickup_location": {
"type": ["string", "null"],
"description": "Pickup address and details"
},
"pickup_appointment": {
"type": ["string", "null"],
"description": "Pickup appointment date and time"
},
"delivery_location": {
"type": ["string", "null"],
"description": "Delivery address and details"
},
"delivery_appointment": {
"type": ["string", "null"],
"description": "Delivery appointment date and time"
},
"confirmation_date": {
"type": ["string", "null"],
"description": "Date and time confirmation was issued"
}
}
}
```
## Schema
The extraction schema captures 9 top-level fields plus nested objects for shipment specs, pricing, and logistics appointments:
| Field | Type | Purpose | Example |
|-------|------|---------|---------|
| `pro_number` | string\|null | Unique shipment reference in carrier system | `"PRO123456789"` |
| `carrier_name` | string\|null | Legal name of carrier issuing quote | `"XYZ Refrigerated Trucking Inc."` |
| `carrier_contact` | string\|null | Account manager or ops contact name | `"Sarah Johnson"` |
| `carrier_phone` | string\|null | Direct phone to carrier dispatch | `"(773) 555-0147 ext. 22"` |
| `shipper_name` | string\|null | Your company or customer origin | `"Fresh Produce Solutions LLC"` |
| **shipment_details** | object\|null | Cargo and equipment specs | — |
| └ `truck_size_type` | string\|null | Trailer/truck classification | `"53ft Reefer Trailer"` |
| └ `cargo_description` | string\|null | Commodity being shipped | `"Frozen Chicken Breast - Food Grade"` |
| └ `weight_lbs` | string\|null | Total shipment weight | `"42000 lbs"` |
| └ `pieces` | string\|null | Pallet/case count for unload ops | `"48 pallets on jack"` |
| └ `miles`import { ExtendClient, extendDate, extendCurrency } from "extend-ai";
import { z } from "zod";
import fs from "fs";
const client = new ExtendClient({ token: process.env.EXTEND_API_KEY });
/**
* Process a rate confirmation document:
* 1. Parse to markdown (agentic OCR for form fields)
* 2. Extract structured fields (carrier, shipment, rates, appointments)
* 3. Return typed JSON ready for TMS insertion
*/
export async function processRateConfirmation(filePath: string) {
console.log(`[RateConfirmation] Processing: ${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 rate confirmation to markdown
console.log("[1/2] Parsing rate confirmation...");
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: ${parseRun.status}`);
}
const markdownContent = parseRun.output.chunks
.map((chunk) => chunk.content)
.join("\n\n");
console.log(`[Parse] Extracted ${markdownContent.length} characters of markdown`);
// Step 2: Extract structured fields
console.log("[2/2] Extracting rate confirmation fields...");
const rateConfirmationSchema = z.object({
pro_number: z
.string()
.nullable()
.describe(
"PRO number identifier for the rate confirmation. Look for 'PRO', 'Reference', 'Confirmation #', or similar headers."
),
carrier_name: z
.string()
.nullable()
.describe(
"Full legal name of the carrier company issuing the rate confirmation."
),
carrier_contact: z
.string()
.nullable()
.describe(
"Primary contact person name at carrier (e.g., 'Account Manager: John Smith'). Extract just the name if possible."
),
carrier_phone: z
.string()
.nullable()
.describe(
"Carrier phone number in any format. Include area code and extension if present."
),
shipper_name: z
.string()
.nullable()
.describe(
"Legal company name of the shipper or origin account. Usually at the top left of the confirmation."
),
shipment_details: z
.object({
truck_size_type: z
.string()
.nullable()
.describe(
"Type and size of truck (e.g., '53ft Reefer', 'Straight Truck', 'Pup Trailer'). Critical for equipment matching."
),
cargo_description: z
.string()
.nullable()
.describe(
"What is being shipped (e.g., 'Frozen Poultry', 'Fresh Produce', 'Dairy Products'). Required for hazmat and customs."
),
weight_lbs: z
.string()
.nullable()
.describe(
"Total shipment weight in pounds (e.g., '40000'). May include units; extract numeric + 'lbs'."
),
pieces: z
.string()
.nullable()
.describe(
"Number of pallets, cases, or units (e.g., '48 pallets' or '240 cases'). Used for unload planning."
),
miles: z
.string()
.nullable()
.describe(
"Estimated distance in miles. May be labeled 'miles', 'distance', or as a multiplier for rate calculation."
),
temperature_range: z
.string()
.nullable()
.describe(
"Required reefer or climate control temperature (e.g., '-10 to 0°F', '32-36°F'). Critical for refrigerated loads."
),
})
.nullable()
.describe(
"Object containing all shipment specifications. Must be present for TMS routing and equipment allocation."
),
rate_charges: z
.object({
line_haul_rate: z
.string()
.nullable()
.describe(
"Base linehaul rate, typically in format '$X.XX per mile' or total '$X.XX'. Core billing component."
),
additional_charges: z
.string()
.nullable()
.describe(
"Extra fees (e.g., 'Reefer Fuel Surcharge: $125', 'Layover: $50'). Often comma-separated or in a table."
),
total_rate: z
.string()
.nullable()
.describe(
"Final total amount due for this shipment (e.g., '$2,450.00'). Must match linehaul + additional charges."
),
})
.nullable()
.describe(
"Pricing breakdown. Essential for cost allocation and margin analysis."
),
pickup_location: z
.string()
.nullable()
.describe(
"Pickup address and facility details (e.g., '123 Industrial Blvd, Chicago, IL 60601 - Loading Dock A'). Include dock/gate info if present."
),
pickup_appointment: z
.string()
.nullable()
.describe(
"Pickup appointment date and time (e.g., '2024-12-15 08:00 AM - 12:00 PM' or '12/15/24 08:00-12:00'). Convert to ISO 8601 if possible."
),
delivery_location: z
.string()
.nullable()
.describe(
"Delivery address and facility details (e.g., '456 Warehouse Way, Atlanta, GA 30303 - Receiving 2'). Include dock/gate info."
),
delivery_appointment: z
.string()
.nullable()
.describe(
"Delivery appointment date and time. Convert to ISO 8601 format (YYYY-MM-DD HH:MM) if possible. Time window is critical for customer compliance."
),
confirmation_date: z
.string()
.nullable()
.describe(
"Date and time this rate confirmation was issued (e.g., '2024-12-10 14:30'). Used to track quote expiration and SLA compliance."
),
});
const extractRun = await client.extractRuns.createAndPoll({
file: { url: dataUrl },
config: {
schema: rateConfirmationSchema,
advancedOptions: {
reviewAgent: {
enabled: true,
},
advancedMultimodalEnabled: true,
},
},
});
if (extractRun.status !== "PROCESSED") {
throw new Error(`Extraction failed: ${extractRun.status}`);
}
const extractedData = extractRun.output.value;
console.log("[Extract] Successfully extracted rate confirmation fields");
// Log results
console.log("\n=== RATE CONFIRMATION EXTRACTION RESULTS ===");
console.log(JSON.stringify(extractedData, null, 2));
return {
status: "success",
pro_number: extractedData.pro_number,
carrier: {
name: extractedData.carrier_name,
contact: extractedData.carrier_contact,
phone: extractedData.carrier_phone,
},
shipper: {
name: extractedData.shipper_name,
},
shipment: extractedData.shipment_details,
pricing: extractedData.rate_charges,
logistics: {
pickup: {
location: extractedData.pickup_location,
appointment: extractedData.pickup_appointment,
},
delivery: {
location: extractedData.delivery_location,
appointment: extractedData.delivery_appointment,
},
},
confirmation_date: extractedData.confirmation_date,
raw_extraction: extractedData,
};
}
// Allow direct execution: node script.ts <filePath>
const filePath = process.argv[2] || "./rate_confirmation.pdf";
processRateConfirmation(filePath)
.then((result) => {
console.log("\n✓ Pipeline complete");
process.exit(0);
})
.catch((err) => {
console.error("✗ Pipeline error:", err.message);
process.exit(1);
});import os
import sys
import base64
from extend_ai import Extend
client = Extend(token=os.environ["EXTEND_API_KEY"])
def process_rate_confirmation(file_path: str):
"""
Process a rate confirmation document:
1. Parse to markdown (agentic OCR for form fields)
2. Extract structured fields (carrier, shipment, rates, appointments)
3. Return typed JSON ready for TMS insertion
"""
print(f"[RateConfirmation] Processing: {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,{base64.b64encode(file_buffer).decode('utf-8')}"
# Step 1: Parse rate confirmation to markdown
print("[1/2] Parsing rate confirmation...")
parse_run = client.parse_runs.create_and_poll(
file={"url": data_url},
config={
"blockOptions": {
"text": {
"agentic": {
"enabled": True,
},
},
},
"chunkingStrategy": {
"type": "document",
},
},
)
if parse_run.status != "PROCESSED":
raise Exception(f"Parse failed: {parse_run.status}")
markdown_content = "\n\n".join(chunk.content for chunk in parse_run.output.chunks)
print(f"[Parse] Extracted {len(markdown_content)} characters of markdown")
# Step 2: Extract structured fields
print("[2/2] Extracting rate confirmation fields...")
rate_confirmation_schema = {
"type": "object",
"properties": {
"pro_number": {
"type": ["string", "null"],
"description": "PRO number identifier for the rate confirmation. Look for 'PRO', 'Reference', 'Confirmation #', or similar headers.",
},
"carrier_name": {
"type": ["string", "null"],
"description": "Full legal name of the carrier company issuing the rate confirmation.",
},
"carrier_contact": {
"type": ["string", "null"],
"description": "Primary contact person name at carrier (e.g., 'Account Manager: John Smith'). Extract just the name if possible.",
},
"carrier_phone": {
"type": ["string", "null"],
"description": "Carrier phone number in any format. Include area code and extension if present.",
},
"shipper_name": {
"type": ["string", "null"],
"description": "Legal company name of the shipper or origin account. Usually at the top left of the confirmation.",
},
"shipment_details": {
"type": ["object", "null"],
"description": "Object containing all shipment specifications. Must be present for TMS routing and equipment allocation.",
"properties": {
"truck_size_type": {
"type": ["string", "null"],
"description": "Type and size of truck (e.g., '53ft Reefer', 'Straight Truck', 'Pup Trailer'). Critical for equipment matching.",
},
"cargo_description": {
"type": ["string", "null"],
"description": "What is being shipped (e.g., 'Frozen Poultry', 'Fresh Produce', 'Dairy Products'). Required for hazmat and customs.",
},
"weight_lbs": {
"type": ["string", "null"],
"description": "Total shipment weight in pounds (e.g., '40000'). May include units; extract numeric + 'lbs'.",
},
"pieces": {
"type": ["string", "null"],
"description": "Number of pallets, cases, or units (e.g., '48 pallets' or '240 cases'). Used for unload planning.",
},
"miles": {
"type": ["string", "null"],
"description": "Estimated distance in miles. May be labeled 'miles', 'distance', or as a multiplier for rate calculation.",
},
"temperature_range": {
"type": ["string", "null"],
"description": "Required reefer or climate control temperature (e.g., '-10 to 0°F', '32-36°F'). Critical for refrigerated loads.",
},
},
},
"rate_charges": {
"type": ["object", "null"],
"description": "Pricing breakdown. Essential for cost allocation and margin analysis.",
"properties": {
"line_haul_rate": {
"type": ["string", "null"],
"description": "Base linehaul rate, typically in format '$X.XX per mile' or total '$X.XX'. Core billing component.",
},
"additional_charges": {
"type": ["string", "null"],
"description": "Extra fees (e.g., 'Reefer Fuel Surcharge: $125', 'Layover: $50'). Often comma-separated or in a table.",
},
"total_rate": {
"type": ["string", "null"],
"description": "Final total amount due for this shipment (e.g., '$2,450.00'). Must match linehaul + additional charges.",
},
},
},
"pickup_location": {
"type": ["string", "null"],
"description": "Pickup address and facility details (e.g., '123 Industrial Blvd, Chicago, IL 60601 - Loading Dock A'). Include dock/gate info if present.",
},
"pickup_appointment": {
"type": ["string", "null"],
"description": "Pickup appointment date and time (e.g., '2024-12-15 08:00 AM - 12:00 PM' or '12/15/24 08:00-12:00'). Convert to ISO 8601 if possible.",
},
"delivery_location": {
"type": ["string", "null"],
"description": "Delivery address and facility details (e.g., '456 Warehouse Way, Atlanta, GA 30303 - Receiving 2'). Include dock/gate info.",
},
"delivery_appointment": {
"type": ["string", "null"],
"description": "Delivery appointment date and time. Convert to ISO 8601 format (YYYY-MM-DD HH:MM) if possible. Time window is critical for customer compliance.",
},
"confirmation_date": {
"type": ["string", "null"],
"description": "Date and time this rate confirmation was issued (e.g., '2024-12-10 14:30'). Used to track quote expiration and SLA compliance.",
},
},
}
extract_run = client.extract_runs.create_and_poll(
file={"url": data_url},
config={
"schema": rate_confirmation_schema,
"advancedOptions": {
"reviewAgent": {
"enabled": True,
},
"advancedMultimodalEnabled": True,
},
},
)
if extract_run.status != "PROCESSED":
raise Exception(f"Extraction failed: {extract_run.status}")
extracted_data = extract_run.output.value
print("[Extract] Successfully extracted rate confirmation fields")
# Log results
print("\n=== RATE CONFIRMATION EXTRACTION RESULTS ===")
print(extracted_data)
return {
"status": "success",
"pro_number": extracted_data.get("pro_number"),
"carrier": {
"name": extracted_data.get("carrier_name"),
"contact": extracted_data.get("carrier_contact"),
"phone": extracted_data.get("carrier_phone"),
},
"shipper": {
"name": extracted_data.get("shipper_name"),
},
"shipment": extracted_data.get("shipment_details"),
"pricing": extracted_data.get("rate_charges"),
"logistics": {
"pickup": {
"location": extracted_data.get("pickup_location"),
"appointment": extracted_data.get("pickup_appointment"),
},
"delivery": {
"location": extracted_data.get("delivery_location"),
"appointment": extracted_data.get("delivery_appointment"),
},
},
"confirmation_date": extracted_data.get("confirmation_date"),
"raw_extraction": extracted_data,
}
if __name__ == "__main__":
file_path = sys.argv[1] if len(sys.argv) > 1 else "./rate_confirmation.pdf"
try:
result = process_rate_confirmation(file_path)
print("\n✓ Pipeline complete")
sys.exit(0)
except Exception as err:
print(f"✗ Pipeline error: {str(err)}")
sys.exit(1)// This code uses the Extend REST API directly because Extend has no official Java SDK yet.
// It calls https://api.extend.ai endpoints with Bearer token authentication.
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class RateConfirmationProcessor {
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();
public static void main(String[] args) throws Exception {
String filePath = args.length > 0 ? args[0] : "./rate_confirmation.pdf";
try {
Map<String, Object> result = processRateConfirmation(filePath);
System.out.println("\n✓ Pipeline complete");
System.exit(0);
} catch (Exception err) {
System.err.println("✗ Pipeline error: " + err.getMessage());
err.printStackTrace();
System.exit(1);
}
}
public static Map<String, Object> processRateConfirmation(String filePath)
throws Exception {
System.out.println("[RateConfirmation] Processing: " + filePath);
// Convert local file to data URL
byte[] fileBuffer = Files.readAllBytes(Paths.get(filePath));
String base64Content = Base64.getEncoder().encodeToString(fileBuffer);
String dataUrl = "data:application/octet-stream;base64," + base64Content;
// Step 1: Parse rate confirmation to markdown
System.out.println("[1/2] Parsing rate confirmation...");
Map<String, Object> parseConfig = new HashMap<>();
Map<String, Object> blockOptions = new HashMap<>();
Map<String, Object> textOptions = new HashMap<>();
Map<String, Object> agenticOptions = new HashMap<>();
agenticOptions.put("enabled", true);
textOptions.put("agentic", agenticOptions);
blockOptions.put("text", textOptions);
parseConfig.put("blockOptions", blockOptions);
Map<String, Object> chunkingStrategy = new HashMap<>();
chunkingStrategy.put("type", "document");
parseConfig.put("chunkingStrategy", chunkingStrategy);
Map<String, Object> parsePayload = new HashMap<>();
Map<String, String> fileMap = new HashMap<>();
fileMap.put("url", dataUrl);
parsePayload.put("file", fileMap);
parsePayload.put("config", parseConfig);
Map<String, Object> parseRun = createAndPollParseRun(parsePayload);
if (!"PROCESSED".equals(parseRun.get("status"))) {
throw new Exception("Parse failed: " + parseRun.get("status"));
}
String markdownContent = extractMarkdownFromParseRun(parseRun);
System.out.println("[Parse] Extracted " + markdownContent.length() + " characters of markdown");
// Step 2: Extract structured fields
System.out.println("[2/2] Extracting rate confirmation fields...");
Map<String, Object> schema = buildRateConfirmationSchema();
Map<String, Object> extractConfig = new HashMap<>();
extractConfig.put("schema", schema);
Map<String, Object> advancedOptions = new HashMap<>();
Map<String, Object> reviewAgent = new HashMap<>();
reviewAgent.put("enabled", true);
advancedOptions.put("reviewAgent", reviewAgent);
advancedOptions.put("advancedMultimodalEnabled", true);
extractConfig.put("advancedOptions", advancedOptions);
Map<String, Object> extractPayload = new HashMap<>();
extractPayload.put("file", fileMap);
extractPayload.put("config", extractConfig);
Map<String, Object> extractRun = createAndPollExtractRun(extractPayload);
if (!"PROCESSED".equals(extractRun.get("status"))) {
throw new Exception("Extraction failed: " + extractRun.get("status"));
}
Map<String, Object> extractedData = (Map<String, Object>) extractRun.get("output");
if (extractedData != null) {
extractedData = (Map<String, Object>) extractedData.get("value");
}
System.out.println("[Extract] Successfully extracted rate confirmation fields");
// Log results
System.out.println("\n=== RATE CONFIRMATION EXTRACTION RESULTS ===");
System.out.println(jsonStringify(extractedData));
// Build response
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("pro_number", extractedData.get("pro_number"));
Map<String, Object> carrier = new HashMap<>();
carrier.put("name", extractedData.get("carrier_name"));
carrier.put("contact", extractedData.get("carrier_contact"));
carrier.put("phone", extractedData.get("carrier_phone"));
response.put("carrier", carrier);
Map<String, Object> shipper = new HashMap<>();
shipper.put("name", extractedData.get("shipper_name"));
response.put("shipper", shipper);
response.put("shipment", extractedData.get("shipment_details"));
response.put("pricing", extractedData.get("rate_charges"));
Map<String, Object> logistics = new HashMap<>();
Map<String, Object> pickup = new HashMap<>();
pickup.put("location", extractedData.get("pickup_location"));
pickup.put("appointment", extractedData.get("pickup_appointment"));
Map<String, Object> delivery = new HashMap<>();
delivery.put("location", extractedData.get("delivery_location"));
delivery.put("appointment", extractedData.get("delivery_appointment"));
logistics.put("pickup", pickup);
logistics.put("delivery", delivery);
response.put("logistics", logistics);
response.put("confirmation_date", extractedData.get("confirmation_date"));
response.put("raw_extraction", extractedData);
return response;
}
private static Map<String, Object> createAndPollParseRun(Map<String, Object> payload)
throws Exception {
String requestBody = jsonStringify(payload);
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/v1/parseRuns"))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> result = parseJson(response.body());
String runId = (String) result.get("id");
return pollRun(runId, "parseRuns");
}
private static Map<String, Object> createAndPollExtractRun(Map<String, Object> payload)
throws Exception {
String requestBody = jsonStringify(payload);
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/v1/extractRuns"))
.header("Authorization", "Bearer " + API_KEY)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> result = parseJson(response.body());
String runId = (String) result.get("id");
return pollRun(runId, "extractRuns");
}
private static Map<String, Object> pollRun(String runId, String runType) throws Exception {
while (true) {
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(API_BASE + "/v1/" + runType + "/" + runId))
.header("Authorization", "Bearer " + API_KEY)
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> result = parseJson(response.body());
String status = (String) result.get("status");
if ("PROCESSED".equals(status) || "FAILED".equals(status)) {
return result;
}
Thread.sleep(1000);
}
}
private static String extractMarkdownFromParseRun(Map<String, Object> parseRun) {
Map<String, Object> output = (Map<String, Object>) parseRun.get("output");
if (output == null) return "";
java.util.List<Map<String, Object>> chunks =
(java.util.List<Map<String, Object>>) output.get("chunks");
if (chunks == null) return "";
StringBuilder sb = new StringBuilder();
for (Map<String, Object> chunk : chunks) {
String content = (String) chunk.get("content");
if (content != null) {
sb.append(content).append("\n\n");
}
}
return sb.toString();
}
private static Map<String, Object> buildRateConfirmationSchema() {
Map<String, Object> schema = new HashMap<>();
schema.put("type", "object");
Map<String, Object> properties = new HashMap<>();
properties.put("pro_number", stringProperty("PRO number identifier for the rate confirmation"));
properties.put("carrier_name", stringProperty("Full legal name of the carrier company"));
properties.put("carrier_contact", stringProperty("Primary contact person name at carrier"));
properties.put("carrier_phone", stringProperty("Carrier phone number"));
properties.put("shipper_name", stringProperty("Legal company name of the shipper"));
Map<String, Object> shipmentDetails = new HashMap<>();
shipmentDetails.put("type", new Object[] {"object", "null"});
Map<String, Object> shipmentProps = new HashMap<>();
shipmentProps.put("truck_size_type", stringProperty("Type and size of truck"));
shipmentProps.put("cargo_description", stringProperty("What is being shipped"));
shipmentProps.put("weight_lbs", stringProperty("Total shipment weight in pounds"));
shipmentProps.put("pieces", stringProperty("Number of pallets, cases, or units"));
shipmentProps.put("miles", stringProperty("Estimated distance in miles"));
shipmentProps.put("temperature_range", stringProperty("Required reefer temperature"));
shipmentDetails.put("properties", shipmentProps);
properties.put("shipment_details", shipmentDetails);
Map<String, Object> rateCharges = new HashMap<>();
rateCharges.put("type", new Object[] {"object", "null"});
Map<String, Object> rateProps = new HashMap<>();
rateProps.put("line_haul_rate", stringProperty("Base linehaul rate"));
rateProps.put("additional_charges", stringProperty("Extra fees"));
rateProps.put("total_rate", stringProperty("Final total amount due"));
rateCharges.put("properties", rateProps);
properties.put("rate_charges", rateCharges);
properties.put("pickup_location", stringProperty("Pickup address and facility details"));
properties.put("pickup_appointment", stringProperty("Pickup appointment date and time"));
properties.put("delivery_location", stringProperty("Delivery address and facility details"));
properties.put("delivery_appointment", stringProperty("Delivery appointment date and time"));
properties.put("confirmation_date", stringProperty("Date and time confirmation was issued"));
schema.put("properties", properties);
return schema;
}
private static Map<String, Object> stringProperty(String description) {
Map<String, Object> prop = new HashMap<>();
prop.put("type", new Object[] {"string", "null"});
prop.put("description", description);
return prop;
}
private static String jsonStringify(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 java.util.Map) {
Map<String, Object> map = (Map<String, Object>) obj;
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) sb.append(",");
sb.append("\"").append(entry.getKey()).append("\":");
sb.append(jsonStringify(entry.getValue()));
first = false;
}
sb.append("}");
return sb.toString();
}
if (obj instanceof java.util.List) {
java.util.List<?> list = (java.util.List<?>) obj;
StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (Object item : list) {
if (!first) sb.append(",");
sb.append(jsonStringify(item));
first = false;
}
sb.append("]");
return sb.toString();
}
return "\"" + escapeJson(obj.toString()) + "\"";
}
private static String escapeJson(String s) {
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
private static Map<String, Object> parseJson(String json) {
// Simple JSON parser for response objects
json = json.trim();
if (!json.startsWith("{")) return new HashMap<>();
Map<String, Object> result = new HashMap<>();
int depth = 0;
StringBuilder key = new StringBuilder();
StringBuilder value = new StringBuilder();
boolean inKey = true;
boolean inString = false;
boolean escaped = false;
for (int i = 1; i < json.length() - 1; i++) {
char c = json.charAt(i);
if (escaped) {
if (inString) value.append(c);
escaped = false;
continue;
}
if (c == '\\') {
escaped = true;
if (inString) value.append(c);
continue;
}
if (c == '"') {
inString = !inString;
if (!inKey && inString) value.append(c);
else if (inKey && !inString && key.length() > 0) {
inKey = false;
}
continue;
}
if (inString) {
value.append(c);
continue;
}
if (c == ':' && !inKey) {
continue;
}
if (c == ',' && depth == 0) {
String k = key.toString().trim();
String v = value.toString().trim();
result.put(k, parseValue(v));
key = new StringBuilder();
value = new StringBuilder();
inKey = true;
continue;
}
if (c == '{' || c == '[') {
depth++;
value.append(c);
continue;
}
if (c == '}' || c == ']') {
depth--;
value.append(c);
continue;
}
if (inKey && c != ' ') {
key.append(c);
} else if (!inKey) {
value.append(c);
}
}
if (key.length() > 0) {
String k = key.toString().trim();
String v = value.toString().trim();
result.put(k, parseValue(v));
}
return result;
}
private static Object parseValue(String v) {
v = v.trim();
if (v.isEmpty()) return null;
if ("null".equals(v)) return null;
if ("true".equals(v)) return true;
if ("false".equals(v)) return false;
if (v.startsWith("\"") && v.endsWith("\"")) {
return v.substring(1, v.length() - 1);
}
if (v.startsWith("{") || v.startsWith("[")) {
return v;
}
try {
if (v.contains(".")) return Double.parseDouble(v);
return Long.parseLong(v);
} catch (NumberFormatException e) {
return v;
}
}
}// This code uses the Extend REST API directly because Extend has no official Go SDK yet.
// It calls https://api.extend.ai endpoints with standard net/http and encoding/json.
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"time"
)
const extendAPIBase = "https://api.extend.ai"
type ExtendClient struct {
token string
}
func NewExtendClient(token string) *ExtendClient {
return &ExtendClient{token: token}
}
func (c *ExtendClient) do(method, path string, body interface{}) ([]byte, error) {
url := extendAPIBase + path
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, url, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(respBody))
}
return respBody, nil
}
type ParseRunRequest struct {
File FileInput `json:"file"`
Config ParseConfig `json:"config"`
}
type FileInput struct {
URL string `json:"url"`
}
type ParseConfig struct {
BlockOptions BlockOptions `json:"blockOptions"`
ChunkingStrategy ChunkingStrategy `json:"chunkingStrategy"`
}
type BlockOptions struct {
Text TextOptions `json:"text"`
}
type TextOptions struct {
Agentic AgenticOptions `json:"agentic"`
}
type AgenticOptions struct {
Enabled bool `json:"enabled"`
}
type ChunkingStrategy struct {
Type string `json:"type"`
}
type ParseRunResponse struct {
Status string `json:"status"`
Output struct {
Chunks []struct {
Content string `json:"content"`
} `json:"chunks"`
} `json:"output"`
}
type ExtractRunRequest struct {
File FileInput `json:"file"`
Config ExtractConfig `json:"config"`
}
type ExtractConfig struct {
Schema json.RawMessage `json:"schema"`
AdvancedOptions AdvancedOptions `json:"advancedOptions"`
}
type AdvancedOptions struct {
ReviewAgent struct {
Enabled bool `json:"enabled"`
} `json:"reviewAgent"`
AdvancedMultimodalEnabled bool `json:"advancedMultimodalEnabled"`
}
type ExtractRunResponse struct {
Status string `json:"status"`
Output struct {
Value map[string]interface{} `json:"value"`
} `json:"output"`
}
type RateConfirmationResult struct {
Status string `json:"status"`
ProNumber interface{} `json:"pro_number"`
Carrier map[string]interface{} `json:"carrier"`
Shipper map[string]interface{} `json:"shipper"`
Shipment interface{} `json:"shipment"`
Pricing interface{} `json:"pricing"`
Logistics map[string]interface{} `json:"logistics"`
ConfirmationDate interface{} `json:"confirmation_date"`
RawExtraction map[string]interface{} `json:"raw_extraction"`
}
func processRateConfirmation(filePath string) (*RateConfirmationResult, error) {
fmt.Printf("[RateConfirmation] Processing: %s\n", filePath)
apiKey := os.Getenv("EXTEND_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("EXTEND_API_KEY environment variable not set")
}
client := NewExtendClient(apiKey)
// Read file and convert to data URL
fileBuffer, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
dataURL := fmt.Sprintf("data:application/octet-stream;base64,%s", base64.StdEncoding.EncodeToString(fileBuffer))
// Step 1: Parse rate confirmation to markdown
fmt.Println("[1/2] Parsing rate confirmation...")
parseReq := ParseRunRequest{
File: FileInput{URL: dataURL},
Config: ParseConfig{
BlockOptions: BlockOptions{
Text: TextOptions{
Agentic: AgenticOptions{Enabled: true},
},
},
ChunkingStrategy: ChunkingStrategy{Type: "document"},
},
}
parseResp, err := client.do("POST", "/v1/parseRuns", parseReq)
if err != nil {
return nil, err
}
var parseRun ParseRunResponse
if err := json.Unmarshal(parseResp, &parseRun); err != nil {
return nil, err
}
if parseRun.Status != "PROCESSED" {
return nil, fmt.Errorf("parse failed: %s", parseRun.Status)
}
var markdownContent string
for _, chunk := range parseRun.Output.Chunks {
markdownContent += chunk.Content + "\n\n"
}
fmt.Printf("[Parse] Extracted %d characters of markdown\n", len(markdownContent))
// Step 2: Extract structured fields
fmt.Println("[2/2] Extracting rate confirmation fields...")
schema := json.RawMessage(`{
"type": "object",
"properties": {
"pro_number": {"type": ["string", "null"], "description": "PRO number identifier for the rate confirmation"},
"carrier_name": {"type": ["string", "null"], "description": "Full legal name of the carrier company"},
"carrier_contact": {"type": ["string", "null"], "description": "Primary contact person name at carrier"},
"carrier_phone": {"type": ["string", "null"], "description": "Carrier phone number"},
"shipper_name": {"type": ["string", "null"], "description": "Legal company name of the shipper"},
"shipment_details": {
"type": ["object", "null"],
"description": "Object containing all shipment specifications",
"properties": {
"truck_size_type": {"type": ["string", "null"]},
"cargo_description": {"type": ["string", "null"]},
"weight_lbs": {"type": ["string", "null"]},
"pieces": {"type": ["string", "null"]},
"miles": {"type": ["string", "null"]},
"temperature_range": {"type": ["string", "null"]}
}
},
"rate_charges": {
"type": ["object", "null"],
"description": "Pricing breakdown",
"properties": {
"line_haul_rate": {"type": ["string", "null"]},
"additional_charges": {"type": ["string", "null"]},
"total_rate": {"type": ["string", "null"]}
}
},
"pickup_location": {"type": ["string", "null"], "description": "Pickup address and facility details"},
"pickup_appointment": {"type": ["string", "null"], "description": "Pickup appointment date and time"},
"delivery_location": {"type": ["string", "null"], "description": "Delivery address and facility details"},
"delivery_appointment": {"type": ["string", "null"], "description": "Delivery appointment date and time"},
"confirmation_date": {"type": ["string", "null"], "description": "Date and time confirmation was issued"}
}
}`)
extractReq := ExtractRunRequest{
File: FileInput{URL: dataURL},
Config: ExtractConfig{
Schema: schema,
AdvancedOptions: AdvancedOptions{
ReviewAgent: struct {
Enabled bool `json:"enabled"`
}{Enabled: true},
AdvancedMultimodalEnabled: true,
},
},
}
extractResp, err := client.do("POST", "/v1/extractRuns", extractReq)
if err != nil {
return nil, err
}
var extractRun ExtractRunResponse
if err := json.Unmarshal(extractResp, &extractRun); err != nil {
return nil, err
}
if extractRun.Status != "PROCESSED" {
return nil, fmt.Errorf("extraction failed: %s", extractRun.Status)
}
extractedData := extractRun.Output.Value
fmt.Println("[Extract] Successfully extracted rate confirmation fields")
// Log results
fmt.Println("\n=== RATE CONFIRMATION EXTRACTION RESULTS ===")
resultJSON, _ := json.MarshalIndent(extractedData, "", " ")
fmt.Println(string(resultJSON))
// Build result
result := &RateConfirmationResult{
Status: "success",
ProNumber: extractedData["pro_number"],
RawExtraction: extractedData,
}
result.Carrier = map[string]interface{}{
"name": extractedData["carrier_name"],
"contact": extractedData["carrier_contact"],
"phone": extractedData["carrier_phone"],
}
result.Shipper = map[string]interface{}{
"name": extractedData["shipper_name"],
}
result.Shipment = extractedData["shipment_details"]
result.Pricing = extractedData["rate_charges"]
result.Logistics = map[string]interface{}{
"pickup": map[string]interface{}{
"location": extractedData["pickup_location"],
"appointment": extractedData["pickup_appointment"],
},
"delivery": map[string]interface{}{
"location": extractedData["delivery_location"],
"appointment": extractedData["delivery_appointment"],
},
}
result.ConfirmationDate = extractedData["confirmation_date"]
return result, nil
}
func main() {
flag.Parse()
filePath := flag.Arg(0)
if filePath == "" {
filePath = "./rate_confirmation.pdf"
}
result, err := processRateConfirmation(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "✗ Pipeline error: %v\n", err)
os.Exit(1)
}
fmt.Println("\n✓ Pipeline complete")
resultJSON, _ := json.MarshalIndent(result, "", " ")
fmt.Println(string(resultJSON))
}// Deploy the "Rate Confirmation" 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/rate-confirmation.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: rate-confirmation).
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, "rate-confirmation.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": "Rate Confirmation Processing Pipeline",
"steps": [
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": [
{
"step": "parse1"
}
]
},
{
"name": "parse1",
"type": "PARSE",
"config": {
"parseConfig": {
"blockOptions": {
"text": {
"agentic": {
"enabled": true
}
}
},
"chunkingStrategy": {
"type": "document"
}
}
}
}
]
};
async function main() {
console.log(`Deploying "${WORKFLOW.name}"…`);
if (state.workflowId) {
console.log(`✓ workflow already provisioned (${state.workflowId}) — updating steps`);
await api("POST", `/workflows/${state.workflowId}`, { steps: WORKFLOW.steps });
} else {
// Reuse an existing workflow with the same name if one exists (e.g. a
// previous run's state file was lost) instead of creating a duplicate.
try {
const list = await api("GET", `/workflows?name=${encodeURIComponent(WORKFLOW.name)}`);
const items = (list.data ?? list.items ?? []) as Array<{ name?: string; id?: string }>;
const existing = items.find((x) => x.name === WORKFLOW.name);
if (existing?.id) {
state.workflowId = existing.id; saveState();
console.log(`✓ workflow "${WORKFLOW.name}" found in your account (${existing.id}) — updating steps`);
await api("POST", `/workflows/${existing.id}`, { steps: WORKFLOW.steps });
}
} catch { /* lookup is best-effort; fall through to create */ }
if (!state.workflowId) {
const created = await api("POST", "/workflows", WORKFLOW);
const wfId = created.id ?? created.workflow?.id;
if (!wfId) throw new Error("Could not read created workflow id from response");
state.workflowId = wfId; saveState();
console.log(`+ created workflow (${wfId})`);
}
}
// Deploy the current draft as a new version so the workflow is runnable —
// best-effort: some accounts/plans may not require this explicit step.
await api("POST", `/workflows/${state.workflowId}/versions`, {}).catch(() => {});
console.log("\nDone. Run documents through it with:");
console.log(` POST ${API}/workflow_runs { workflow: { id: "${state.workflowId}" }, file: { url: "https://…" } }`);
console.log("Or open the workflow in the Extend dashboard to review and deploy it.");
}
main().catch((e) => { console.error(e.message ?? e); process.exit(1); });
import os
import json
import sys
from pathlib import Path
from extend_ai import Extend
API_KEY = os.environ.get("EXTEND_API_KEY")
if not API_KEY:
print("Set EXTEND_API_KEY first.", file=sys.stderr)
sys.exit(1)
STATE_DIR = Path.cwd() / ".extend"
STATE_FILE = STATE_DIR / "rate-confirmation.json"
state = {}
if STATE_FILE.exists():
state = json.loads(STATE_FILE.read_text())
def save_state():
STATE_DIR.mkdir(parents=True, exist_ok=True)
STATE_FILE.write_text(json.dumps(state, indent=2))
client = Extend(token=API_KEY)
WORKFLOW = {
"name": "Rate Confirmation Processing Pipeline",
"steps": [
{
"name": "startTrigger1",
"type": "TRIGGER",
"next": [
{
"step": "parse1"
}
]
},
{
"name": "parse1",
"type": "PARSE",
"config": {
"parseConfig": {
"blockOptions": {
"text": {
"agentic": {
"enabled": True
}
}
},
"chunkingStrategy": {
"type": "document"
}
}
}
}
]
}
def main():
print(f'Deploying "{WORKFLOW["name"]}…"')
if state.get("workflowId"):
print(f'✓ workflow already provisioned ({state["workflowId"]}) — updating steps')
client.workflows.update(id=state["workflowId"], steps=WORKFLOW["steps"])
else:
existing_id = 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 item in items:
if item.get("name") == WORKFLOW["name"]:
existing_id = item.get("id")
break
if existing_id:
state["workflowId"] = existing_id
save_state()
print(f'✓ workflow "{WORKFLOW["name"]}" found in your account ({existing_id}) — updating steps')
client.workflows.update(id=existing_id, steps=WORKFLOW["steps"])
except Exception:
pass
if not state.get("workflowId"):
created = client.workflows.create(**WORKFLOW)
wf_id = created.id if hasattr(created, "id") else (created.workflow.id if hasattr(created, "workflow") else None)
if not wf_id:
raise ValueError("Could not read created workflow id from response")
state["workflowId"] = wf_id
save_state()
print(f"+ created workflow ({wf_id})")
try:
client.workflows.create_version(id=state["workflowId"])
except Exception:
pass
print("\nDone. Run documents through it with:")
print(f' POST https://api.extend.ai/workflow_runs {{ "workflow": {{ "id": "{state["workflowId"]}" }}, "file": {{ "url": "https://…" }} }}')
print("Or open the workflow in the Extend dashboard to review and deploy it.")
if __name__ == "__main__":
try:
main()
except Exception as e:
print(str(e), file=sys.stderr)
sys.exit(1)// This code calls Extend's REST API directly using Java's built-in HttpClient.
// Extend does not publish an official Java SDK; this approach has zero external dependencies.
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.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class RateConfirmationProvisioner {
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("rate-confirmation.json");
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
static class State {
String workflowId;
}
private static State state = new State();
public static void main(String[] args) {
try {
if (API_KEY == null || API_KEY.isEmpty()) {
System.err.println("Set EXTEND_API_KEY first.");
System.exit(1);
}
loadState();
Map<String, Object> workflow = buildWorkflow();
String workflowName = (String) workflow.get("name");
System.out.println("Deploying \"" + workflowName + "\"…");
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", workflow.get("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<?> items = (List<?>) listResponse.getOrDefault("data",
listResponse.getOrDefault("items", List.of()));
for (Object item : items) {
if (item instanceof Map) {
Map<?, ?> itemMap = (Map<?, ?>) item;
if (workflowName.equals(itemMap.get("name"))) {
String existingId = (String) itemMap.get("id");
if (existingId != null) {
state.workflowId = existingId;
saveState();
System.out.println("✓ workflow \"" + workflowName + "\" found in your account (" + existingId + ") — updating steps");
Map<String, Object> updateBody = new HashMap<>();
updateBody.put("steps", workflow.get("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) {
Map<?, ?> workflowObj = (Map<?, ?>) created.get("workflow");
if (workflowObj != null) {
wfId = (String) workflowObj.get("id");
}
}
if (wfId == null) {
throw new RuntimeException("Could not read created workflow id from response");
}
state.workflowId = wfId;
saveState();
System.out.println("+ created workflow (" + wfId + ")");
}
}
try {
api("POST", "/workflows/" + state.workflowId + "/versions", new HashMap<>());
} 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> buildWorkflow() {
Map<String, Object> workflow = new LinkedHashMap<>();
workflow.put("name", "Rate Confirmation Processing Pipeline");
Map<String, Object> trigger = new LinkedHashMap<>();
trigger.put("name", "startTrigger1");
trigger.put("type", "TRIGGER");
Map<String, Object> nextStep = new LinkedHashMap<>();
nextStep.put("step", "parse1");
trigger.put("next", List.of(nextStep));
Map<String, Object> parseStep = new LinkedHashMap<>();
parseStep.put("name", "parse1");
parseStep.put("type", "PARSE");
Map<String, Object> config = new LinkedHashMap<>();
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);
config.put("parseConfig", parseConfig);
parseStep.put("config", config);
workflow.put("steps", List.of(trigger, parseStep));
return workflow;
}
private static Map<String, Object> api(String method, String pathName, Map<String, Object> body)
throws IOException, InterruptedException {
String url = API + pathName;
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + API_KEY)
.header("x-extend-api-version", VERSION);
if (body != null) {
String jsonBody = mapToJson(body);
requestBuilder.header("Content-Type", "application/json")
.method(method, HttpRequest.BodyPublishers.ofString(jsonBody));
} else {
requestBuilder.method(method, HttpRequest.BodyPublishers.noBody());
}
HttpRequest request = requestBuilder.build();
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
Map<String, Object> data = new HashMap<>();
try {
data = jsonToMap(response.body());
} catch (Exception e) {
// empty map on parse failure
}
if (response.statusCode() < 200 || response.statusCode() >= 300) {
String errorMsg = mapToJson(data);
if (errorMsg.length() > 300) {
errorMsg = errorMsg.substring(0, 300);
}
throw new RuntimeException(method + " " + pathName + " failed (" + response.statusCode() + "): " + errorMsg);
}
return data;
}
private static void loadState() throws IOException {
if (Files.exists(STATE_FILE)) {
String content = Files.readString(STATE_FILE);
Map<String, Object> parsed = jsonToMap(content);
state.workflowId = (String) parsed.get("workflowId");
}
}
private static void saveState() throws IOException {
Files.createDirectories(STATE_DIR);
Map<String, Object> stateMap = new HashMap<>();
if (state.workflowId != null) {
stateMap.put("workflowId", state.workflowId);
}
String json = mapToJson(stateMap);
Files.writeString(STATE_FILE, json);
}
private static String mapToJson(Map<String, Object> map) {
StringBuilder sb = new StringBuilder();
sb.append("{");
boolean first = true;
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (!first) sb.append(",");
first = false;
sb.append("\"").append(escapeJson(entry.getKey())).append("\":");
sb.append(valueToJson(entry.getValue()));
}
sb.append("}");
return sb.toString();
}
private static String valueToJson(Object value) {
if (value == null) {
return "null";
} else if (value instanceof String) {
return "\"" + escapeJson((String) value) + "\"";
} else if (value instanceof Boolean) {
return value.toString();
} else if (value instanceof Number) {
return value.toString();
} else if (value instanceof Map) {
return mapToJson((Map<String, Object>) value);
} else if (value instanceof List) {
StringBuilder sb = new StringBuilder("[");
List<?> list = (List<?>) value;
for (int i = 0; i < list.size(); i++) {
if (i > 0) sb.append(",");
sb.append(valueToJson(list.get(i)));
}
sb.append("]");
return sb.toString();
}
return "null";
}
private static String escapeJson(String s) {
return s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
private static Map<String, Object> jsonToMap(String json) {
json = json.trim();
if (!json.startsWith("{")) {
return new HashMap<>();
}
Map<String, Object> result = new HashMap<>();
int depth = 0;
int i = 1;
String currentKey = null;
StringBuilder currentValue = new StringBuilder();
boolean inString = false;
boolean escaped = false;
while (i < json.length() - 1) {
char c = json.charAt(i);
if (escaped) {
currentValue.append(c);
escaped = false;
i++;
continue;
}
if (c == '\\' && inString) {
escaped = true;
currentValue.append(c);
i++;
continue;
}
if (c == '"') {
inString = !inString;
currentValue.append(c);
i++;
continue;
}
if (inString) {
currentValue.append(c);
i++;
continue;
}
if (c == '{' || c == '[') {
depth++;
currentValue.append(c);
} else if (c == '}' || c == ']') {
depth--;
currentValue.append(c);
} else if (c == ':' && depth == 0 && currentKey == null) {
currentKey = currentValue.toString().trim();
if (currentKey.startsWith("\"") && currentKey.endsWith("\"")) {
currentKey = currentKey.substring(1, currentKey.length() - 1);
}
currentValue = new StringBuilder();
} else if (c == ',' && depth == 0) {
String val = currentValue.toString().trim();
if (currentKey != null) {
result.put(currentKey, parseJsonValue(val));
}
currentKey = null;
currentValue = new StringBuilder();
} else {
currentValue.append(c);
}
i++;
}
if (currentKey != null) {
String val = currentValue.toString().trim();
result.put(currentKey, parseJsonValue(val));
}
return result;
}
private static Object parseJsonValue(String value) {
value = value.trim();
if (value.equals("null")) {
return null;
} else if (value.equals("true")) {
return true;
} else if (value.equals("false")) {
return false;
} else if (value.startsWith("\"") && value.endsWith("\"")) {
return value.substring(1, value.length() - 1);
} else if (value.startsWith("{")) {
return jsonToMap(value);
} else if (value.startsWith("[")) {
return jsonToList(value);
} else {
try {
if (value.contains(".")) {
return Double.parseDouble(value);
} else {
return Long.parseLong(value);
}
} catch (NumberFormatException e) {
return value;
}
}
}
private static List<?> jsonToList(String json) {
List<Object> result = new java.util.ArrayList<>();
json = json.trim();
if (!json.startsWith("[") || !json.endsWith("]")) {
return result;
}
int depth = 0;
int i = 1;
StringBuilder currentValue = new StringBuilder();
boolean inString = false;
boolean escaped = false;
while (i < json.length() - 1) {
char c = json.charAt(i);
if (escaped) {
currentValue.append(c);
escaped = false;
i++;
continue;
}
if (c == '\\' && inString) {
escaped = true;
currentValue.append(c);
i++;
continue;
}
if (c == '"') {
inString = !inString;
currentValue.append(c);
i++;
continue;
}
if (inString) {
currentValue.append(c);
i++;
continue;
}
if (c == '{' || c == '[') {
depth++;
currentValue.append(c);
} else if (c == '}' || c == ']') {
depth--;
currentValue.append(c);
} else if (c == ',' && depth == 0) {
String val = currentValue.toString().trim();
result.add(parseJsonValue(val));
currentValue = new StringBuilder();
} else {
currentValue.append(c);
}
i++;
}
if (currentValue.length() > 0) {
String val = currentValue.toString().trim();
result.add(parseJsonValue(val));
}
return result;
}
}// This script uses the Extend REST API directly because Extend has no official Go SDK yet.
// It deploys the "Rate Confirmation" 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: rate-confirmation).
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
)
const (
API = "https://api.extend.ai"
VERSION = "2026-02-09"
)
var (
apiKey string
stateDir string
stateFile string
)
type State struct {
WorkflowID string `json:"workflowId,omitempty"`
}
var state State
func init() {
apiKey = os.Getenv("EXTEND_API_KEY")
if apiKey == "" {
fmt.Fprintf(os.Stderr, "Set EXTEND_API_KEY first.\n")
os.Exit(1)
}
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get working directory: %v\n", err)
os.Exit(1)
}
stateDir = filepath.Join(cwd, ".extend")
stateFile = filepath.Join(stateDir, "rate-confirmation.json")
// Load existing state if it exists
if data, err := os.ReadFile(stateFile); err == nil {
json.Unmarshal(data, &state)
}
}
func saveState() error {
if err := os.MkdirAll(stateDir, 0755); err != nil {
return err
}
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
return os.WriteFile(stateFile, data, 0644)
}
func apiCall(method, pathName string, body interface{}) (map[string]interface{}, error) {
var reqBody io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
reqBody = bytes.NewReader(data)
}
req, err := http.NewRequest(method, API+pathName, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
req.Header.Set("x-extend-api-version", VERSION)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var data map[string]interface{}
json.Unmarshal(respBody, &data)
if resp.StatusCode >= 400 {
respStr := string(respBody)
if len(respStr) > 300 {
respStr = respStr[:300]
}
return nil, fmt.Errorf("%s %s failed (%d): %s", method, pathName, resp.StatusCode, respStr)
}
return data, nil
}
type WorkflowStep struct {
Name string `json:"name"`
Type string `json:"type"`
Next []interface{} `json:"next,omitempty"`
Config interface{} `json:"config,omitempty"`
}
type Workflow struct {
Name string `json:"name"`
Steps []WorkflowStep `json:"steps"`
}
var WORKFLOW = Workflow{
Name: "Rate Confirmation Processing Pipeline",
Steps: []WorkflowStep{
{
Name: "startTrigger1",
Type: "TRIGGER",
Next: []interface{}{
map[string]string{"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]bool{
"enabled": true,
},
},
},
"chunkingStrategy": map[string]string{
"type": "document",
},
},
},
},
},
}
func main() {
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), map[string]interface{}{"steps": WORKFLOW.Steps})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
} else {
// Try to find an existing workflow with the same name
query := url.QueryEscape(WORKFLOW.Name)
list, err := apiCall("GET", fmt.Sprintf("/workflows?name=%s", query), nil)
if err == nil {
var items []map[string]interface{}
if data, ok := list["data"].([]interface{}); ok {
for _, item := range data {
if m, ok := item.(map[string]interface{}); ok {
items = append(items, m)
}
}
} else if data, ok := list["items"].([]interface{}); ok {
for _, item := range data {
if m, ok := item.(map[string]interface{}); ok {
items = append(items, m)
}
}
}
for _, item := range items {
if name, ok := item["name"].(string); ok && name == WORKFLOW.Name {
if id, ok := item["id"].(string); ok {
state.WorkflowID = id
saveState()
fmt.Printf("✓ workflow \"%s\" found in your account (%s) — updating steps\n", WORKFLOW.Name, id)
_, err := apiCall("POST", fmt.Sprintf("/workflows/%s", id), map[string]interface{}{"steps": WORKFLOW.Steps})
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
break
}
}
}
}
if state.WorkflowID == "" {
created, err := apiCall("POST", "/workflows", WORKFLOW)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
var wfID string
if id, ok := created["id"].(string); ok {
wfID = id
} else if workflow, ok := created["workflow"].(map[string]interface{}); ok {
if id, ok := workflow["id"].(string); ok {
wfID = id
}
}
if wfID == "" {
fmt.Fprintf(os.Stderr, "Could not read created workflow id from response\n")
os.Exit(1)
}
state.WorkflowID = wfID
saveState()
fmt.Printf("+ created workflow (%s)\n", wfID)
}
}
// Deploy the current draft as a new version (best-effort)
apiCall("POST", fmt.Sprintf("/workflows/%s/versions", state.WorkflowID), map[string]interface{}{})
fmt.Println("\nDone. Run documents through it with:")
fmt.Printf(" POST %s/workflow_runs { workflow: { id: \"%s\" }, file: { url: \"https://…\" } }\n", API, state.WorkflowID)
fmt.Println("Or open the workflow in the Extend dashboard to review and deploy it.")
}A Rate Confirmation template for freight transportation that captures carrier information, shipment details, pricing charges, and multi-stop delivery appointments. Commonly used in trucking logistics to confirm linehaul rates, special handling instructions, and appointment scheduling for pickup and delivery locations.