Estimating repair cost via Mitchell
Preview — not live yet. The Mitchell client is a contract-only stub today. We have not signed the Mitchell partner agreement, so calls to
getEstimate()throw"Not implemented — requires Mitchell API contract". This guide documents the shape so you can build against it ahead of time; production wiring lands once the contract is in place.
VerifyAI's damage pipeline produces a structured
damage_findings array per verification (see
Detecting and grading damage). For
fleet, rental, and insurance customers, the next question is usually
"what would this cost to repair?" — a number we can only get from a
real body-shop catalog.
Cluster 1 / Feature 1.3 introduces a Mitchell Cloud Estimating client
that takes the findings, sends them to Mitchell, and returns a priced
RepairEstimate. The client lives at
lib/verify-ai/damage/cost/mitchell-client.ts. Everything below is
the shape we're committing to; the actual HTTP calls and per-customer
caching land alongside the contract.
Integration architecture
┌────────────────────┐ damage_findings ┌──────────────────────┐
│ verify_ai_ │ ───────────────────▶ │ Mitchell client │
│ verifications row │ │ (server-only) │
└────────────────────┘ └──────────────────────┘
│
cached lookups │ HTTPS
▼
┌────────────────────────────┐ ┌──────────────────────────────┐
│ verify_ai_repair_catalog_ │ ◀────── │ Mitchell Cloud Estimating │
│ cache (per-line cache) │ │ REST API │
└────────────────────────────┘ └──────────────────────────────┘The client:
- Filters findings to
severity !== "none". - Looks up (vehicle × panel × damage_type) lines in the
per-customer cache table (
verify_ai_repair_catalog_cache— created in a follow-up migration once the contract is signed). - Falls back to a live Mitchell call for any uncached lines.
- Returns a
RepairEstimatekeyed bysource: 'mitchell'.
Calls are server-side only (the Mitchell API key never leaves the
server) and fail-soft: if Mitchell is down or the contract isn't
configured, the surrounding verification still succeeds and the
estimate is recorded as null.
The RepairEstimate shape
This is the contract callers can rely on — exported from
mitchell-client.ts:
export interface RepairEstimate {
source: "mitchell";
currency: string; // ISO 4217 — "USD" for v1
total_cents: number; // Sum of line_items[].amount_cents
line_items: RepairLineItem[];
raw: unknown; // Raw vendor payload, retained for audit
fetched_at: string; // ISO 8601 timestamp
}
export interface RepairLineItem {
catalog_id: string; // Mitchell parts SKU or labor operation
description: string;
amount_cents: number; // Cents in the estimate currency
type: "parts" | "labor" | "paint" | "sublet" | "fee";
quantity?: number;
unit_amount_cents?: number;
}And the request:
export interface MitchellEstimateRequest {
findings: DamageFinding[];
vehicle?: { vin?: string; year?: number; make?: string; model?: string; trim?: string };
tenant_id?: string; // For multi-tenant Mitchell accounts
postal_code?: string; // Used for regional labor rates
currency?: string;
}What the client looks like today
The function is exported but throws:
import { getEstimate } from "@/lib/verify-ai/damage/cost/mitchell-client";
// Will throw until Mitchell access lands.
await getEstimate({
findings: damage_findings,
vehicle: { vin: "1FAFP404X1F123456", year: 2022, make: "Ford", model: "F-150" },
postal_code: "94110",
});For downstream code that wants to compile against the module today (a
dashboard panel, the PDF renderer), the file exports a
makeFakeEstimate() helper that returns a stub estimate with
placeholder line items priced at $50 (light), $250 (medium), and
$1,500 (severe). Stub only — do not surface this number to
customers.
import { makeFakeEstimate } from "@/lib/verify-ai/damage/cost/mitchell-client";
const estimate = makeFakeEstimate(damage_findings);
// → { source: "mitchell", currency: "USD", total_cents: 30000, line_items: [...], raw: { stub: true }, fetched_at: "..." }Going live (post-contract)
When the Mitchell contract is signed, ops will:
- Drop the API credentials into env:
bash
MITCHELL_API_KEY=... MITCHELL_API_BASE_URL=https://api.mitchell.com/cloudest/v1 MITCHELL_PARTNER_ID=switchlabs - Run the follow-up migration that creates
verify_ai_repair_catalog_cache. - Replace the
throwbodies ingetEstimate()andvalidateEstimate()with real HTTPS calls (tracked as TODOs in the file). - Flip a per-customer feature flag
(
feature_flags.repair_estimate_mitchell) on each customer who has purchased the add-on. Customers without the flag will continue to seenullestimates.
A working call returns a priced estimate alongside the verification:
curl https://verify.switchlabs.dev/api/v1/verify \
-H "X-API-Key: vai_your_api_key" \
-F "image=@inspection.jpg" \
-F "policy=pol_fleet_damage" \
-F 'metadata={"vin":"1FAFP404X1F123456","postal_code":"94110"}'{
"id": "ver_8x92m4k9",
"is_compliant": false,
"overall_severity": "medium",
"k_grade": "K3",
"repair_estimate": {
"source": "mitchell",
"currency": "USD",
"total_cents": 87500,
"line_items": [
{ "catalog_id": "LAB-DENT-MED", "description": "Repair medium dent (front-left door)", "amount_cents": 35000, "type": "labor" },
{ "catalog_id": "PNT-DFL-BLEND", "description": "Blend paint adjacent panels", "amount_cents": 22500, "type": "paint" },
{ "catalog_id": "LAB-SCRATCH-BF", "description": "Light scratch repair (front bumper)", "amount_cents": 30000, "type": "labor" }
],
"fetched_at": "2026-08-12T14:30:00Z"
}
}The
repair_estimatefield on the API response is also gated on the per-customer flag — until both the contract and the flag are in place it will be absent from the response.
GT Motive (Europe) — future v2
Mitchell covers North America. For European customers, a parallel
GT Motive client is on the v2 roadmap (same RepairEstimate shape,
different source discriminator). The current mitchell-client.ts
file is structured so the source: "mitchell" literal can be widened
to a union without breaking existing consumers — code that branches
on source today is safe to keep.
Honest limitations
- Mitchell access is not yet contracted. Until then,
getEstimate()throws. UsemakeFakeEstimate()only for local testing. - No partial pricing. If a finding's panel/damage_type isn't in Mitchell's catalog, the line is dropped (with a Sentry tag) rather than guessed. Downstream callers should not assume the line count matches the finding count.
- No labor-rate negotiation. The estimate uses Mitchell's posted
rates for the supplied
postal_code. Customer-specific labor-rate agreements with DRP shops are out of scope for v1. - Currency is USD only in v1. Multi-currency lands with GT Motive.
What's next
- Detecting and grading damage — the
source of the
damage_findingsthat feed the estimate. - AIAG codes and K1–K5 grading — rollups that frequently bracket the estimate range.
- Before/after rental inspection delta — estimate the cost of new damage only, not pre-existing damage.