# 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`.

## Endpoint

```
GET https://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](https://developers.motorsdynamic.com/pricing.md).

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

```bash
curl "https://api.motorsdynamic.com/api/v2/specials?domain_id=201&status=all&limit=1" \
  -H "x-api-key: $MD_API_KEY"
```

JavaScript:

```javascript
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);
```

Python:

```python
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

```json
{
  "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:<source> (a stored reading of the creative). Additive (2026-09-29). |
| `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: `{ "error": "<code>", "message": "..." }`. Full list: [errors](https://developers.motorsdynamic.com/docs/errors.md).
