DealerData

자동차 딜러에게 판매하는 팀을 위한 검증된 미국 및 캐나다 딜러십 연락처 데이터입니다. REST 또는 원격 MCP 서버를 통해 딜러십과 직책별 의사 결정자를 검색하고, 실시간으로 확인된 업무용 이메일을 이용할 수 있습니다.

호스팅형 MCP 서버

npx add-mcp 'https://api.usedealerdata.com/functions/v1/mcp'

Claude Code, Codex, Cursor 등에 설치됩니다

문서

DealerData API

Verified contacts and store data for every US and Canadian car dealership, over REST or MCP. Search and counts are free during your 7-day card trial and on a plan or credit pack; unlocking a record spends credits. Built for scripts and AI agents: every filter is validated, and a typo is refused with the valid options instead of returning the wrong list.

REST base URL

https://api.usedealerdata.com/functions/v1/api/v1

MCP server

https://api.usedealerdata.com/functions/v1/mcp

Auth

Authorization: Bearer dd_live_… Keys are made in your account.

For agents: the same reference as Markdown and OpenAPI 3.1.

Pull a list in five calls

  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 it comes back null.
  4. POST /unlock with the ids you want and "return_rows": true to get the emails in the same call.
  5. GET /export/contacts any time for what you have unlocked (contacts no longer at their store are left out).

Lists take comma separated values (role=gm,gsm), at most 1,000 per filter; a longer list is refused with a 400 bad_value. Responses may carry warnings, for example when a plural filter name was read as the singular or an OEM matches no store.

GET /contacts

Search contacts (free)

Dealership staff matching the filters, best ranked first. Emails are masked and phones hidden until unlocked: a masked email shows only the first letter and the top-level domain (j•••@•••.com), so the store domain is not in it; the full address shows once the contact is unlocked, and the store domain once the store is unlocked (GET /dealers). The row id is what you pass to /unlock. Each page counts toward the daily search allowance. Each row also carries stock: the stock of its store (by dealer_id; {new, used, as_of, source}) when this account has unlocked that store and a count is on file, and null otherwise. department (the person's department at the store, the headcount_by_department split) is on Pro and Scale only; on other plans it is left out of contact rows (search, by id, unlock return_rows) and null in GET /export/contacts.

FilterTypeMeaning
qtextFree text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it.
rolelistJob 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
senioritylistSeniority. Values: owner, executive, director, manager, staff
titletextJob title contains this phrase (case insensitive), e.g. "internet director".
title_anylistComma 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". At most 25 titles of up to 10 words each.
countrylistCountry codes. Values: US, CA
statelistState or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes (an unknown code is refused with the valid list)
citylistExact city names (case insensitive), e.g. Houston,Dallas.
brandlistOEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty
grouptextDealer group name contains this, e.g. AutoNation.
exclude_grouplistLeave out stores whose dealer group name contains any of these (case insensitive), e.g. Lithia,AutoNation. A store also goes when a parent group matches (Lithia's DCH Auto Group, Suburban Collection...), where we hold the ownership link. Stores with no group stay. Works with group (include one name, exclude others). A name with its own comma stays one term (Koons Automotive Companies, Inc); write any other comma inside a name as \, or send an array.
sizelistStore headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+
revenuelistStore revenue band (USD); unknown = no revenue on record. Pro and Scale only (refused with filter_locked below Pro, and revenue_band is left out of rows). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+, unknown
mail_providerlistWho hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Pro and Scale only (refused with filter_locked below Pro, and mail_provider / mail_gateway are left out of rows). Values: google, microsoft, gateway, other
domainlistStore website domains, e.g. boydauto.com. https://, www. and paths are stripped. Matches only stores this account has unlocked (a locked store's domain is hidden): a domain of a store you have not unlocked returns no rows, with a warning.
dealer_idlistOnly contacts at these dealer ids.
idslistLook up these record ids directly (max 1,000). On contacts, each contact returned counts as a profile view, as in the app (60 a minute, 1,000 an hour). On dealers, each store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 in the trial); unlocked stores are free. A page past the window returns the rows that fit with truncated: true, truncated_reason, ids_not_returned and next_cursor.
email_tierlistEmail check result: verified (a confirmed email, unlock kind contact_email); unconfirmed (the host accepts any address, or a re-check could not confirm a once-verified email, so the email cannot be confirmed: unlock kind contact_email_unconfirmed); unverified (no usable email, hidden). Pro and Scale also see which host an unconfirmed email is on (microsoft or gateway) and can filter on those two values; below Pro rows say unconfirmed and email_tier=microsoft / gateway is refused with filter_locked, as the mail_provider filter is. Values: verified, unconfirmed, microsoft, gateway, unverified
has_emailtrue/falsetrue: only contacts with a verified work email (email_tier verified).
has_phonetrue/falsetrue: only contacts with a mobile or direct line.
has_linkedintrue/falsetrue: only contacts with a LinkedIn profile.
movedtrue/falsetrue: only people recently found in a new seat at another store. A signal: Pro plan only (moved_from is also left out of rows below Pro).
verified_within_daysintegerEmail verified within this many days: the address is verified now, dated by its last deliverable check (a risky or unknown answer does not count). The check date counts only for emails you have unlocked (or on Pro); other contacts are dated by verified_at.
max_per_dealerintegerAt most this many contacts per store (highest ranked first).
pageintegerPage number for offset paging. Default 1.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Faster and stable for long pulls (resumes where that page ended even if its last row changes rank or is removed; a row that moves rank mid-walk can come back again, so dedupe on id); overrides page.
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"
{
  "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•••@•••.com",
    "email_verified": true, "email_tier": "verified", "mail_provider": "google", "verified_at": "2026-10-21T09:12:00Z",
    "email_verified_at": null, "has_phone": true, "phone": null, "phone_type": "mobile", "has_linkedin": true, "linkedin_url": null,
    "moved_from": null, "unlocked": false, "phone_unlocked": false, "stock": null
  }]
}

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 lists. The domain filter matches only stores this account has unlocked; any search that uses it carries a warning saying so.

