# DealerData API

Verified contacts and store data for every US and Canadian car dealership: ~75,800 rooftops and their staff, with
verified work emails, mobiles and LinkedIn. Search and counts are free; unlocking a record spends credits.

- REST base URL: https://api.usedealerdata.com/functions/v1/api/v1
- MCP server (streamable HTTP, JSON): https://api.usedealerdata.com/functions/v1/mcp
- Auth: `Authorization: Bearer dd_live_…` (or `x-api-key: dd_live_…`). Create keys at https://usedealerdata.com/app/api
- HTML version: https://usedealerdata.com/docs/api · OpenAPI: https://usedealerdata.com/openapi.json

## How to pull a list

1. `GET /filters` to see valid roles, OEMs, states, bands and prices.
2. `GET /contacts/count` with your filters to size the list (free).
3. `GET /contacts` page by page, passing `cursor=<next_cursor>` until `next_cursor` is null.
4. `POST /unlock` with the ids you want and `"return_rows": true` to get the emails back in the same call.
5. `GET /export/contacts` any time for everything you have unlocked.

Lists take comma separated values (`role=gm,gsm`). Unknown filters and values are refused with a 400 that names the
valid options, so a typo never returns the wrong list. Responses may carry `warnings` (for example a plural filter
name read as the singular, or an OEM that matches no store).

## GET /contacts

Search contacts (free). Dealership staff matching the filters, best ranked first. Emails are masked and phones hidden until unlocked; the row id is what you pass to /unlock. Each page counts toward the daily search allowance.

