Connecting your TMS
The
chargeback defense workflow
depends on one thing: a shipment record (verify_ai_shipments)
that the verification can join to via bol_number. You can get
those rows in three ways — via a managed TMS adapter, by pushing
shipments through the API yourself, or by tagging them inline on
each /api/v1/verify call.
This guide covers the four adapters we ship (or plan to ship), and the contract for writing your own.
Adapter status at v1
| TMS | Adapter | Status |
| ------------------ | ---------------------------------------------------- | --------------------------------------------------------------------- |
| McLeod LoadMaster | lib/integrations/runtime/mcleod.ts | Preview — stub. Typed contract defined; all methods throw. |
| SupplyPike | lib/integrations/runtime/supplypike.ts | Preview — stub. Outbound submit + inbound webhook both deferred. |
| Cargowise | — | Roadmap — v1.1. |
| MyEZclaim | — | Roadmap — v1.1. |
Each adapter file exposes the expected TypeScript surface and
a factory (createMcLeodAdapter / createSupplyPikeAdapter),
but every method throws
Error("Not implemented: ... deferred to the post-merge Cluster 3 framework"). Neither adapter authenticates against
the live vendor APIs yet, and they are not registered with the
Cluster 3 integration framework
(lib/verify-ai/integrations/registry.ts). Until they
graduate, populate verify_ai_shipments directly (service-role
client) or tag metadata.bol_number on captures so
draftDispute() can join when it's eventually called.
McLeod LoadMaster
McLeod is the dominant TMS for North American truckload carriers and a common one for asset-based brokers. Their integration surface is the LoadMaster REST API plus the IFS (Integrated File System) feed for higher-volume shippers.
Auth is OAuth2 client-credentials. The expected per-customer
config (stored on verify_ai_integrations.config once the adapter
moves into the Cluster 3 framework) is:
{
"base_url": "https://acme.loadmaster.com/lme",
"client_id": "<oauth2 client>",
"scope": "ifs.read ifs.documents"
}The client_secret lives in Supabase Vault, referenced by
verify_ai_integrations.credentials_ref.
Sync model. The runtime contract today (per
lib/integrations/runtime/mcleod.ts) exposes three calls:
const adapter = createMcLeodAdapter(config);
await adapter.getShipments(since); // since: Date — list shipments
await adapter.getShipment(bolNumber); // single lookup
await adapter.getDocuments(orderId); // POD / BOL document URLsAll three throw "Not implemented" at v1. When the adapter
graduates, a scheduled cron is expected to call getShipments()
nightly per customer and upsert into verify_ai_shipments keyed on
(customer_id, bol_number). The dispute drafter consumes the same
rows. Until then, McLeod customers should either push shipments to
verify_ai_shipments directly or rely on capture-time tagging.
SupplyPike
SupplyPike is the dispute-management layer many shippers already use for Walmart and Amazon Vendor deductions. The integration is designed to go both directions; both directions are stubbed today.
-
Outbound — VerifyAI submits drafted disputes (
verify_ai_chargeback_disputesrows withstatus: "draft") to SupplyPike for processing. Preview — stubbed at v1. The runtime contract (lib/integrations/runtime/supplypike.ts) is:tsconst adapter = createSupplyPikeAdapter(config); await adapter.createDispute(payload); // submit new dispute await adapter.getDispute(disputeId); // poll status await adapter.listDeductions(since); // pull new deductions -
Inbound — SupplyPike will eventually post adjudication outcomes to
POST /api/v1/integrations/supplypike/webhook, signature-verified withX-SupplyPike-Signature(HMAC-SHA256). The handler will match onsupplypike_dispute_id(the column already exists onverify_ai_chargeback_disputes) and flipstatustowon/lost/partial/expired. The route is not implemented at v1 — it lands together with the outbound adapter graduation.
Auth (when the outbound adapter ships) is a per-tenant Bearer API key. The expected config shape:
{
"base_url": "https://api.supplypike.com/v1",
"tenant_id": "<supplypike tenant uuid>"
}For customers without SupplyPike, render the dispute letter via chargeback defense and submit manually through whatever portal the retailer uses.
Cargowise and MyEZclaim (roadmap)
Cargowise (WiseTech) covers global freight forwarders and a significant slice of LTL operators. MyEZclaim is the dominant SaaS for OS&D claims management in North American freight.
Both are on the v1.1 roadmap. The interface for each will match
the McLeod / SupplyPike pattern: a syncShipments call to pull
load data, plus push hooks for disputes and claims. If you have
either deployed and want to be a launch partner, send a note to
support — we prioritize adapter rollout by signed pilots.
V1: bridging without an adapter
Until McLeod and SupplyPike graduate from preview, here's the recommended pattern for any customer with chargeback exposure:
Option 1 — Tag inline on capture
Pass metadata.bol_number on every /api/v1/verify call. The
verify pipeline simply records the value on the verification row —
no shipment lookup happens at capture time. When you later call
draftDispute(verificationId), the drafter reads the metadata and
joins by (customer_id, bol_number).
curl -X POST https://verify.switchlabs.dev/api/v1/verify \
-H "X-API-Key: vai_your_api_key" \
-F "image=@pallet.jpg" \
-F "policy=pol_damage_detect" \
-F 'metadata={"bol_number":"BOL-44128301","po_number":"PO-9912204"}'You can populate bol_number automatically by running the
BOL OCR endpoint on a snapshot of the
paperwork.
Option 2 — Insert shipments directly
Populate verify_ai_shipments from your service-role client. Either
feed it from a nightly export out of your TMS, or trigger inserts
on dispatch events from your warehouse system. The first-class
columns are customer_id, bol_number, pro_number, retailer,
ship_date, deliver_date, origin (jsonb), destination
(jsonb), status, and metadata (jsonb) — extras like MABD, PO
number, and DC code live under metadata. A minimal insert:
{
"customer_id": "<uuid>",
"bol_number": "BOL-44128301",
"retailer": "walmart",
"ship_date": "2026-05-15",
"deliver_date": "2026-05-20",
"metadata": {
"po_number": "PO-9912204",
"ship_to_dc": "DC-6094",
"mabd": "2026-05-20"
}
}The drafter performs the join at draftDispute() time — there is
no automatic verification-to-shipment binding event in the system.
A REST endpoint for shipment writes is not part of v1; admins go
through the service role today.
Writing a custom adapter
Two related interfaces live in the tree today; pick the one that matches what you're building.
1. Outbound integration framework
(lib/verify-ai/integrations/). This is the Cluster 3 framework
shared with claims platforms (Guidewire, Duck Creek, Mitchell). It
is the canonical home for adapters that push verification events
into a third-party system. The interface in
lib/verify-ai/integrations/types.ts is:
export interface IntegrationAdapter {
readonly type: string; // matches verify_ai_integrations.type
readonly label: string;
pushVerification(
integration: IntegrationConfig,
event: OutboundEvent,
): Promise<DeliveryResult>;
testConnection?(integration: IntegrationConfig): Promise<DeliveryResult>;
}Adapters call registerAdapter(adapter) from ./registry, and
side-effect imports live in lib/verify-ai/integrations/index.ts
(see guidewire/adapter.ts for a reference stub). Use this path
when you want a verification event to fan out to a TMS in real
time.
2. Runtime TMS adapters (lib/integrations/runtime/). This is
the standalone surface the McLeod and SupplyPike stubs live on
today. There is no shared base interface yet — each adapter exports
its own typed contract plus a factory:
// lib/integrations/runtime/<your-tms>.ts
export interface YourTmsConfig { /* ... */ }
export interface YourTmsAdapter {
getShipments(since: Date): Promise<unknown[]>;
// + any TMS-specific calls
}
export function createYourTmsAdapter(config: YourTmsConfig): YourTmsAdapter { /* ... */ }There is no lib/integrations/runtime/index.ts factory file yet —
callers import the per-vendor module directly. The McLeod and
SupplyPike stubs document the API surfaces they expect to wrap
when they graduate; mirror that structure for new vendors.
The Cluster 3 merge agent is expected to move both McLeod and
SupplyPike under lib/verify-ai/integrations/runtime/ and register
them with the framework registry above.
Operational prerequisites
A few platform-side prerequisites apply to any TMS integration that's going to scale:
- Webhook signing secrets — every inbound TMS webhook needs
its own secret, stored on the customer's integration config and
verified per request. The SupplyPike inbound contract is
HMAC-SHA256 against
X-SupplyPike-Signature. - Backpressure — high-volume customers (>10k shipments/day) should run the sync hourly rather than daily and consider a message queue between TMS pulls and the shipments table.
- PostGIS is a prerequisite for the separate Regulator
Dashboard work (Cluster 4.2 geofence joins), not for TMS
integrations. If you plan to enable both, run
docs/operations/postgis-check.mdonce before either ships.
What's next
- Chargeback defense — the primary downstream consumer of shipment data.
- Reading bills of lading from a photo —
populate
metadata.bol_numberautomatically from the BOL image. - Webhooks — receive outcome events from SupplyPike (and, when live, McLeod) once disputes resolve.