FilterTypeMeaning
qtextFree text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it.
rolelistJob 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
senioritylistSeniority. Values: owner, executive, director, manager, staff
titletextJob title contains this phrase (case insensitive), e.g. "internet director".
title_anylistComma 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". At most 25 titles of up to 10 words each.
countrylistCountry codes. Values: US, CA
statelistState or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes (an unknown code is refused with the valid list)
citylistExact city names (case insensitive), e.g. Houston,Dallas.
brandlistOEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty
grouptextDealer group name contains this, e.g. AutoNation.
exclude_grouplistLeave out stores whose dealer group name contains any of these (case insensitive), e.g. Lithia,AutoNation. A store also goes when a parent group matches (Lithia's DCH Auto Group, Suburban Collection...), where we hold the ownership link. Stores with no group stay. Works with group (include one name, exclude others). A name with its own comma stays one term (Koons Automotive Companies, Inc); write any other comma inside a name as \, or send an array.
sizelistStore headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+
revenuelistStore revenue band (USD); unknown = no revenue on record. Pro and Scale only (refused with filter_locked below Pro, and revenue_band is left out of rows). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+, unknown
mail_providerlistWho hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Pro and Scale only (refused with filter_locked below Pro, and mail_provider / mail_gateway are left out of rows). Values: google, microsoft, gateway, other
domainlistStore website domains, e.g. boydauto.com. https://, www. and paths are stripped. Matches only stores this account has unlocked (a locked store's domain is hidden): a domain of a store you have not unlocked returns no rows, with a warning.
dealer_idlistOnly contacts at these dealer ids.
idslistLook up these record ids directly (max 1,000). On contacts, each contact returned counts as a profile view, as in the app (60 a minute, 1,000 an hour). On dealers, each store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 in the trial); unlocked stores are free. A page past the window returns the rows that fit with truncated: true, truncated_reason, ids_not_returned and next_cursor.
email_tierlistEmail check result: verified (a confirmed email, unlock kind contact_email); unconfirmed (the host accepts any address, or a re-check could not confirm a once-verified email, so the email cannot be confirmed: unlock kind contact_email_unconfirmed); unverified (no usable email, hidden). Pro and Scale also see which host an unconfirmed email is on (microsoft or gateway) and can filter on those two values; below Pro rows say unconfirmed and email_tier=microsoft / gateway is refused with filter_locked, as the mail_provider filter is. Values: verified, unconfirmed, microsoft, gateway, unverified
has_emailtrue/falsetrue: only contacts with a verified work email (email_tier verified).
has_phonetrue/falsetrue: only contacts with a mobile or direct line.
has_linkedintrue/falsetrue: only contacts with a LinkedIn profile.
movedtrue/falsetrue: only people recently found in a new seat at another store. A signal: Pro plan only (moved_from is also left out of rows below Pro).
verified_within_daysintegerEmail verified within this many days: the address is verified now, dated by its last deliverable check (a risky or unknown answer does not count). The check date counts only for emails you have unlocked (or on Pro); other contacts are dated by verified_at.
max_per_dealerintegerAt most this many contacts per store (highest ranked first).
pageintegerPage number for offset paging. Default 1.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Faster and stable for long pulls (resumes where that page ended even if its last row changes rank or is removed; a row that moves rank mid-walk can come back again, so dedupe on id); overrides page.
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}'
{ "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.

FilterTypeMeaning
qtextFree text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it.
rolelistJob 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
senioritylistSeniority. Values: owner, executive, director, manager, staff
titletextJob title contains this phrase (case insensitive), e.g. "internet director".
title_anylistComma 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". At most 25 titles of up to 10 words each.
countrylistCountry codes. Values: US, CA
statelistState or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes (an unknown code is refused with the valid list)
citylistExact city names (case insensitive), e.g. Houston,Dallas.
brandlistOEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty
grouptextDealer group name contains this, e.g. AutoNation.
exclude_grouplistLeave out stores whose dealer group name contains any of these (case insensitive), e.g. Lithia,AutoNation. A store also goes when a parent group matches (Lithia's DCH Auto Group, Suburban Collection...), where we hold the ownership link. Stores with no group stay. Works with group (include one name, exclude others). A name with its own comma stays one term (Koons Automotive Companies, Inc); write any other comma inside a name as \, or send an array.
sizelistStore headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+
revenuelistStore revenue band (USD); unknown = no revenue on record. Pro and Scale only (refused with filter_locked below Pro, and revenue_band is left out of rows). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+, unknown
mail_providerlistWho hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Pro and Scale only (refused with filter_locked below Pro, and mail_provider / mail_gateway are left out of rows). Values: google, microsoft, gateway, other
domainlistStore website domains, e.g. boydauto.com. https://, www. and paths are stripped. Matches only stores this account has unlocked (a locked store's domain is hidden): a domain of a store you have not unlocked returns no rows, with a warning.
dealer_idlistOnly contacts at these dealer ids.
idslistLook up these record ids directly (max 1,000). On contacts, each contact returned counts as a profile view, as in the app (60 a minute, 1,000 an hour). On dealers, each store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 in the trial); unlocked stores are free. A page past the window returns the rows that fit with truncated: true, truncated_reason, ids_not_returned and next_cursor.
email_tierlistEmail check result: verified (a confirmed email, unlock kind contact_email); unconfirmed (the host accepts any address, or a re-check could not confirm a once-verified email, so the email cannot be confirmed: unlock kind contact_email_unconfirmed); unverified (no usable email, hidden). Pro and Scale also see which host an unconfirmed email is on (microsoft or gateway) and can filter on those two values; below Pro rows say unconfirmed and email_tier=microsoft / gateway is refused with filter_locked, as the mail_provider filter is. Values: verified, unconfirmed, microsoft, gateway, unverified
has_emailtrue/falsetrue: only contacts with a verified work email (email_tier verified).
has_phonetrue/falsetrue: only contacts with a mobile or direct line.
has_linkedintrue/falsetrue: only contacts with a LinkedIn profile.
movedtrue/falsetrue: only people recently found in a new seat at another store. A signal: Pro plan only (moved_from is also left out of rows below Pro).
verified_within_daysintegerEmail verified within this many days: the address is verified now, dated by its last deliverable check (a risky or unknown answer does not count). The check date counts only for emails you have unlocked (or on Pro); other contacts are dated by verified_at.
max_per_dealerintegerAt most this many contacts per store (highest ranked first).
curl "https://api.usedealerdata.com/functions/v1/api/v1/contacts/count?role=gm&brand=Ford&has_email=true" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "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. Counts as a profile view, as in the app (60 a minute, 1,000 an hour, shared with the ids filter and MCP get_contacts): 429 rate_limited when that window is full. Also counts as one search page (429 daily_search_limit when the day's are used up).

curl "https://api.usedealerdata.com/functions/v1/api/v1/contacts/204401" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "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 rank_score (the sort key, built from headcount) show once the store is unlocked, and the revenue estimate too on Pro and Scale. revenue, revenue_band and mail_provider are Pro and Scale fields, left out of rows on other plans (null on /export/dealers rows). Each row also carries stock: {new, used, as_of, source} (vehicles in stock, as_of the date it was counted) for a store this account has unlocked with a count on file, and null otherwise (not unlocked, or no count yet). An unlocked store with a live social profile also carries socials: {network: https url} with only the networks that have a live profile (facebook, instagram, linkedin, youtube, tiktok, x); the socials key is absent, never null, when no live profile is known or the store is not unlocked. The second example row is an unlocked store.

FilterTypeMeaning
qtextFree text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it.
countrylistCountry codes. Values: US, CA
statelistState or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes (an unknown code is refused with the valid list)
citylistExact city names (case insensitive), e.g. Houston,Dallas.
brandlistOEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty
grouptextDealer group name contains this, e.g. AutoNation.
exclude_grouplistLeave out stores whose dealer group name contains any of these (case insensitive), e.g. Lithia,AutoNation. A store also goes when a parent group matches (Lithia's DCH Auto Group, Suburban Collection...), where we hold the ownership link. Stores with no group stay. Works with group (include one name, exclude others). A name with its own comma stays one term (Koons Automotive Companies, Inc); write any other comma inside a name as \, or send an array.
sizelistStore headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+
revenuelistStore revenue band (USD); unknown = no revenue on record. Pro and Scale only (refused with filter_locked below Pro, and revenue_band is left out of rows). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+, unknown
mail_providerlistWho hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Pro and Scale only (refused with filter_locked below Pro, and mail_provider / mail_gateway are left out of rows). Values: google, microsoft, gateway, other
domainlistStore website domains, e.g. boydauto.com. https://, www. and paths are stripped. Matches only stores this account has unlocked (a locked store's domain is hidden): a domain of a store you have not unlocked returns no rows, with a warning.
idslistLook up these record ids directly (max 1,000). On contacts, each contact returned counts as a profile view, as in the app (60 a minute, 1,000 an hour). On dealers, each store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 in the trial); unlocked stores are free. A page past the window returns the rows that fit with truncated: true, truncated_reason, ids_not_returned and next_cursor.
min_headcountintegerStores with at least this many staff. Exact headcount on unlocked stores; on locked stores, the lower bound of the headcount range shown.
min_contactsintegerStores with at least this many contacts on file.
pageintegerPage number for offset paging. Default 1.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Faster and stable for long pulls (resumes where that page ended even if its last row changes rank or is removed; a row that moves rank mid-walk can come back again, so dedupe on id); overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/dealers?brand=Toyota&state=CA&size=100-249,250%2B" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{
  "total": 64,
  "page_size": 25,
  "next_cursor": "dQm3vX8kT2pLr6yNc0aW4eHs9JfUg1Bz",
  "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": null, "stock": null
  }, {
    "id": 1840, "name": "Example Ford", "unlocked": true, "domain": "exampleford.com", …,
    "stock": { "new": 212, "used": 148, "as_of": "2026-11-10", "source": "website" },
    "socials": { "facebook": "https://www.facebook.com/exampleford", "instagram": "https://www.instagram.com/exampleford" }
  }]
}

