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

plaintext
┌────────────────────┐   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:

  1. Filters findings to severity !== "none".
  2. 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).
  3. Falls back to a live Mitchell call for any uncached lines.
  4. Returns a RepairEstimate keyed by source: '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:

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:

ts
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:

ts
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.

ts
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:

  1. Drop the API credentials into env:
    bash
    MITCHELL_API_KEY=...
    MITCHELL_API_BASE_URL=https://api.mitchell.com/cloudest/v1
    MITCHELL_PARTNER_ID=switchlabs
  2. Run the follow-up migration that creates verify_ai_repair_catalog_cache.
  3. Replace the throw bodies in getEstimate() and validateEstimate() with real HTTPS calls (tracked as TODOs in the file).
  4. 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 see null estimates.

A working call returns a priced estimate alongside the verification:

bash
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"}'
json
{
  "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_estimate field 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. Use makeFakeEstimate() 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

Get in Touch

Questions about pricing, integrations, or custom deployments? We'd love to hear from you.