| Filter | Type | Meaning |
|---|---|---|
| `q` | text | Free text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it. |
| `role` | list | Job role keys. Values: owner_dealer_principal, gm, gsm, sales_manager, used_car_manager, fi_manager, internet_sales, bdc_manager, marketing, fixed_ops_director, service_manager, parts_manager, fleet_manager, sales_staff, service_staff, accounting, hr, it, other. |
| `seniority` | list | Seniority. Values: owner, executive, director, manager, staff. |
| `title` | text | Job title contains this phrase (case insensitive), e.g. "internet director". |
| `title_any` | list | Comma or | separated titles. A contact matches when its title holds every word of any one entry, in any order, e.g. "internet director|digital manager". |
| `country` | list | Country codes. Values: US, CA. |
| `state` | list | State or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes. |
| `city` | list | Exact city names (case insensitive), e.g. Houston,Dallas. |
| `brand` | list | OEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters. |
| `type` | list | Store type. Values: franchise, independent, group, powersports_rv, specialty. |
| `group` | text | Dealer group name contains this, e.g. AutoNation. |
| `size` | list | Store headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+. |
| `revenue` | list | Store revenue band (USD). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+. |
| `mail_provider` | list | Who hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Values: google, microsoft, gateway, other. |
| `domain` | list | Store website domains, e.g. boydauto.com. https://, www. and paths are stripped. |
| `dealer_id` | list | Only contacts at these dealer ids. |
| `ids` | list | Look up these record ids directly (max 1,000). |
| `email_tier` | list | Email check result. Values: verified, unverified. |
| `has_email` | true/false | true: only contacts with a verified work email. |
| `has_phone` | true/false | true: only contacts with a mobile or direct line. |
| `has_linkedin` | true/false | true: only contacts with a LinkedIn profile. |
| `moved` | true/false | true: only people recently found in a new seat at another store. |
| `verified_within_days` | integer | Email verified within this many days. |
| `max_per_dealer` | integer | At most this many contacts per store (highest ranked first). |
| `page` | integer | Page number for offset paging. Default 1. |
| `page_size` | integer | Rows per page. Default 25, max 50 (500 on admin accounts). |
| `cursor` | text | next_cursor from the previous page. Faster and stable for long pulls; overrides page. |

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/contacts?role=gm,gsm&state=TX&has_email=true&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{
  "total": 1188,
  "page_size": 50,
  "next_cursor": "16:204414",
  "rows": [{
    "id": 204401, "first_name": "Jane", "last_name": "Example", "title": "General Manager", "role": "gm",
    "seniority": "executive", "dealer_id": 1840, "dealer_name": "Example Ford", "city": "Austin", "state": "TX",
    "country": "US", "group_name": null, "brands": ["Ford"], "email": "j•••@exampleford.com",
    "email_verified": true, "email_tier": "verified", "mail_provider": "google", "verified_at": "2026-10-21T09:12:00Z",
    "has_phone": true, "phone": null, "phone_type": "mobile", "has_linkedin": true, "linkedin_url": null,
    "moved_from": null, "unlocked": false, "phone_unlocked": false
  }]
}
```

## POST /contacts/search

Search contacts with a JSON body. Same as GET /contacts, with the filters in a JSON body. Lists can be arrays or comma separated strings. Use it for long id or domain lists.

| Filter | Type | Meaning |
|---|---|---|
| `q` | text | Free text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it. |
| `role` | list | Job role keys. Values: owner_dealer_principal, gm, gsm, sales_manager, used_car_manager, fi_manager, internet_sales, bdc_manager, marketing, fixed_ops_director, service_manager, parts_manager, fleet_manager, sales_staff, service_staff, accounting, hr, it, other. |
| `seniority` | list | Seniority. Values: owner, executive, director, manager, staff. |
| `title` | text | Job title contains this phrase (case insensitive), e.g. "internet director". |
| `title_any` | list | Comma or | separated titles. A contact matches when its title holds every word of any one entry, in any order, e.g. "internet director|digital manager". |
| `country` | list | Country codes. Values: US, CA. |
| `state` | list | State or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes. |
| `city` | list | Exact city names (case insensitive), e.g. Houston,Dallas. |
| `brand` | list | OEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters. |
| `type` | list | Store type. Values: franchise, independent, group, powersports_rv, specialty. |
| `group` | text | Dealer group name contains this, e.g. AutoNation. |
| `size` | list | Store headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+. |
| `revenue` | list | Store revenue band (USD). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+. |
| `mail_provider` | list | Who hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Values: google, microsoft, gateway, other. |
| `domain` | list | Store website domains, e.g. boydauto.com. https://, www. and paths are stripped. |
| `dealer_id` | list | Only contacts at these dealer ids. |
| `ids` | list | Look up these record ids directly (max 1,000). |
| `email_tier` | list | Email check result. Values: verified, unverified. |
| `has_email` | true/false | true: only contacts with a verified work email. |
| `has_phone` | true/false | true: only contacts with a mobile or direct line. |
| `has_linkedin` | true/false | true: only contacts with a LinkedIn profile. |
| `moved` | true/false | true: only people recently found in a new seat at another store. |
| `verified_within_days` | integer | Email verified within this many days. |
| `max_per_dealer` | integer | At most this many contacts per store (highest ranked first). |
| `page` | integer | Page number for offset paging. Default 1. |
| `page_size` | integer | Rows per page. Default 25, max 50 (500 on admin accounts). |
| `cursor` | text | next_cursor from the previous page. Faster and stable for long pulls; overrides page. |

```bash
curl -X POST "https://api.usedealerdata.com/functions/v1/api/v1/contacts/search" \
  -H "Authorization: Bearer $DEALERDATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role":["gm"],"brand":["Toyota","Lexus"],"max_per_dealer":1,"page_size":50}'