POST /dealers/search

Search dealerships with a JSON body

Same as GET /dealers, with the filters in a JSON body. Lists can be arrays or comma separated strings. domain matches only stores this account has unlocked (a locked store's domain is hidden), so it looks up your unlocked stores; to find a store you have not unlocked, search by name, city, brand or group.

FilterTypeMeaning
qtextFree text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it.
countrylistCountry codes. Values: US, CA
statelistState or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes (an unknown code is refused with the valid list)
citylistExact city names (case insensitive), e.g. Houston,Dallas.
brandlistOEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty
grouptextDealer group name contains this, e.g. AutoNation.
exclude_grouplistLeave out stores whose dealer group name contains any of these (case insensitive), e.g. Lithia,AutoNation. A store also goes when a parent group matches (Lithia's DCH Auto Group, Suburban Collection...), where we hold the ownership link. Stores with no group stay. Works with group (include one name, exclude others). A name with its own comma stays one term (Koons Automotive Companies, Inc); write any other comma inside a name as \, or send an array.
sizelistStore headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+
revenuelistStore revenue band (USD); unknown = no revenue on record. Pro and Scale only (refused with filter_locked below Pro, and revenue_band is left out of rows). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+, unknown
mail_providerlistWho hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Pro and Scale only (refused with filter_locked below Pro, and mail_provider / mail_gateway are left out of rows). Values: google, microsoft, gateway, other
domainlistStore website domains, e.g. boydauto.com. https://, www. and paths are stripped. Matches only stores this account has unlocked (a locked store's domain is hidden): a domain of a store you have not unlocked returns no rows, with a warning.
idslistLook up these record ids directly (max 1,000). On contacts, each contact returned counts as a profile view, as in the app (60 a minute, 1,000 an hour). On dealers, each store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 in the trial); unlocked stores are free. A page past the window returns the rows that fit with truncated: true, truncated_reason, ids_not_returned and next_cursor.
min_headcountintegerStores with at least this many staff. Exact headcount on unlocked stores; on locked stores, the lower bound of the headcount range shown.
min_contactsintegerStores with at least this many contacts on file.
pageintegerPage number for offset paging. Default 1.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Faster and stable for long pulls (resumes where that page ended even if its last row changes rank or is removed; a row that moves rank mid-walk can come back again, so dedupe on id); overrides page.
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"]}'
{
  "total": 1, "page_size": 25, "next_cursor": null,
  "rows": [ { "id": 1840, "name": "Example Ford", "unlocked": true, "domain": "exampleford.com", … } ],
  "warnings": ["domain matches only stores this account has unlocked (unlock kind dealer); a domain of a store you have not unlocked returns no rows, so a missing domain does not mean the store is not in DealerData"]
}

GET /dealers/count

Count dealerships (free)

Exact number of stores matching the filters. Also accepts POST with a JSON body.

FilterTypeMeaning
qtextFree text. Contacts: title, full name or dealership name contains it. Dealers: name or city contains it.
countrylistCountry codes. Values: US, CA
statelistState or province codes, e.g. TX,FL,ON. Values: two-letter US state or Canadian province codes (an unknown code is refused with the valid list)
citylistExact city names (case insensitive), e.g. Houston,Dallas.
brandlistOEMs the store sells, e.g. Ford,Toyota. Matches any. Values: OEM names as listed by GET /filters
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty
grouptextDealer group name contains this, e.g. AutoNation.
exclude_grouplistLeave out stores whose dealer group name contains any of these (case insensitive), e.g. Lithia,AutoNation. A store also goes when a parent group matches (Lithia's DCH Auto Group, Suburban Collection...), where we hold the ownership link. Stores with no group stay. Works with group (include one name, exclude others). A name with its own comma stays one term (Koons Automotive Companies, Inc); write any other comma inside a name as \, or send an array.
sizelistStore headcount band. Values: 1-19, 20-49, 50-99, 100-249, 250+
revenuelistStore revenue band (USD); unknown = no revenue on record. Pro and Scale only (refused with filter_locked below Pro, and revenue_band is left out of rows). Values: <1M, 1-5M, 5-10M, 10-25M, 25-50M, 50M+, unknown
mail_providerlistWho hosts the store's email. gateway = behind a security gateway (Mimecast, Proofpoint and the like). Pro and Scale only (refused with filter_locked below Pro, and mail_provider / mail_gateway are left out of rows). Values: google, microsoft, gateway, other
domainlistStore website domains, e.g. boydauto.com. https://, www. and paths are stripped. Matches only stores this account has unlocked (a locked store's domain is hidden): a domain of a store you have not unlocked returns no rows, with a warning.
idslistLook up these record ids directly (max 1,000). On contacts, each contact returned counts as a profile view, as in the app (60 a minute, 1,000 an hour). On dealers, each store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 in the trial); unlocked stores are free. A page past the window returns the rows that fit with truncated: true, truncated_reason, ids_not_returned and next_cursor.
min_headcountintegerStores with at least this many staff. Exact headcount on unlocked stores; on locked stores, the lower bound of the headcount range shown.
min_contactsintegerStores with at least this many contacts on file.
curl "https://api.usedealerdata.com/functions/v1/api/v1/dealers/count?type=franchise&country=CA" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "total": 3104 }

