The MarketMatch API provides prescriptive vehicle-dealer matching using proprietary data science models. This powerful tool enables commercial sellers, dealers, and finance companies to optimize vehicle placement and sourcing decisions by identifying the best market fit between specific vehicles and dealerships.
MarketMatch analyzes dealer inventory patterns, sales velocity, and year-make-model affinity across tens of thousands of dealerships nationwide. Rankings are updated daily to reflect current market conditions, sold metrics, and active inventory status.
The API delivers actionable rankings at two levels:
A VIN Rank of 1 indicates the strongest predicted match between vehicle and dealer, enabling data-driven decisions for vehicle sourcing, placement, and inventory optimization in wholesale and retail automotive markets. For high-volume workflows, MarketMatch also supports batch processing, allowing you to submit large files of vehicle data for asynchronous ranking—see the Batch API introduction for general batch concepts and best practices.
Inventory misfit represents one of the largest hidden costs in automotive retail. When vehicles are sold to or acquired by dealers that are not a natural market fit, the consequences are measurable:
MarketMatch addresses this problem by providing quantitative market fit scores and prescriptive rankings, enabling data-driven placement and acquisition decisions.
MarketMatch scores vehicles and dealers based on comprehensive inventory analytics derived from extensive retail market data. The API returns three key metrics:
| Metric | Range | Description |
|---|---|---|
vin_score | 400-3000 | Indicates the vehicle's market position based on pricing, mileage, and market conditions. Higher scores suggest stronger market positioning with newer, lower-mileage vehicles at higher price points. |
dealer_ymm_score | 400-3000 | Measures the dealer's historical affinity for the vehicle's year, make, and model segment. Higher scores indicate the dealer typically handles vehicles in that category with stronger market positioning. |
vin_rank | 1 to N | Prescriptive ranking where 1 indicates the best predicted match. Rankings consider inventory alignment, turnover rates, and current stock levels. |
MarketMatch provides two complementary POST endpoints for different matching scenarios:
| Endpoint | Method | Description |
|---|---|---|
/v2/marketmatch/dealers/rank | POST | Rank dealers for a specific vehicle |
/v2/marketmatch/vins/rank | POST | Rank vehicles for a specific dealer |
api_key).POST https://api.marketcheck.com/v2/marketmatch/dealers/rank
Use this endpoint when you have a specific vehicle and want to identify the best dealer matches. This is ideal for wholesale vehicle placement scenarios where you need to find optimal buyers for inventory.
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/marketmatch/dealers/rank',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json', 'Content-Type': 'application/json'},
data: {
vin: {vin: 'WP1BE2AY5PDA28064', price: 20000, miles: 3000},
dealers: [
{source: 'mbofaugusta.com', zip: '30907'},
{source: 'acceleride.mbofaugusta.com', zip: '30907'},
{source: 'acceleride.esterobaychevrolet.com', zip: '33928'}
]
}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
POST https://api.marketcheck.com/v2/marketmatch/vins/rank
Use this endpoint when you have a specific dealer and want to identify which vehicles are the best fit for their inventory. This is ideal for vehicle sourcing and acquisition scenarios.
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/marketmatch/vins/rank',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json', 'Content-Type': 'application/json'},
data: {
dealer: {source: 'mbofaugusta.com', zip: '30907'},
vins: [
{vin: '5FNYF8H54SB000197', price: 37610, miles: 14428},
{vin: 'JTJBM7FX1K5236458', price: 35087, miles: 73788},
{vin: 'YV4L12RL4R1757085', price: 33085, miles: 45490}
]
}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
Both MarketMatch endpoints accept JSON request bodies with vehicle and dealer specifications. The request structure differs based on whether you are ranking dealers or vehicles.
Include your API key as a query parameter:
?api_key=YOUR_API_KEY
When ranking dealers for a vehicle, provide a single VIN with a list of candidate dealers.
interface DealersRankRequest {
vin: VINSpec; // Vehicle to match
dealers: DealerSpec[]; // Candidate dealers (1-100 items)
}
interface VINSpec {
vin: string; // 17-character VIN (required)
price: number; // Vehicle price in USD (required)
miles: number; // Vehicle mileage (required)
}
interface DealerSpec {
source: string; // Dealer website domain (required)
zip?: string; // 5-digit ZIP code (required for multi-location dealers)
mc_location_id?: string; // MarketCheck location ID (alternative to zip)
}
When ranking vehicles for a dealer, provide a single dealer with a list of candidate VINs.
interface VINsRankRequest {
dealer: DealerSpec; // Dealer to match
vins: VINSpec[]; // Candidate vehicles (1-20 items)
}
The DealerSpec and VINSpec interfaces are the same as defined above for the dealers rank endpoint.
| Field | Constraint | Notes |
|---|---|---|
vin | Exactly 17 characters | Standard VIN format |
price | Positive integer | Vehicle price in USD |
miles | Non-negative integer | Vehicle mileage |
zip | Exactly 5 characters | US ZIP code; required for multi-location dealers |
dealers array | 1-100 items | For /dealers/rank endpoint |
vins array | 1-20 items | For /vins/rank endpoint |
zip or mc_location_id to specify which location to use for matching. If omitted for a multi-location dealer, the request will return an error.Both endpoints return the same response structure with ranked matches and any items that failed to process.
interface MarketMatchResponse {
ranked_matches: RankedMatch[]; // Successfully ranked items, sorted by ranking
failed_items: FailedMatch[]; // Items that could not be processed
}
interface RankedMatch {
vin: string; // Vehicle Identification Number
price: number; // Vehicle price
miles: number; // Vehicle mileage
source: string; // Dealer's domain/website
zip: string; // Dealer's ZIP code
mc_location_id: string; // MarketCheck location ID
vin_score: number; // Score based on VIN matching (higher is better)
dealer_ymm_score: number; // Dealer's Year/Make/Model expertise score
vin_rank: number; // Overall ranking position (1 = best match)
}
interface FailedMatch {
vin: string; // Vehicle Identification Number
price: number; // Vehicle price
miles: number; // Vehicle mileage
source: string; // Dealer's domain/website
zip: string; // Dealer's ZIP code
mc_location_id: string; // MarketCheck location ID
vin_score: null; // Null when matching failed
dealer_ymm_score: null; // Null when matching failed
vin_rank: null; // Null when matching failed
}
| Metric | Description |
|---|---|
vin_score | Score indicating the vehicle's market position. Higher scores (toward 3000) represent vehicles with stronger market positioning—newer, lower-mileage vehicles at higher price points. Lower scores (toward 400) indicate older, higher-mileage vehicles at lower price points. |
dealer_ymm_score | Score reflecting the dealer's typical inventory profile for the vehicle's year, make, and model. Higher scores indicate the dealer historically handles similar vehicles with stronger market positioning. |
vin_rank | Overall ranking position where 1 represents the best match. Lower numbers indicate better alignment between vehicle characteristics and dealer inventory patterns. |
200 OK - Returns JSON object with ranked_matches and failed_items arraysranked_matches are sorted by vin_rank in ascending order (best match first)| Status Code | Description | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid JSON, missing required fields, malformed request |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Access denied to MarketMatch API |
| 422 | Unprocessable Entity | VIN decoding failed for all VINs, dealer not found, multi-location dealer without ZIP |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Temporary server issues |
| 502 | Bad Gateway | Issues with upstream services |
| 503 | Service Unavailable | API maintenance or downtime |
Error Response Format
Error responses include detailed information to help diagnose the issue:
{
"code": <HTTP status code>,
"message": "<Error message describing the issue>"
}
| Field | When Included |
|---|---|
failed_vins | VIN decoding errors—lists VINs that could not be decoded |
missing_dealers | Dealer not found errors—lists dealers not in the system |
multi_location_sources | Multi-location errors—lists dealers requiring ZIP specification |
Commercial sellers, auction platforms, and wholesale marketplaces can optimize vehicle sales by identifying which dealers are the best fit for specific wholesale inventory. MarketMatch ranks potential dealer buyers based on their historical affinity for similar vehicles and current inventory levels.
Use the /dealers/rank endpoint with your vehicle details and a list of candidate dealer buyers.
Example:
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/marketmatch/dealers/rank',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json', 'Content-Type': 'application/json'},
data: {
vin: {vin: 'WP1BE2AY5PDA28064', price: 20000, miles: 3000},
dealers: [
{source: 'mbofaugusta.com', zip: '30907'},
{source: 'acceleride.mbofaugusta.com', zip: '30907'},
{source: 'acceleride.esterobaychevrolet.com', zip: '33928'}
]
}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
Dealers can evaluate potential vehicle acquisitions from wholesale channels, trade-ins, auctions, or off-the-street purchases. MarketMatch ranks candidate vehicles based on how well they fit the dealer's inventory profile and market position.
Use the /vins/rank endpoint with your dealer details and a list of candidate vehicles.
Example:
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/marketmatch/vins/rank',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json', 'Content-Type': 'application/json'},
data: {
dealer: {source: 'mbofaugusta.com', zip: '30907'},
vins: [
{vin: '5FNYF8H54SB000197', price: 37610, miles: 14428},
{vin: 'JTJBM7FX1K5236458', price: 35087, miles: 73788},
{vin: 'YV4L12RL4R1757085', price: 33085, miles: 45490}
]
}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
Dealer groups can optimize inventory distribution across their network by identifying which rooftops are the best fit for specific vehicles. This enables data-driven inventory transfers that reduce days on market and improve overall network performance.
Use the /dealers/rank endpoint with multiple dealer locations from your network to identify underperforming inventory that should be relocated to stronger matches.
Example:
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/marketmatch/dealers/rank',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json', 'Content-Type': 'application/json'},
data: {
vin: {vin: 'WP1BE2AY5PDA28064', price: 20000, miles: 3000},
dealers: [
{source: 'mbofaugusta.com', zip: '30907'},
{source: 'acceleride.mbofaugusta.com', zip: '30907'},
{source: 'acceleride.esterobaychevrolet.com', zip: '33928'}
]
}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
Finance companies providing wholesale floor plan financing can assess dealer-vehicle fit as part of their risk evaluation. Vehicles with poor dealer fit may indicate higher risk of extended financing periods and potential margin compression.
Use the /vins/rank endpoint to evaluate a dealer's planned acquisitions and identify potential risk factors before extending credit.
Example:
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/marketmatch/vins/rank',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json', 'Content-Type': 'application/json'},
data: {
dealer: {source: 'carvana.com', zip: '95765'},
vins: [
{vin: 'JTMDFREV7JJ214231', price: 25990, miles: 34307},
{vin: '7YAKN4DA1SY001014', price: 38990, miles: 38990},
{vin: 'KL79MPSL8SB184667', price: 20355, miles: 15313},
{vin: '1FMCU0GD0JUD56190', price: 13790, miles: 73592},
{vin: '4T1C11BKXNU065125', price: 22590, miles: 50947}
]
}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
In addition to the single-vehicle ranking endpoint, you can submit large batches of vehicle data for ranking in a single request. Upload a gzip-compressed CSV file, track processing progress, and download the ranked results as a CSV file.
| API | Endpoint | Description |
|---|---|---|
| Submit Job | POST /v2/batch/marketmatch/rank/jobs | Submit a CSV file of vehicle data for batch ranking |
| Get Job Status | GET /v2/batch/marketmatch/rank/jobs/{job_id} | Check the status and progress of a specific job |
| List Jobs | GET /v2/batch/marketmatch/rank/jobs | List your batch ranking jobs with filtering and pagination |
| Download Results | POST /v2/batch/marketmatch/rank/jobs/{job_id}/download | Download the ranked results for a completed job |
Create a new batch MarketMatch ranking job.
POST https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs
// Loading...
| Parameter | Type | Default | Description | Required |
|---|---|---|---|---|
api_key | string | - | Your API key, passed as a URL query parameter | Y |
file | file | - | Gzip-compressed CSV file containing vehicle data (.csv.gz). Must include vin, price, miles, source, and zip columns. Maximum file size: 100 MB. Plain CSV files are rejected. | Y |
client_reference | string | - | Your internal tracking identifier. Max 255 characters. | N |
webhook_url | string | - | HTTPS URL to receive completion/failure notifications. Max 2,048 characters. | N |
webhook_secret | string | - | Secret used to sign webhook payloads for verification. Max 512 characters. | N |
Idempotency-Key | string (header) | - | Unique key for safe retries. Max 255 characters. | N |
Send the request as multipart/form-data.
Status: 202 Accepted
{
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "PROCESSING",
"client_reference": "ranking-batch-2026-04",
"created_at": "2026-04-01T10:00:00Z"
}
item_count is not included in the initial response. It appears once processing begins and is visible in subsequent status checks.| Field | Type | Description |
|---|---|---|
job_id | string | Unique job identifier (UUID). Use this for all subsequent requests. |
status | string | Always "PROCESSING" for newly created jobs. |
item_count | integer | Total number of records in the file. Absent immediately after submission; populated once processing begins. |
client_reference | string | Your tracking identifier, if provided. Omitted if not provided. |
created_at | string | Job creation timestamp (ISO 8601). |
| Status | Code | Description |
|---|---|---|
| 400 | missing_file | The file field is missing from the multipart form |
| 400 | invalid_file_format | The file is not a valid gzip-compressed CSV |
| 400 | invalid_webhook_url | Webhook URL is not HTTPS or targets a non-routable address |
| 400 | invalid_parameter | A form field exceeds its maximum length |
| 409 | active_job_exists | You already have an active ranking job |
| 413 | file_too_large | The uploaded file exceeds the maximum allowed size |
For retry and error handling guidance, see Best Practices in the Batch API introduction.
Retrieve the current status and details of a specific job.
GET https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs/{job_id}
import axios from 'axios';
const options = {
method: 'GET',
url: 'https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs/770a9622-a4bd-63f6-c938-668877662222',
params: {api_key: 'YOUR_API_KEY'},
headers: {Accept: 'application/json'}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
| Parameter | Type | Default | Description | Required |
|---|---|---|---|---|
api_key | string | - | Your API key, passed as a URL query parameter | Y |
job_id | string (path) | - | The job identifier returned by the Submit endpoint | Y |
Status: 200 OK
During processing:
{
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "PROCESSING",
"progress_percent": 55,
"item_count": 1200,
"client_reference": "ranking-batch-2026-04",
"created_at": "2026-04-01T10:00:00Z"
}
When completed:
{
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "COMPLETED",
"progress_percent": 100,
"item_count": 1200,
"successful_count": 1150,
"failed_count": 0,
"client_reference": "ranking-batch-2026-04",
"created_at": "2026-04-01T10:00:00Z",
"completed_at": "2026-04-01T10:12:00Z",
"download_available": true,
"download_count": 0,
"max_downloads": 3,
"remaining_downloads": 3
}
When failed:
{
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "FAILED",
"progress_percent": 55,
"item_count": 1200,
"client_reference": "ranking-batch-2026-04",
"created_at": "2026-04-01T10:00:00Z",
"error_code": "processing_failed",
"error_message": "Job processing failed unexpectedly. Please resubmit."
}
| Field | Type | Present | Description |
|---|---|---|---|
job_id | string | Always | Unique job identifier (UUID) |
status | string | Always | PROCESSING, COMPLETED, or FAILED |
progress_percent | integer | Always | Processing progress from 0 to 100 |
item_count | integer | When available | Total records in the submitted file. May be absent briefly after submission. |
successful_count | integer | COMPLETED only | Number of unique VINs successfully ranked |
failed_count | integer | COMPLETED only | Number of records that failed processing |
client_reference | string | If provided | Your tracking identifier |
created_at | string | Always | Job creation timestamp (ISO 8601) |
completed_at | string | COMPLETED only | Job completion timestamp (ISO 8601) |
download_available | boolean | COMPLETED only | Whether the result file can be downloaded |
download_count | integer | COMPLETED only | Number of times you have downloaded this job's results |
max_downloads | integer | COMPLETED only | Maximum downloads allowed (3) |
remaining_downloads | integer | COMPLETED only | Downloads remaining |
error_code | string | FAILED only | Machine-readable error code |
error_message | string | FAILED only | Human-readable error description |
item_count reflects the total records in the submitted file. successful_count reflects the number of unique VINs in the results. If your input contains duplicate VINs, successful_count will be less than item_count — this is expected behavior, not an error.| Status | Code | Description |
|---|---|---|
| 404 | job_not_found | Job does not exist or does not belong to your account |
For retry and error handling guidance, see Best Practices in the Batch API introduction.
Retrieve a paginated list of your batch ranking jobs.
GET https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs
import axios from 'axios';
const options = {
method: 'GET',
url: 'https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs',
params: {api_key: 'YOUR_API_KEY', status: 'completed', rows: '10'},
headers: {Accept: 'application/json'}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
| Parameter | Type | Default | Description | Required |
|---|---|---|---|---|
api_key | string | - | Your API key, passed as a URL query parameter | Y |
status | string | all | Filter jobs by status: all, active, completed, failed | N |
rows | integer | 20 | Number of jobs per page (1–100) | N |
cursor | string | - | Opaque cursor from a previous response's next_cursor | N |
Filter definitions:
| Filter Value | Returns |
|---|---|
all | All jobs |
active | Jobs still processing (status = PROCESSING) |
completed | Successfully completed jobs (status = COMPLETED) |
failed | Failed jobs (status = FAILED) |
Status: 200 OK
{
"jobs": [
{
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "COMPLETED",
"progress_percent": 100,
"item_count": 1200,
"successful_count": 1150,
"failed_count": 0,
"client_reference": "ranking-batch-2026-04",
"created_at": "2026-04-01T10:00:00Z",
"completed_at": "2026-04-01T10:12:00Z",
"download_available": true,
"download_count": 1,
"max_downloads": 3,
"remaining_downloads": 2
},
{
"job_id": "881ba733-b5ce-74a7-d049-779988773333",
"status": "PROCESSING",
"progress_percent": 30,
"item_count": 800,
"client_reference": "ranking-batch-2026-03",
"created_at": "2026-03-28T15:00:00Z"
}
],
"total": 15,
"next_cursor": "eyJjIjoiMjAyNi0wMy0yOFQxNTowMDowMFoiLCJqIjoiODgxYmE3MzMtaDVjZS03NGc3LWQwNDktNzc5OTg4NzczMzMzIn0",
"rows": 20
}
| Field | Type | Description |
|---|---|---|
jobs | array | Array of job status objects (same fields as Get Job Status) |
total | integer | Total number of jobs matching the filter |
next_cursor | string | Opaque cursor for the next page. Absent when no more pages exist. |
rows | integer | Page size used for this response |
| Status | Code | Description |
|---|---|---|
| 400 | invalid_parameter | Invalid status filter value or malformed cursor |
For retry and error handling guidance, see Best Practices in the Batch API introduction.
Download the ranked results for a completed job. Each call streams the result file directly.
POST https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs/{job_id}/download
import axios from 'axios';
const options = {
method: 'POST',
url: 'https://api.marketcheck.com/v2/batch/marketmatch/rank/jobs/770a9622-a4bd-63f6-c938-668877662222/download',
params: {api_key: 'YOUR_API_KEY'}
};
try {
const { data } = await axios.request(options);
console.log(data);
} catch (error) {
console.error(error);
}
| Parameter | Type | Default | Description | Required |
|---|---|---|---|---|
api_key | string | - | Your API key, passed as a URL query parameter | Y |
job_id | string (path) | - | The job identifier | Y |
Status: 200 OK
The response body is the raw binary file (gzip-compressed CSV). Metadata is returned in response headers:
| Header | Type | Description |
|---|---|---|
Content-Type | string | application/gzip |
Content-Disposition | string | attachment; filename="output.csv.gz" |
X-Download-Count | integer | Number of downloads consumed, including this one |
X-Max-Downloads | integer | Maximum downloads allowed (3) |
X-Remaining-Downloads | integer | Downloads remaining after this one |
X-File-Checksum | string | SHA-256 checksum of the file for integrity verification |
| Status | Code | Description |
|---|---|---|
| 403 | download_limit_exceeded | All 3 download slots have been consumed |
| 404 | job_not_found | Job does not exist or does not belong to your account |
| 409 | job_not_completed | Job has not finished processing yet |
For retry and error handling guidance, see Best Practices in the Batch API introduction.
The input file must be a gzip-compressed CSV (.csv.gz) with:
vin, price, miles, source, zipYour CSV must include the following columns: vin, price, miles, source (dealer website domain), and zip (dealer ZIP code). Each row represents a vehicle-dealer pair to be ranked. Files with missing or invalid columns will cause the job to fail during processing.
Create the CSV first:
vin,price,miles,source,zip
vin_1,185000,31450,dealer_source_1.com,43056
vin_2,52000,12000,dealer_source_2.com,10001
vin_3,35000,32000,dealer_source_3.com,60601
Then gzip-compress it before uploading:
gzip vehicles.csv
Results are delivered as a gzip-compressed CSV file (.csv.gz). The output CSV contains the same columns as the input, with additional ranking columns added by the MarketMatch engine.
Decompress the file to view the results:
gunzip results.csv.gz
When you register a webhook_url at submission time, you receive notifications when the job completes or fails.
job.completed:
{
"event": "job.completed",
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "COMPLETED",
"item_count": 1200,
"successful_count": 1150,
"failed_count": 0,
"completed_at": "2026-04-01T10:12:00Z"
}
| Field | Type | Description |
|---|---|---|
event | string | "job.completed" |
job_id | string | Job identifier (UUID) |
status | string | "COMPLETED" |
item_count | integer | Total records in the submitted file |
successful_count | integer | Number of unique VINs successfully ranked |
failed_count | integer | Number of records that failed processing |
completed_at | string | Completion timestamp (ISO 8601) |
job.failed:
{
"event": "job.failed",
"job_id": "770a9622-a4bd-63f6-c938-668877662222",
"status": "FAILED",
"error_code": "processing_failed",
"error_message": "Job processing failed unexpectedly. Please resubmit.",
"completed_at": "2026-04-01T10:12:00Z"
}
| Field | Type | Description |
|---|---|---|
event | string | "job.failed" |
job_id | string | Job identifier (UUID) |
status | string | "FAILED" |
error_code | string | Machine-readable error code |
error_message | string | Human-readable error description |
completed_at | string | Failure timestamp (ISO 8601) |