```

```json
{ "total": 912, "page_size": 50, "next_cursor": "15:88120", "rows": [ … ] }
```

## GET /contacts/count

Count contacts (free). Exact number of contacts matching the filters, to size a list before pulling it. Does not use the daily search allowance. Also accepts POST with a JSON body.

| Filter | Type | Meaning |
|---|---|---|
| `q` | text | Free text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it. |
| `role` | list | Job role keys. Values: owner_dealer_principal, gm, gsm, sales_manager, used_car_manager, fi_manager, internet_sales, bdc_manager, marketing, fixed_ops_director, service_manager, parts_manager, fleet_manager, sales_staff, service_staff, accounting, hr, it, other. |
| `seniority` | list | Seniority. Values: owner, executive, director, manager, staff. |
| `title` | text | Job title contains this phrase (case insensitive), e.g. "internet director". |
| `title_any` | list | Comma or | separated titles. A contact matches when its title holds every word of any one entry, in any order, e.g. "internet director|digital manager". |
| `country` | list | Country codes. Values: US, CA. |
| `state` | list | State or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes. |
| `city` | list | Exact city names (case insensitive), e.g. Houston,Dallas. |
| `brand` | list | OEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters. |
| `type` | list | Store type. Values: franchise, independent, group, powersports_rv, specialty. |
| `group` | text | Dealer group name contains this, e.g. AutoNation. |
| `size` | list | Store headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+. |
| `revenue` | list | Store revenue band (USD). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+. |
| `mail_provider` | list | Who hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Values: google, microsoft, gateway, other. |
| `domain` | list | Store website domains, e.g. boydauto.com. https://, www. and paths are stripped. |
| `dealer_id` | list | Only contacts at these dealer ids. |
| `ids` | list | Look up these record ids directly (max 1,000). |
| `email_tier` | list | Email check result. Values: verified, unverified. |
| `has_email` | true/false | true: only contacts with a verified work email. |
| `has_phone` | true/false | true: only contacts with a mobile or direct line. |
| `has_linkedin` | true/false | true: only contacts with a LinkedIn profile. |
| `moved` | true/false | true: only people recently found in a new seat at another store. |
| `verified_within_days` | integer | Email verified within this many days. |
| `max_per_dealer` | integer | At most this many contacts per store (highest ranked first). |

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/contacts/count?role=gm&brand=Ford&has_email=true" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "total": 2481 }
```

## GET /contacts/{id}

One contact. A single contact by id, showing whatever this account has unlocked. 404 if the id does not exist.

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/contacts/204401" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "id": 204401, "first_name": "Jane", … }
```

## GET /dealers

Search dealerships (free). US and Canadian rooftops with OEMs, type, dealer group, size and revenue bands. Domain, phone, street, postal code, LinkedIn, headcount and revenue show once the store is unlocked.

| Filter | Type | Meaning |
|---|---|---|
| `q` | text | Free text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it. |
| `country` | list | Country codes. Values: US, CA. |
| `state` | list | State or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes. |
| `city` | list | Exact city names (case insensitive), e.g. Houston,Dallas. |
| `brand` | list | OEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters. |
| `type` | list | Store type. Values: franchise, independent, group, powersports_rv, specialty. |
| `group` | text | Dealer group name contains this, e.g. AutoNation. |
| `size` | list | Store headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+. |
| `revenue` | list | Store revenue band (USD). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+. |
| `mail_provider` | list | Who hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Values: google, microsoft, gateway, other. |
| `domain` | list | Store website domains, e.g. boydauto.com. https://, www. and paths are stripped. |
| `ids` | list | Look up these record ids directly (max 1,000). |
| `min_headcount` | integer | Stores with at least this many staff. |
| `min_contacts` | integer | Stores with at least this many contacts on file. |
| `page` | integer | Page number for offset paging. Default 1. |
| `page_size` | integer | Rows per page. Default 25, max 50 (500 on admin accounts). |
| `cursor` | text | next_cursor from the previous page. Faster and stable for long pulls; overrides page. |

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/dealers?brand=Toyota&state=CA&size=100-249,250%2B" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{
  "total": 64,
  "page_size": 25,
  "next_cursor": "91.2:40211",
  "rows": [{
    "id": 40211, "name": "Toyota of Example", "city": "Fresno", "state": "CA", "country": "US",
    "dealer_type": "franchise", "brands": ["Toyota"], "group_name": null, "headcount_range": "100-249",
    "size_band": "100-249", "revenue_band": "50M+", "contact_count": 38, "mail_provider": "microsoft",
    "unlocked": false, "domain": null, "phone": null, "street": null, "postal_code": null,
    "linkedin_url": null, "headcount": null, "revenue": null, "inventory_count": null, "rank_score": 91.2
  }]
}
```