GET /dealers/{id}

One dealership

A single store by id, showing whatever this account has unlocked. 404 if the id does not exist. A store not unlocked counts as a store view, as in the app (60 a minute, 1,000 an hour, and 500 a day on the free plan or 2,500 during the trial, shared with the ids filter and MCP get_dealers): 429 rate_limited when that window is full. Also counts as one search page (429 daily_search_limit when the day's are used up). Carries stock and socials as in GET /dealers: stock {new, used, as_of, source} for an unlocked store with a count on file, null otherwise; socials {network: https url} (facebook, instagram, linkedin, youtube, tiktok, x) only for an unlocked store with a live profile, the key absent, never null, when none is known or the store is not unlocked.

curl "https://api.usedealerdata.com/functions/v1/api/v1/dealers/40211" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "id": 40211, "name": "Toyota of Example", "unlocked": false, …, "stock": null }

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. The counts cover the whole database, not your account, so an email you already hold can sit under a different email_tier here than in your own /contacts rows.

curl "https://api.usedealerdata.com/functions/v1/api/v1/filters" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "totals": { "dealers": 73553, "contacts": 791257, … }, "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. A contact DealerData has flagged as no longer at its store (left or moved away) is not sold under any kind: its id is skipped and not charged and, below Pro, listed in off_sale (that key appears only when there is such an id), with skipped_reason "not sold" (or the kind's reason followed by "; off_sale ids: not sold" when other ids were skipped for that reason, so retry only the ids not in off_sale under another kind). Why it is not sold is a Pro Signal: on the Pro plan the id is listed in not_at_store instead of off_sale (that key appears only when there is such an id) and skipped_reason says "no longer at this store (flagged by DealerData; not sold)". An id this account unlocked whose record is no longer in our data (a contact or store dropped from the dataset; its unlock was refunded) is skipped, not charged and listed in gone (that key appears only when there is such an id), with skipped_reason "no longer in our data" (or "; gone ids: no longer in our data" after other reasons). contact_email on an email that cannot be confirmed (an unverified Microsoft 365 / gateway email, or a verified one a later re-check could not confirm) is skipped, not charged and listed in unconfirmed (that key appears only when there is such an id), with skipped_reason "these emails cannot be confirmed (catch-all mail host); they sell as contact_email_unconfirmed" (the re-check case and a mix of both have their own wording; after other reasons it follows "; unconfirmed ids: "); unlock those ids as contact_email_unconfirmed. Max 100 ids per call (1,000 on admin accounts). dealer_ids with a contact kind answers in a second shape (the second example): contact_ids lists the contacts this call unlocked, skipped is counts instead of a list: already_unlocked, no_data and unconfirmed (the stores' contacts that were already unlocked, have nothing on file for the kind, or with contact_email have an email that sells only as contact_email_unconfirmed; all free; the unconfirmed count appears only when there are any), there is no skipped_reason, with contact_email unconfirmed_ids lists those contacts (unlock them as contact_email_unconfirmed) and unconfirmed_reason says why (both only when there are any), and unknown_dealer_ids lists store ids that do not exist (free). In the rare case the post-charge read of what was unlocked fails, the answer has no skipped list and a warnings entry instead (GET /contacts?ids= or /dealers?ids= shows what is unlocked); the charge in unlocked and credits_spent stands.

Body fieldTypeMeaning
kindstringRequired. contact_email (verified work email + full name + LinkedIn), contact_email_unconfirmed (an unconfirmed Microsoft 365 / gateway email + full name + LinkedIn, at a lower price), contact_profile (full name + LinkedIn, for contacts without a verified email), contact_phone (mobile or direct line), or dealer (domain, phone, address, headcount, revenue).
idsinteger[]Record ids from search. contact_ids or dealer_ids also work (one id field per call), and without a kind they unlock contact_email or dealer. dealer_ids with a contact kind unlocks every contact at those stores that has that field and is not already unlocked; more than 100 such contacts (1,000 on admin accounts) refuses the call (400 too_many_contacts, nothing charged).
return_rowsbooleanOptional. true also returns the unlocked records, so no second call is needed. Reading them back does not count against the daily search pages. An unlock that spends the last credits of an account with no paid plan returns the rows anyway, with api_off (the 402 the next call gets) and a warnings entry: at 0 credits such an account has no API or MCP, so the records could not be read back over it.
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}'
// ids or contact_ids
{
  "unlocked": 2,
  "credits_spent": 8,
  "credits_left": 492,
  "skipped": [301122, 301130],
  "skipped_reason": "no verified email; use contact_profile for name + LinkedIn; unconfirmed ids: these emails cannot be confirmed (catch-all mail host); they sell as contact_email_unconfirmed",
  "unconfirmed": [301130],
  "rows": [{ "id": 204401, "email": "jane@exampleford.com", "unlocked": true, … }]
}
// dealer_ids with contact_email, contact_phone or contact_profile, for example {"kind":"contact_email","dealer_ids":[5150,5151,99999999],"return_rows":true}
{
  "unlocked": 2,
  "credits_spent": 8,
  "credits_left": 484,
  "contact_ids": [204401, 204407],
  "skipped": { "already_unlocked": 1, "no_data": 3, "unconfirmed": 1 },
  "unconfirmed_ids": [204412],
  "unconfirmed_reason": "these emails cannot be confirmed (catch-all mail host); they sell as contact_email_unconfirmed",
  "unknown_dealer_ids": [99999999],
  "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.

curl "https://api.usedealerdata.com/functions/v1/api/v1/account" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "email": "you@company.com", "credits": 492, "prices": { "dealer": 1, "contact_email": 4, "contact_phone": 10, "contact_profile": 2 } }

