Inventory API
The inventory API runs every record through the 6-stage pipeline: validate → normalize → deduplicate → enrich → index. Accepted records land in Elasticsearch and appear in search within seconds; rejected records fail fast with a typed error code so your integration can surface actionable messages to dealer staff.
POST /api/v1/inventory
/api/v1/inventoryIngest one record or a batch of up to 500.
Single record
curl -X POST https://truckradar.ai/api/v1/inventory \
-H "Authorization: Bearer tr_live_XXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"source_provider_id": "dealer-123-feed",
"vin": "3ALHCYFE3LDLM8435",
"year": 2020,
"make": "Freightliner",
"model": "M2 106",
"category": "Reefer Truck",
"listing_type": "truck",
"new_or_used": "used",
"price": 65000,
"odometer_miles": 144882,
"engine_make": "Cummins",
"engine_model": "L9",
"horsepower": 300,
"fuel_type": "diesel",
"transmission_type": "automatic",
"axle_config": "6x4",
"location_city": "Norton",
"location_state": "MA",
"description": "8,738 REEFER HOURS AS OF 10/13/2025; MULTI-TEMP",
"photos": [
"https://cdn.example.com/img/282354/1.jpg",
"https://cdn.example.com/img/282354/2.jpg"
]
}'Success response:
{
"data": {
"success": true,
"stages": { "validate": "ok", "normalize": "ok", "deduplicate": "ok", "enrich": "ok", "index": "ok" },
"durationMs": 482
}
}Batch (up to 500 records)
curl -X POST https://truckradar.ai/api/v1/inventory \
-H "Authorization: Bearer tr_live_XXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"source_provider_id": "dealer-123-feed",
"records": [
{ "vin": "3ALHCYFE3LDLM8435", "year": 2020, "make": "Freightliner", "model": "M2 106", "price": 65000 },
{ "vin": "3HAMMMML2KL595663", "year": 2019, "make": "International", "model": "4300", "price": 43000 }
]
}'Success response (partial success is allowed — failed records do not block the batch):
{
"data": { "total": 2, "succeeded": 2, "failed": 0 }
}Batch cap
400 BATCH_TOO_LARGE. Split on the client side.Fetch example (TypeScript)
type IngestResult = {
data?: { success: boolean; stages: Record<string, string>; durationMs: number };
error?: { code: string; message: string; details?: unknown };
};
async function pushUnit(record: Record<string, unknown>): Promise<IngestResult> {
const res = await fetch("https://truckradar.ai/api/v1/inventory", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TRUCKRADAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ source_provider_id: "dealer-123-feed", ...record }),
});
return res.json();
}Accepted fields
Any field not listed below is preserved on the raw payload but not indexed. The pipeline resolves aliases, converts units (km → miles, kg → lbs, CAD → USD when the currency field is set), and enriches with geocoding, embeddings, and quality scoring.
| Field | Type | Req | Notes |
|---|---|---|---|
| vin | string (17) | yes | 17-character VIN. Checksum validated (warn only). Duplicates across your feed are merged by VIN + source_provider_id. |
| year | integer | yes | Model year. Must be between 1990 and current year + 2. |
| make | string | yes | Resolved via MakeAlias. Accepts FRTLNR, KW, Pete, INTL, etc. |
| model | string | no | Resolved via ModelAlias (scoped to make). Defaults to "Unknown" if missing. |
| trim | string | no | Free text. |
| category | string | no | Resolved via CategoryAlias (e.g. Reefer → Reefer Truck). |
| listing_type | enum | no | truck | trailer. Auto-detected from make/category if omitted. |
| new_or_used | enum | no | new | used | certified_preowned. |
| price | number | no | Asking price in USD. Values under $1,000 are rejected as junk and treated as “Call for Price”. |
| price_cents | integer | no | Alternative to price. If present and > 100,000, taken as-is. Otherwise multiplied by 100. |
| msrp | number | no | Manufacturer's suggested retail price in USD. |
| odometer_miles | integer | no | If the feed reports km, pass odometer_km instead and we convert (÷ 1.60934). |
| engine_hours | integer | no | Useful for vocational equipment. |
| engine_make | string | no | Cummins, Detroit, PACCAR, Mack, Volvo, etc. |
| engine_model | string | no | X15, DD15, MX-13, MP8, D13, etc. |
| horsepower | integer | no | Peak HP. |
| torque_ftlb | integer | no | Peak torque in ft·lb. |
| fuel_type | enum | no | diesel | gasoline | cng | lng | electric | hybrid | hydrogen. |
| transmission_type | enum | no | manual | automated | automatic. |
| transmission_model | string | no | e.g. Eaton Fuller RTLO-16918B. |
| transmission_speeds | integer | no | Number of forward gears. |
| axle_config | enum | no | 4x2 | 6x4 | 6x2 | 8x4 | 8x6. |
| rear_axle_ratio | decimal | no | e.g. 3.42. |
| suspension_rear | string | no | Free text: air ride, spring, walking-beam. |
| wheelbase_in | integer | no | Inches. |
| gvwr | integer | no | Pounds. Hard floor: 10,001 lbs. Values below are rejected (Class 1–2 filter). |
| gcwr | integer | no | Gross combination weight rating (pounds). |
| class | string | no | class_5…class_8. Explicit class_1/class_2 values are rejected. Required for dual-use makes (Ford, Chevy, GMC, Ram) unless GVWR ≥ 10,001. |
| sleeper_size_in | integer | no | Sleeper depth in inches (72/76/80). |
| sleeper_type | enum | no | mid_roof | raised_roof | flat_top | ultra_high. |
| cab_type | enum | no | conventional | cabover | crew. |
| body_type | string | no | Rejected if it resolves to a consumer body (sedan, suv, coupe, etc). |
| condition | enum | no | new | excellent | good | fair | salvage | parts_only. |
| location_city | string | no | Primary city. Geocoded downstream. |
| location_state | string | no | 2-letter postal code preferred. |
| location_zip | string | no | ZIP/postal code. |
| description | string | no | Free text. Cleaned during enrich. |
| features | object | no | JSON object: {apu:true, inverter:true, fridge:true, pto:true}. |
| photos | string[] | no | Array of publicly accessible image URLs. /pending/ placeholders are skipped. Re-hosted to Cloudflare R2 during enrich. |
| photo_urls | string[] | no | Alias for photos (XML convention). |
| source_id | string | no | The dealer-side record ID. Defaults to VIN if omitted. Used with source_provider_id to dedup. |
| source_provider_id | string | no | Optional at record level; usually set at the batch envelope level. Defaults to "manual". |
PATCH /api/v1/inventory
/api/v1/inventoryMark a VIN as sold or expired. Use this the moment a unit is sold — stale inventory is the #1 complaint from buyers. Updates both the database and the search index.
Request body
| Field | Type | Req | Notes |
|---|---|---|---|
| vin | string (17) | yes | VIN of the unit to update. |
| status | enum | no | sold (default) | expired. |
| source_provider_id | string | no | Scope the update to a specific feed when one VIN appears across multiple feeds. |
curl -X PATCH https://truckradar.ai/api/v1/inventory \
-H "Authorization: Bearer tr_live_XXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" \
-d '{
"vin": "3ALHCYFE3LDLM8435",
"status": "sold",
"source_provider_id": "dealer-123-feed"
}'{ "data": { "updated": 1 } }GET /api/v1/inventory/{vin}
/api/v1/inventory/{vin}Look up the current TruckRadar state for a VIN. Returns all active listings across all feeds (most recently synced first). Use this to verify that a recently-pushed record was accepted without re-ingesting.
curl https://truckradar.ai/api/v1/inventory/3ALHCYFE3LDLM8435 \
-H "Authorization: Bearer tr_live_XXXXXXXXXXXXXXXX"Success response:
{
"data": [
{
"id": "b8b3e2e0-0f5b-4cbf-9c72-2cabb66e4e85",
"slug": "2020-freightliner-m2-106-3alhcyfe3ldlm8435",
"vin": "3ALHCYFE3LDLM8435",
"status": "active",
"listing_type": "truck",
"year": 2020,
"price_cents": 6500000,
"source_id": "282354",
"source_provider_id": "dealer-123-feed",
"last_synced_at": "2026-04-14T19:12:44.221Z",
"make": { "name": "Freightliner", "slug": "freightliner" },
"model": { "name": "M2 106", "slug": "m2-106" }
}
]
}VIN format
400 INVALID_VIN; no match returns 404 NOT_FOUND.Pipeline stages
Every record passes through, in order:
- Validate — VIN checksum, year range, price sanity, GVWR floor, body-type blocklist, dual-use quarantine.
- Normalize — resolve Make/Model/Category aliases; convert units; map enums.
- Deduplicate —
SHA-256(VIN + source_provider_id)key; merge or insert. - Enrich — geocode ZIP, compute quality score, generate vision tags and vector embedding.
- Index — upsert into Elasticsearch with the embedding attached for semantic search.
Failed records quarantine — they do not block a batch. The response body tells you which stage failed per record.
See also
- Feed Formats — XML / CSV alternatives if you can't call the API directly.
- Error Codes — remediation guidance for every error the pipeline emits.
- OpenAPI 3.1 descriptor — for client generation.