Dealer specials
id is stable for the life of an offer on its site (same platform offer id, same creative, or
same make/model/year/type/amount): a changed payment keeps the id and is recorded in the
history; an offer that disappears and returns keeps its id. source=published (default) is what
v2 publishes; source=shadow is what it would publish for sites still in shadow. For
incremental sync pass since (rows changed or removed since then) with status=all.
GEThttps://api.motorsdynamic.com/api/v2/specials
Plan and price
| Plan | Access | Price |
|---|---|---|
| Free | No | not included |
| Growth | No | not included |
| Scale | No | not included |
| Enterprise | Yes | contract |
A call is billed when it answers 2xx. See pricing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
domain_id | query | integer | no | One site; without it, every entitled site. |
status | query | active or removed or all | no | |
source | query | published or shadow | no | |
since | query | string (date-time) | no | |
make | query | string | no | |
limit | query | integer | no | |
cursor | query | string | no | page.next_cursor from the previous page, unchanged. |
Example request
curl "https://api.motorsdynamic.com/api/v2/specials?domain_id=201&status=all&limit=1" \
-H "x-api-key: $MD_API_KEY"const res = await fetch("https://api.motorsdynamic.com/api/v2/specials?domain_id=201&status=all&limit=1", {
headers: { "x-api-key": process.env.MD_API_KEY },
});
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).error}`);
const data = await res.json();
console.log(data);import os, requests
res = requests.get(
"https://api.motorsdynamic.com/api/v2/specials?domain_id=201&status=all&limit=1",
headers={"x-api-key": os.environ["MD_API_KEY"]},
timeout=10,
)
res.raise_for_status()
print(res.json())Example response
{
"data": [
{
"id": 9001,
"domain_id": 201,
"source": "published",
"status": "active",
"offer_key": "id:tel-1",
"make": "Kia",
"model": "Telluride",
"model_year": 2026,
"trim": null,
"vin": null,
"stock_number": null,
"offer_type": "Lease",
"offer_amount": "$499/mo",
"monthly_payment": 499,
"term": 36,
"due_at_signing": 3999,
"msrp": 38000,
"selling_price": null,
"offer_details": null,
"disclaimer": "Lease for well-qualified lessees.",
"raw_ocr_text": null,
"expires_at": "2026-09-30",
"offers": null,
"image_url": null,
"image_hash": null,
"source_type": "html",
"source_url": "https://www.kiaofexample.com/specials",
"source_page_type": "specials",
"strategy": "tv_api",
"first_seen_at": "2026-09-20T10:00:00.000000Z",
"last_seen_at": "2026-09-24T10:00:00.000000Z",
"removed_at": null,
"freshness": {
"last_seen_at": "2026-09-24T10:00:00.000000Z",
"last_complete_run_at": "2026-09-24T10:00:00.000000Z",
"stale": false
},
"provenance": {
"source": "v2_specials",
"mode": "publish",
"strategy": "tv_api",
"run_id": 77,
"first_run_id": 70
}
}
],
"page": {
"limit": 1,
"has_more": false,
"next_cursor": null
},
"meta": {
"source": "published",
"target": {
"mode": "publish",
"last_complete_at": "2026-09-24T10:00:00.000000Z",
"next_crawl_at": "2026-09-24T10:00:00.000000Z",
"last_outcome": "RECIPE_HIT",
"alert": false,
"alert_reason": null,
"consecutive_failures": 0
}
}
}
Response fields
| Field | Type | Description |
|---|---|---|
data[] | array of object | |
data[].id | integer | Stable special id. |
data[].domain_id | integer | |
data[].source | published or shadow | |
data[].status | active or removed | |
data[].offer_key | string | |
data[].make | string or null | |
data[].model | string or null | |
data[].model_year | integer or null | |
data[].trim | string or null | |
data[].vin | string or null | |
data[].stock_number | string or null | |
data[].offer_type | string or null | |
data[].offer_amount | string or null | |
data[].monthly_payment | number or null | |
data[].term | integer or null | |
data[].due_at_signing | number or null | |
data[].msrp | number or null | |
data[].selling_price | number or null | |
data[].offer_details | string or null | |
data[].disclaimer | string or null | |
data[].raw_ocr_text | string or null | |
data[].expires_at | string or null | YYYY-MM-DD |
data[].offers | array or null | |
data[].image_url | string or null | |
data[].image_hash | string or null | |
data[].source_type | string or null | |
data[].source_url | string or null | |
data[].source_page_type | string or null | |
data[].strategy | string or null | |
data[].first_seen_at | string or null | RFC 3339 UTC with microseconds. |
data[].last_seen_at | string or null | RFC 3339 UTC with microseconds. |
data[].removed_at | string or null | RFC 3339 UTC with microseconds. |
data[].freshness | object | |
data[].freshness.last_seen_at | string or null | RFC 3339 UTC with microseconds. |
data[].freshness.last_complete_run_at | string or null | RFC 3339 UTC with microseconds. |
data[].freshness.stale | boolean | |
data[].provenance | object | |
data[].provenance.source | v2_specials | |
data[].provenance.mode | shadow or publish | |
data[].provenance.strategy | string or null | How the offer was found: tv_api, scene7_params, structured_slides, banner_ocr, offer_text (a card read as HTML text), ai_located, image_discovered. |
data[].provenance.extractor | string or null | The extractor that read the offer: offer-text:overfuel, offer-text:dealercom-promos, banner-ocr, scene7-params, structured-slides, dealervenom-card, tv-offers, reading: |
data[].provenance.run_id | integer or null | |
data[].provenance.first_run_id | integer or null | |
page | object | |
page.limit | integer | |
page.has_more | boolean | |
page.next_cursor | string or null | |
meta | object |
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_parameter | A parameter is missing or malformed. The message names it. |
| 401 | invalid_api_key | The key is missing, revoked or mistyped. |
| 402 | plan_required | The endpoint is on a higher plan. The body names the plan and links to the upgrade. |
| 403 | not_entitled | The key may not read this dealer or source. |
| 429 | rate_limited or quota_exceeded | Slow down (see Retry-After), or the Free monthly calls are used up. |
| 504 | query_timeout | The query passed its 4 second budget. Narrow the filters. Not billed. |
Every error has the same body. Full list: errors.