GET /export/contacts

Your unlocked contacts

The contacts this account has unlocked (email, profile or phone), with store details. Contacts DealerData has since flagged as no longer at their store (left or moved away; their credits were refunded) are left out, as on the Exports page, so an id you unlocked can drop out of the export (and come back if the flag is lifted); GET /contacts?ids still opens them. Each row carries the contact id and its dealer_id. Below Pro, mail_provider and department are null on these rows (the columns stay; Pro and Scale fields, as on GET /contacts), so a null there can mean your plan rather than no data. Below Pro, email_tier reads unconfirmed where Pro and Scale see microsoft or gateway (the mail host). total is every row of the export (whatever the offset); returned is the rows in this response. Rows come oldest unlock first; a contact that comes back into the export counts from when it came back. Up to 25,000 rows per call; past that the response has truncated: true and next_cursor, so call again with?cursor=next_cursor until a reply has no truncated. A cursor continues after the last row you got even if you unlock or lose contacts in between (new unlocks and contacts that come back are added at the end, nothing is skipped; a contact you already got that drops out and comes back mid-walk comes again);?offset=next_offset still works but can repeat or skip a row when the export changes between calls (next_offset is only on replies paged without a cursor). 20 exports per hour.

curl "https://api.usedealerdata.com/functions/v1/api/v1/export/contacts" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "total": 9162, "returned": 9162, "rows": [ { "id": 204401, "dealer_id": 1840, "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; each row carries the store id. Below Pro, revenue, revenue_band and mail_provider are null on these rows (the columns stay; Pro and Scale fields, as on GET /dealers), so a null there can mean your plan rather than no data. total is every row of the export, returned the rows in this response (oldest unlock first, same 25,000-row pages and?cursor= as contacts). Export rows carry neither stock nor socials (and export contact rows no stock); read them with GET /dealers?ids= or GET /dealers/{id}. 20 exports per hour.

curl "https://api.usedealerdata.com/functions/v1/api/v1/export/dealers" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "total": 412, "returned": 412, "rows": [ { "id": 1840, "name": "Example Ford", "domain": "exampleford.com", … } ] }

GET /datasets

The micro-datasets

Each micro-dataset with its fields, parameters and the plan it needs. A micro-dataset is a narrow slice (its own columns plus the store id, name and domain), paged like search: each page counts as one search page, with the same page size, cursor and offset limits. Nothing is charged in credits. The store domain and any hidden store columns show only for stores you unlocked.

curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "datasets": [ { "name": "staff", "route": "/datasets/staff", "tool": "dataset_staff", "plan": "free", "fields": [ … ], "params": [ … ] }, … ] }

GET /datasets/staff

People at a store by department and role (GM, managers)

Contacts at the matching stores (give at least one of dealer_id, state, brand, group, type, role or department), best ranked first, with role, seniority and department (department: Pro and Scale); is_gm and is_manager flag the general manager and managers. Emails are masked and phones and LinkedIn hidden until unlocked, as in GET /contacts; as there, a verified email that a later re-check could not confirm reads email_verified false with email_sold_unconfirmed: true (unlock it as contact_email_unconfirmed). source=store_staff reads the people listed on the stores' own staff pages instead (when loaded): name, title and department, never the email or phone shown on the site. The department split is Premium, as headcount_by_department: below Pro the rows leave department out and the department filter needs Pro (403 plan_required, nothing charged). Plan: every plan. Fields: id, dealer_id, dealer_name, dealer_domain, first_name, last_name, title, role, seniority, department, is_gm, is_manager, has_email, email, email_verified, email_verified_at, has_phone, phone, has_linkedin, linkedin_url, unlocked, phone_unlocked, source. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
rolelistJob role keys (general_manager is read as gm). 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.
senioritylistSeniority. Values: owner, executive, director, manager, staff.
departmentlistDepartment, e.g. sales, service, parts, management (case insensitive). Pro and Scale only (403 plan_required below Pro).
managers_onlytrue/falseOnly the GM, owners, directors and managers.
sourcetextcontacts (default) or store_staff (people listed on the store website; role, seniority, managers_only and cursor do not apply: it pages with page, within the offset depth). While store_staff is not loaded it answers {loaded: false, total: 0, rows: []} and is not counted as a search page. Values: contacts, store_staff.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/staff?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "staff", "total": 412, "page_size": 50, "next_cursor": "98.5:1201", "rows": [ … ] }

GET /datasets/stock

New and used vehicles in stock per store

Stock counts (new, used, total) with the date and source of the count. has_stock says whether a count exists; the counts show once the store is unlocked. loaded is false (with a note) while no store has a count. Plan: every plan. Fields: id, name, domain, country, state, unlocked, has_stock, inventory_new, inventory_used, inventory_count, inventory_checked_at, inventory_source, inventory_status. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
has_stocktrue/falseOnly stores with (true) or without (false) a stock count.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/stock?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "stock", "total": 412, "page_size": 50, "next_cursor": "dR7nKp2wXc5LtQ9sVb3mHy8GjE0aUf4D", "rows": [ … ] }

GET /datasets/signals

Live buying signals per store (Pro and Scale)

Signals that are still live (hiring, new in role, moved, tech present or switched, ownership change, reviews, ads), newest first sighting first (paging is stable: a signal seen again by a later run keeps its place), with source and confidence. Values never carry URLs, emails or phone numbers. Needs Pro or Scale (Signals unlocked). Other accounts get a teaser instead: {teaser: true, total} (how many live signals the store filters match, nothing else), still one search page; signal_type and since need Pro (403 plan_required). Plan: Pro and Scale. Fields: id, dealer_id, dealer_name, dealer_domain, contact_id, signal_type, event_date, first_seen, last_seen, source, source_url, confidence, value. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
signal_typelistSignal types. Values: new_in_role, moved, career_path, hiring, tech_present, tech_switch, ownership_change, inventory, reviews, ads_active, mobile_phone.
sincedateOnly signals seen on or after this date (YYYY-MM-DD).
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/signals?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "signals", "total": 412, "page_size": 50, "next_cursor": "98.5:1201", "rows": [ … ] }

