// Deploy the "Bill of Lading" 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/bill-of-lading.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: bill-of-lading). 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, "bill-of-lading.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": "Bill of Lading Processing Pipeline", "steps": [ { "name": "startTrigger1", "type": "TRIGGER", "next": [ { "step": "parse1" } ] }, { "name": "parse1", "type": "PARSE", "config": { "parseConfig": { "blockOptions": { "text": { "agentic": { "enabled": false } } }, "chunkingStrategy": { "type": "document" } } }, "next": [ { "step": "extraction2" } ] }, { "name": "extraction2", "type": "EXTRACT", "config": { "extractorConfig": { "schema": { "type": "object", "properties": { "bill_of_lading_number": { "type": [ "string", "null" ], "description": "Shipper's Bill of Lading Number (e.g., INTL-BL-09284)" }, "carrier_name": { "type": [ "string", "null" ], "description": "Name of the carrier (e.g., Continental Logistics Group)" }, "carrier_scac_code": { "type": [ "string", "null" ], "description": "Standard Carrier Alpha Code for the carrier" }, "shipper_address": { "type": [ "string", "null" ], "description": "Complete shipper address including street, city, state, and zip" }, "consignee_name": { "type": [ "string", "null" ], "description": "Name of the consignee/receiver" }, "destination_address": { "type": [ "string", "null" ], "description": "Destination address including street, city, state, and zip" }, "delivering_carrier": { "type": [ "string", "null" ], "description": "Name of the delivering carrier (e.g., Oakland)" }, "trailer_number": { "type": [ "string", "null" ], "description": "Trailer identification number" }, "freight_items": { "type": "array", "description": "Array of freight line items with handling units, packages, description, weight, and class", "items": { "type": "object", "properties": { "description": { "type": [ "string", "null" ] }, "weight": { "type": [ "string", "null" ] }, "class": { "type": [ "string", "null" ] } } } }, "cod_amount": { "type": [ "string", "null" ], "description": "Collect on Delivery amount if applicable" }, "bill_type": { "type": [ "string", "null" ], "description": "Type of bill of lading (e.g., UNIFORM STRAIGHT BILL OF LADING)" } } }, "baseProcessor": "extraction_performance", "advancedOptions": { "reviewAgent": { "enabled": false }, "advancedMultimodalEnabled": false } } } } ] }; 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); });