## POST /dealers/search

Search dealerships with a JSON body. Same as GET /dealers, with the filters in a JSON body.

| Filter | Type | Meaning |
|---|---|---|
| `q` | text | Free text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it. |
| `country` | list | Country codes. Values: US, CA. |
| `state` | list | State or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes. |
| `city` | list | Exact city names (case insensitive), e.g. Houston,Dallas. |
| `brand` | list | OEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters. |
| `type` | list | Store type. Values: franchise, independent, group, powersports_rv, specialty. |
| `group` | text | Dealer group name contains this, e.g. AutoNation. |
| `size` | list | Store headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+. |
| `revenue` | list | Store revenue band (USD). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+. |
| `mail_provider` | list | Who hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Values: google, microsoft, gateway, other. |
| `domain` | list | Store website domains, e.g. boydauto.com. https://, www. and paths are stripped. |
| `ids` | list | Look up these record ids directly (max 1,000). |
| `min_headcount` | integer | Stores with at least this many staff. |
| `min_contacts` | integer | Stores with at least this many contacts on file. |
| `page` | integer | Page number for offset paging. Default 1. |
| `page_size` | integer | Rows per page. Default 25, max 50 (500 on admin accounts). |
| `cursor` | text | next_cursor from the previous page. Faster and stable for long pulls; overrides page. |

```bash
curl -X POST "https://api.usedealerdata.com/functions/v1/api/v1/dealers/search" \
  -H "Authorization: Bearer $DEALERDATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":["exampleford.com","examplehonda.com"]}'
```

```json
{ "total": 2, "rows": [ … ] }
```

## GET /dealers/count

Count dealerships (free). Exact number of stores matching the filters. Also accepts POST with a JSON body.

| Filter | Type | Meaning |
|---|---|---|
| `q` | text | Free text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it. |
| `country` | list | Country codes. Values: US, CA. |
| `state` | list | State or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes. |
| `city` | list | Exact city names (case insensitive), e.g. Houston,Dallas. |
| `brand` | list | OEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters. |
| `type` | list | Store type. Values: franchise, independent, group, powersports_rv, specialty. |
| `group` | text | Dealer group name contains this, e.g. AutoNation. |
| `size` | list | Store headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+. |
| `revenue` | list | Store revenue band (USD). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+. |
| `mail_provider` | list | Who hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Values: google, microsoft, gateway, other. |
| `domain` | list | Store website domains, e.g. boydauto.com. https://, www. and paths are stripped. |
| `ids` | list | Look up these record ids directly (max 1,000). |
| `min_headcount` | integer | Stores with at least this many staff. |
| `min_contacts` | integer | Stores with at least this many contacts on file. |

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/dealers/count?type=franchise&country=CA" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "total": 3104 }
```

## GET /dealers/{id}

One dealership. A single store by id. 404 if the id does not exist.

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/dealers/40211" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "id": 40211, "name": "Toyota of Example", … }
```

## GET /filters

Valid filter values with counts. Every role, seniority, state, OEM, store type, size and revenue band and mail provider, with live counts, plus the top dealer groups and the credit price of each unlock. Read this before building filters.

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/filters" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "totals": { "dealers": 75800, "contacts": 755428, … }, "role": { "gm": 34766, … }, "brand": { "Ford": 6222, … }, "prices": { "contact_email": 4, … } }
```

## POST /unlock

Unlock records (spends credits). Reveals records for this account. Already unlocked ids are free. Ids that do not qualify for the kind (for example contact_email on a contact without a verified email) are skipped, not charged, and listed in skipped with the reason. Max 100 ids per call (1,000 on admin accounts).

| Body field | Type | Meaning |
|---|---|---|
| `kind` | string | Required. contact_email (verified work email + full name + LinkedIn), contact_profile (full name + LinkedIn, for contacts without a verified email), contact_phone (mobile or direct line), or dealer (domain, phone, address, headcount, revenue). |
| `ids` | integer[] | Record ids from search. contact_ids or dealer_ids also work, and without a kind they unlock contact_email or dealer. |
| `return_rows` | boolean | Optional. true also returns the unlocked records, so no second call is needed. |

```bash
curl -X POST "https://api.usedealerdata.com/functions/v1/api/v1/unlock" \
  -H "Authorization: Bearer $DEALERDATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"contact_email","ids":[204401,204407,301122],"return_rows":true}'