GET /datasets/dealer_type

Store type and dealer group

Franchise, independent, group, powersports / RV or specialty, and the dealer group the store belongs to. Plan: every plan. Fields: id, name, domain, country, state, unlocked, dealer_type, group_name, in_group. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/dealer_type?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "dealer_type", "total": 412, "page_size": 50, "next_cursor": "dR7nKp2wXc5LtQ9sVb3mHy8GjE0aUf4D", "rows": [ … ] }

GET /datasets/oem

OEM brands each store sells

The makes each store is franchised for, with the count. Plan: every plan. Fields: id, name, domain, country, state, unlocked, brands, brand_count. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/oem?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "oem", "total": 412, "page_size": 50, "next_cursor": "dR7nKp2wXc5LtQ9sVb3mHy8GjE0aUf4D", "rows": [ … ] }

GET /datasets/revenue

Revenue estimate per store (Pro and Scale)

The revenue band, and the revenue estimate (text and USD) once the store is unlocked. loaded is false (with a note) while no store has an estimate. Pro and Scale only (a Premium field); other accounts get 403 plan_required. Plan: Pro and Scale. Fields: id, name, domain, country, state, unlocked, revenue_band, revenue, revenue_usd. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/revenue?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "revenue", "total": 412, "page_size": 50, "next_cursor": "dR7nKp2wXc5LtQ9sVb3mHy8GjE0aUf4D", "rows": [ … ] }

GET /datasets/headcount_by_department

Staff on file per department at each store (Pro and Scale)

staff_count and departments ({sales: 3, service: 1, …}): the people on file at the store by department (lower case; unassigned when the department is not known). Pro and Scale only (a Premium field). Other accounts get a teaser instead: {teaser: true, total} (how many stores the store filters match, nothing else), still one search page. Plan: Pro and Scale. Fields: id, name, domain, country, state, unlocked, staff_count, departments. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/headcount_by_department?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "headcount_by_department", "total": 412, "page_size": 50, "next_cursor": "dR7nKp2wXc5LtQ9sVb3mHy8GjE0aUf4D", "rows": [ … ] }

GET /datasets/email_provider

Email provider of each store (Pro and Scale)

mail_provider (google, microsoft, gateway or other, read from the domain's mail servers) and, for a gateway, mail_gateway (e.g. Proofpoint). loaded is false (with a note) while no store has one. Pro and Scale only (a Premium field). Other accounts get a teaser instead: {teaser: true, total} (how many stores the store filters match, nothing else), still one search page; the mail_provider filter needs Pro (403 plan_required). Plan: Pro and Scale. Fields: id, name, domain, country, state, unlocked, mail_provider, mail_gateway. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
mail_providerlistOnly stores on these email providers. Values: google, microsoft, gateway, other.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/email_provider?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "email_provider", "total": 412, "page_size": 50, "next_cursor": "dR7nKp2wXc5LtQ9sVb3mHy8GjE0aUf4D", "rows": [ … ] }

GET /datasets/founded_year

Year each store was founded (Pro and Scale; coming soon)

Coming soon: no store has a founded year yet. Every call answers {loaded: false, coming_soon: true, total: 0, rows: []} and is not counted as a search page. Pro and Scale only once loaded. Plan: Pro and Scale. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/founded_year?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "founded_year", "loaded": false, "coming_soon": true, "total": 0, "page_size": 50, "rows": [], "next_cursor": null, "plan": "premium", "required": "premium", "note": "…" }

GET /datasets/company_news

Recent news about each store (Pro and Scale; coming soon)

Coming soon: no store news is loaded yet. Every call answers {loaded: false, coming_soon: true, total: 0, rows: []} and is not counted as a search page. Pro and Scale only once loaded. Plan: Pro and Scale. POST with a JSON body takes the same parameters.

ParameterTypeMeaning
dealer_idlistDealer ids (up to 1,000), comma separated.
countrylistCountry codes. Values: US, CA.
statelistTwo-letter state or province codes. Values: AL, AK, AZ, AR, CA, CO, CT, DE, DC, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY, PR, GU, VI, AB, BC, MB, NB, NL, NS, NT, NU, ON, PE, QC, SK, YT.
brandlistOEM brands the store sells (any of them). See GET /filters for names.
typelistStore type. Values: franchise, independent, group, powersports_rv, specialty.
grouptextDealer group name contains this text.
exclude_grouplistLeave out stores whose dealer group name, or a parent group's name, contains any of these, e.g. Lithia,AutoNation. Stores with no group stay.
pageintegerPage number for offset paging. Default 1. Offset paging stops at row 10,000.
page_sizeintegerRows per page. Default 25, max 50 (200 on admin accounts).
cursortextnext_cursor from the previous page. Not limited by the offset depth; overrides page.
curl "https://api.usedealerdata.com/functions/v1/api/v1/datasets/company_news?state=TX&page_size=50" \
  -H "Authorization: Bearer $DEALERDATA_KEY"
{ "dataset": "company_news", "loaded": false, "coming_soon": true, "total": 0, "page_size": 50, "rows": [], "next_cursor": null, "plan": "premium", "required": "premium", "note": "…" }

MCP tools

Add https://api.usedealerdata.com/functions/v1/mcp as a remote MCP server (streamable HTTP) with the same Bearer key. The tools take the same filters as the REST endpoints.

ToolWhat it does
search_contactsSame filters as GET /contacts. Free. Rows carry stock (their store's, or null) as GET /contacts.
search_dealersSame filters as GET /dealers. Free. Store rows carry stock ({new, used, as_of, source}, or null when the store is not unlocked or has no count) and, for an unlocked store with a live profile, socials ({network: url}; absent, never null, when none is known).
count_contactsExact count, same filters. Free.
count_dealersExact count, same filters. Free.
get_contactsContacts by id (up to 500). No credits; one search page per call, and each contact is a profile view.
get_dealersDealerships by id (up to 500). Each store not unlocked is a store view (60 a minute, 1,000 an hour; free plan 500 a day, trial 2,500), shared with the app; a read the window stops part way returns the rows that fit with truncated. No credits; one search page per call. Store rows carry stock ({new, used, as_of, source}, or null when the store is not unlocked or has no count) and, for an unlocked store with a live profile, socials ({network: url}; absent, never null, when none is known).
filter_valuesEvery valid filter value with counts, and unlock prices.
unlockSpend credits; same rules and the same two answers as POST /unlock, including return_rows and skipped (with ids: unconfirmed, the contact_email ids whose email sells only as contact_email_unconfirmed; with dealer_ids: contact_ids, skipped counts {already_unlocked, no_data, unconfirmed}, unconfirmed_ids and unconfirmed_reason, unknown_dealer_ids). Retry unconfirmed ids as contact_email_unconfirmed.
accountCredit balance and prices.
dataset_staffSame as GET /datasets/staff: people at a store by department and role (gm, managers). Plan: every plan.
dataset_stockSame as GET /datasets/stock: new and used vehicles in stock per store. Plan: every plan.
dataset_signalsSame as GET /datasets/signals: live buying signals per store (pro and scale). Plan: Pro and Scale (other plans: total only).
dataset_dealer_typeSame as GET /datasets/dealer_type: store type and dealer group. Plan: every plan.
dataset_oemSame as GET /datasets/oem: oem brands each store sells. Plan: every plan.
dataset_revenueSame as GET /datasets/revenue: revenue estimate per store (pro and scale). Plan: Pro and Scale.
dataset_headcount_by_departmentSame as GET /datasets/headcount_by_department: staff on file per department at each store (pro and scale). Plan: Pro and Scale (other plans: total only).
dataset_email_providerSame as GET /datasets/email_provider: email provider of each store (pro and scale). Plan: Pro and Scale (other plans: total only).
dataset_founded_yearSame as GET /datasets/founded_year: year each store was founded (pro and scale; coming soon). Plan: Pro and Scale. Coming soon: answers loaded: false, not a search page.
dataset_company_newsSame as GET /datasets/company_news: recent news about each store (pro and scale; coming soon). Plan: Pro and Scale. Coming soon: answers loaded: false, not a search page.

Limits

Standard accountAdmin account
Calls60 a minute, 600 an hour (scale: 120 a minute, 2,000 an hour; 20 a minute, 100 an hour during the 7-day trial)600 a minute, 20,000 an hour
Exports20 an hour100 an hour
Contacts read by id (GET /contacts/{id}, the ids filter, MCP get_contacts; profile views, shared with the app)60 a minute, 1,000 an hour600 a minute, 20,000 an hour
Stores not unlocked read by id (GET /dealers/{id}, the ids filter, MCP get_dealers; store views, shared with the app; unlocked stores do not count)60 a minute, 1,000 an hour (and 500 a day on the free plan, 2,500 during the 7-day trial)600 a minute, 20,000 an hour
Rows per page50200
Ids per unlock1001,000
Values per list filter (city, domain, brand, ids...)1,000 (more is refused, 400 bad_value)1,000
Search pages a dayStarter 300, growth 450, pro 600, scale 2,000 (100 during the 7-day trial; 300 on invite or promo credits with no purchase; 20 before a plan, trial or invite, or at 0 credits without a plan)20,000
Search pages a minute120600
MCP setup calls (initialize, tools/list, ping; refused as JSON-RPC -32000 rate_limited)120 a minute1,200 a minute
New emails and phones unlocked2,500 in 24 hours10,000 in 24 hours
Offset paging depth10,000 rows (next_cursor is not limited)10,000 rows
Rows per export call25,000 (then?cursor=)25,000 (then?cursor=)

Every response carries X-RateLimit-Limit and X-RateLimit-Remaining for the window closest to its limit (minute, hour, or export hour on exports); a 429 carries Retry-After in seconds. MCP replies that include a tools/call carry them too (a batch: the tightest of its calls).

Errors

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

StatusCodeMeaning
400unknown_filterA 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.
400bad_valueA value outside the allowed set (unknown role, state name instead of code, bad boolean). The message lists what is valid. Also a bad export cursor: GET /export/contacts or /export/dealers got a cursor that is not a next_cursor from an earlier reply, or a cursor together with offset. Nothing was charged; send next_cursor unchanged, or leave cursor out to start from the first row.
400bad_cursorBad dealer cursor: GET /dealers, POST /dealers/search, the store datasets (all but staff and signals) and the MCP search_dealers / get_dealers / store dataset tools got a cursor that is not the next_cursor of an earlier page (changed, made up, or an old id-only cursor), or the store it pointed at is no longer listed. Nothing was charged. Restart the walk without cursor; a store that changes rank mid-walk can come back, so dedupe on id. (A bad export cursor is bad_value.)
400kind_requiredUnlock sent without kind. The response lists the valid kinds in kinds.
400filter_lockedA filter your plan cannot use (moved, a signal filter; revenue; mail_provider: Pro and Scale). The message names the filter and the plan needed. Nothing was charged; remove the filter, or upgrade: the body carries upgrade_url (the Billing page, where the Pro pack and plan are sold during the beta; Scale is through our team). After the beta Pro is sold through our team, so an account created after it gets enquiry_url instead; an account from the beta, or holding a Pro discount, can still buy the Pro pack on Billing and keeps upgrade_url.
400bad_bodyA POST body that is not a JSON object (unparsable, empty, or an array). Send the filters or unlock fields as one JSON object.
400scope_requiredThe staff dataset was called without a narrowing filter (dealer_id, state, brand, group, type, role or department).
403plan_requiredA micro-dataset (revenue) or a filter (signals by signal_type or since, email_provider by mail_provider, staff by department) that needs Pro or Scale. The body names the plan needed and carries upgrade_url (the Billing page, where the Pro pack and plan are sold during the beta; Scale is through our team; after the beta Pro is sold through our team, so an account created after it gets enquiry_url instead, while an account from the beta or holding a Pro discount keeps upgrade_url for the Pro pack). Nothing was charged. Below Pro, the signals, headcount_by_department and email_provider datasets answer 200 with {teaser: true, total} and the same link only.
400too_many_idsMore ids in one unlock than the account may send (100, or 1,000 on admin accounts).
400too_many_contactsdealer_ids with a contact kind found more contacts to unlock than one call may charge (100, or 1,000 on admin accounts). Nothing was charged; send fewer dealer_ids.
401invalid_api_keyMissing, wrong or revoked key.
402insufficient_creditsNot enough credits for the unlock. Nothing was charged.
402trial_requiredThe account has not started its 7-day free trial: add a card in the app (50 credits, no charge today). Until then every call (search and count included) is refused before it runs, and nothing is counted or charged. MCP answers every request the same way (see MCP -32002). After a trial that ended or was cancelled with nothing paid, every call answers this too, with reason: trial_ended and upgrade_url (the Billing page's One-time tab, where the credit packs are): pick a plan or buy a credit pack. A trial cancelled while it still runs answers reason: trial_cancelled, with trial_end (when the trial ends in the app): resume it under Manage plan or buy a credit pack. A plan whose payment did not go through, or a trial that ended without a card, answers reason: payment_due with its status: update the card on the Billing page. An account that gets no free trial answers reason: trial_refused: its phone number or card already had a free trial, or it bought credits before its trial. With trial_end, its plan's first charge is ahead (the API turns on with that charge; if the plan is set to cancel, it ends on trial_end without one: resume it under Manage plan). Without trial_end, no plan's first charge is pending (no Checkout yet, or the plan was cancelled): a plan turns it on, charged when it starts. Adding a card does not help here; a credit pack lifts it now.
402credits_requiredThe account has 0 credits and no paid plan or purchase, so every API and MCP call is refused, search included. Nothing was charged. Add a card and pick a plan, or buy a credit pack, on the Billing page; upgrade_url opens its One-time tab, where the credit packs are (as for trial_credits_used and trial_refused; trial_cancelled and payment_due give the plain Billing page, for Manage plan and Update card). An active trial that has used its credits answers reason: trial_credits_used with trial_end: buy a credit pack now, or wait for the plan credits at the first charge. reason: trial_cancelled and payment_due can come here too, as for trial_required.
403account_on_holdA payment dispute (chargeback) is open or was lost on the account, so every API and MCP call is refused, search included, until it is resolved. Nothing is counted or charged. Contact us to resolve it.
404not_foundUnknown route or record id.
429rate_limitedPer-minute (60; scale 120; 20 during the 7-day trial), per-hour (600; scale 2,000; 100 during the trial), export (20 per hour) or profile view (contacts read by id: 60 a minute, 1,000 an hour) or store view (stores not unlocked read by id: 60 a minute, 1,000 an hour; 500 a day on the free plan, 2,500 during the trial) limit reached. A contacts or dealers page read by ids that overflows the profile or store view window is not refused: it answers 200 with the rows that fit and truncated: true, truncated_reason and ids_not_returned. Wait Retry-After seconds. A profile, store view or export window refusal says which window: limit is its number, window its unit (minute, hour or day) and the message what it counts and the wait. The body carries enquiry_url for higher limits; the store view day limit (free plan and trial only) carries upgrade_url (the Billing page's credit packs) instead, as any pack or plan removes it; a trial refused by its own per-minute or per-hour window gets tier: trial and upgrade_url (the Billing page) instead, as a credit pack lifts it at once to 60 / 600. Every plan and pack on the Billing page has 60 / 600, so a paid account refused by its per-minute or per-hour window gets tier: paid with enquiry_url: Scale (120 / 2,000) is sold through our team.
429daily_search_limitDaily search pages used up (on a paid plan or pack: starter 300, growth 450, pro 600, scale 2,000 a day; 300 a day on invite or promo credits with no purchase; 100 a day during the 7-day trial; 20 a day otherwise, and at 0 credits without a plan). Each search page, GET /contacts/{id}, GET /dealers/{id} and MCP get_contacts / get_dealers call counts as one (a get_ call is one page whatever the number of ids). Counts are not limited. The body carries tier (trial, free, no_credits, paid, promo or admin) and daily_limit; during the trial, on the free or invite allowance, or on a plan below pro it carries upgrade_url (the Billing page) instead of enquiry_url (scale is sold through our team, so at the pro cap and above it carries enquiry_url; after the beta pro is sold through our team too, so from the growth cap up it carries enquiry_url). During the trial a credit pack lifts it (a plan cannot be picked while the trial runs; the plan's daily pages start with its first charge); on the free allowance a credit pack or a plan does; on invite credits or a plan below pro a plan or pack with a bigger daily cap does (Growth or above on invite credits: a Starter purchase keeps the Starter cap; an invite-credit account whose 7-day trial is running, tier promo, gets the trial's answer: a Growth or bigger credit pack, upgrade_url the One-time tab).
429limit_exceeded (daily_unlock_limit)More than 2,500 new emails and phones unlocked in 24 hours. The body carries message, left (unlocks you can make now), retry_after (seconds until the same call fits) and enquiry_url.
403limit_exceeded (page_too_deep)Offset paging stops at row 10,000. Narrow the filters or page with next_cursor.
503limit_exceeded (cap_not_configured)A usage limit is not configured, so the request was refused for safety. Nothing was charged. Retry later.
500server_errorSomething went wrong on our side. The body carries request_id; quote it if you contact us. Retrying later is safe.
503service_unavailableThe API key could not be checked (a database hiccup on our side). This does not mean the key is wrong or revoked: keep it and retry after Retry-After seconds (5). MCP answers HTTP 503 with JSON-RPC error -32000 service_unavailable; error.data.retry_after carries the wait.
503rate_limiter_unavailableEvery call waits while the rate limiter is unreachable (limits fail closed). Retry after Retry-After seconds.
MCP -32000rate_limited (MCP setup calls)More than 120 MCP setup calls (initialize, tools/list, ping; 1,200 on admin accounts) in a minute. Answered as a JSON-RPC error with code -32000 and message rate_limited; error.data carries the same body as a refused tool call: error, window, limit, retry_after, message and enquiry_url. Wait retry_after seconds. Tool calls count in the call limits instead.
MCP -32002trial_required, credits_requiredThe account is locked, as REST 402 trial_required and credits_required. Every MCP request, initialize and tools/list included, answers HTTP 402 with a JSON-RPC error: code -32002, and message (like error.data.error) is the lock. trial_required: the account has not started its 7-day free trial (error.data.message says how to add a card), or a trial ended or was cancelled with nothing paid, when error.data.reason is trial_ended and error.data.upgrade_url is the Billing page's One-time tab (pick a plan or buy a credit pack). error.data.reason can also be trial_cancelled, trial_credits_used, payment_due or trial_refused (no free trial: its phone number or card already had a free trial, or it bought credits before its trial; the plan's first charge on error.data.trial_end turns it on, without it a plan turns it on, charged when it starts, or a credit pack now), as for REST 402. credits_required: 0 credits and no paid plan or purchase; error.data.upgrade_url is the Billing page's One-time tab, where the credit packs are. Key on both messages, not only trial_required. Nothing is counted or charged.
MCP -32002account_on_holdAs REST 403 account_on_hold: every MCP request, initialize and tools/list included, answers HTTP 403 with a JSON-RPC error whose message (like error.data.error) is account_on_hold.