```

```json
{
  "unlocked": 2, "credits_spent": 8, "credits_left": 492,
  "skipped": [301122], "skipped_reason": "no verified email; use contact_profile for name + LinkedIn",
  "rows": [ { "id": 204401, "email": "jane@exampleford.com", "unlocked": true, … } ]
}
```

## GET /account

Credit balance and prices. The account email, credit balance and the credit price of each unlock kind.

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/account" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "email": "you@company.com", "credits": 492, "prices": { "dealer": 1, "contact_email": 4, "contact_phone": 10, "contact_profile": 2 } }
```

## GET /export/contacts

Every unlocked contact. All contacts this account has unlocked (email, profile or phone), with store details, in one response with total. 20 exports per hour.

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/export/contacts" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "total": 9162, "rows": [ { "first_name": "Jane", "email": "jane@exampleford.com", "phone": null, "dealer_domain": "exampleford.com", … } ] }
```

## GET /export/dealers

Every unlocked dealership. All stores this account has unlocked, with full details, in one response with total. 20 exports per hour.

```bash
curl "https://api.usedealerdata.com/functions/v1/api/v1/export/dealers" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
```

```json
{ "total": 412, "rows": [ { "name": "Example Ford", "domain": "exampleford.com", … } ] }
```

## MCP tools

Add https://api.usedealerdata.com/functions/v1/mcp as a remote MCP server with the same Bearer key.

| Tool | What it does |
|---|---|
| `search_contacts` | Same filters as GET /contacts. Free. |
| `search_dealers` | Same filters as GET /dealers. Free. |
| `count_contacts` | Exact count, same filters. Free. |
| `count_dealers` | Exact count, same filters. Free. |
| `get_contacts` | Contacts by id (up to 500). |
| `get_dealers` | Dealerships by id (up to 500). |
| `filter_values` | Every valid filter value with counts, and unlock prices. |
| `unlock` | Spend credits; same rules as POST /unlock, including return_rows and skipped. |
| `account` | Credit balance and prices. |

## Limits

| | Standard account | Admin account |
|---|---|---|
| Calls | 60 a minute, 600 an hour | No limit |
| Exports | 20 an hour | No limit |
| Rows per page | 50 | 500 |
| Ids per unlock | 100 | 1,000 |
| Search pages a day | 600 (20 before a plan, trial or invite) | No limit |

Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`; a 429 carries `Retry-After` (seconds).

## Errors

Errors are JSON: `{"error": "<code>", "message": "<what to fix>"}`.

| Status | Code | Meaning |
|---|---|---|
| 400 | `unknown_filter` | A filter name the endpoint does not take. The message lists the valid ones. Common plurals (roles, states, brands) are read as the singular and named in warnings instead. |
| 400 | `bad_value` | A value outside the allowed set (unknown role, state name instead of code, bad boolean, bad cursor). The message lists what is valid. |
| 400 | `kind_required` | Unlock sent without kind. The response lists the valid kinds in kinds. |
| 400 | `too_many_ids` | More ids in one unlock than the account may send (100, or 1,000 on admin accounts). |
| 401 | `invalid_api_key` | Missing, wrong or revoked key. |
| 402 | `insufficient_credits` | Not enough credits for the unlock. Nothing was charged. |
| 404 | `not_found` | Unknown route or record id. |
| 429 | `rate_limited` | Per-minute (60), per-hour (600) or export (20 per hour) limit reached. Wait Retry-After seconds. Admin accounts are not limited. |
| 429 | `daily_search_limit` | Daily search pages used up (20 a day before a plan, trial or invite; 600 a day after). Counts are not limited. |
