API Documentation

Everything you need to integrate with the DealerPricing API

Quick Start

1. Get your API key

Sign up for an account and create an API key from the Keys page.

2. Make your first request

curl -X POST https://www.dealerpricing.co.uk/api/v1/vehicle/decode \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"vrm": "SA22TLZ"}'

Every endpoint takes vrm (registration). The vehicle data endpoints - decode, spec, valuation and provenance - also accept vin (chassis number, 5 to 17 characters) in its place. Tax & MOT, the market endpoints and pricing insights need a registration; sending a VIN there returns a 400 with the endpoints that accept one. The rule is the same for sandbox and live keys.

3. Handle the response

{
  "success": true,
  "data": {
    "vehicle": {
      "vrm": "SA22TLZ",
      "make": "Volkswagen",
      "model": "Polo",
      ...
    }
  },
  "meta": {
    "request_id": "req_abc123",
    "cost_pence": 16,
    "balance_pence": 984,
    "is_sandbox": false
  }
}

Authentication

All API requests must include your API key in the X-API-Key header.

X-API-Key: dp_live_your_api_key_here
Sandbox Keys: Keys starting with dp_sandbox_ are free and answer only for the published test registrations below. Sandbox data may not be accurate and is for testing purposes only. Every sandbox response carries meta.sandbox: true and a notice saying so, and it must never be shown to end users or used to make decisions. Any other registration returns 404 SANDBOX_VRM_NOT_ALLOWED.

Sandbox registrations

The complete set a sandbox key can query, grouped by the scenario each one exercises. The same list is available as JSON from GET /api/v1/sandbox/vrms (no key required). Market endpoints return the market snapshot taken the first time a plate was queried in the sandbox.

Loading sandbox registrations…

Rate Limits

Package limits (per account)

Every live call counts towards your package's limits across all your live keys, and some endpoints also have a daily cap per account. A package change applies to the limits straight away.

LimitPay As You GoStarterProEnterprise
Per second251020
Per minute60100300600
Per day5002,0005,00015,000
Daily cap per endpoint
Provenance25100250500
Service history102550100
Valuation2005001,5005,000
Active listings2007502,0005,000
Sold listings2007502,0005,000
Market insights2007502,0005,000
Pricing insights2007502,0005,000
Specification3001,0003,00010,000
Vehicle decode3001,0003,00010,000

A call over a package limit is refused with 429 and the code PACKAGE_LIMIT_EXCEEDED; a call over an endpoint's daily cap with 429 and ENDPOINT_DAILY_CAP_REACHED. Both carry Retry-After (seconds), are shown in your request log and are never charged. Live responses carry X-Package-Limit-Day, X-Package-Remaining-Day and, for capped endpoints, X-Endpoint-Limit-Day and X-Endpoint-Remaining-Day.

Key limits (per key)

LimitLive keysSandbox keys
Per secondYour package's limit2 requests
Per minuteYour package's limit30 requests
Per day, per keyYour package's limit300 requests
Per day, all sandbox keys on the account-1,000 requests

A live key follows your package's limits, so one key can use the whole package, and moves with the package when you change it. You can set a key lower in Keys (never higher than the package); a call must be within both the key's and the package's limits. Key limit headers are included in every response: X-RateLimit-Remaining-*; a call over a key limit is refused with 429 and RATE_LIMIT_EXCEEDED. Daily limits reset at midnight UK time (Europe/London, GMT or BST); X-RateLimit-Reset-Day gives the seconds until then.

API Endpoints

Base URL: https://www.dealerpricing.co.uk/api

Vehicle Data

POST/v1/vehicle/decode
Get comprehensive vehicle details from VRM
POST/v1/vehicle/tax-mot
Current tax status and MOT history
POST/v1/vehicle/valuation
Trade, retail, and private valuations
POST/v1/vehicle/spec
Full vehicle specifications
POST/v1/vehicle/provenance
Finance, stolen, write-off checks
POST/v1/vehicle/service-history
Digital service records - asynchronous, see below
GET/v1/vehicle/service-history/{request_id}
Progress and result of a service history request (free)

Market Data

POST/v1/market/active
Similar vehicles currently for sale
POST/v1/market/sold
Similar vehicles recently sold
POST/v1/market/insights
Supply, demand, and pricing analytics

Analysis

POST/v1/pricing/insights
Comprehensive pricing analysis

Tax & MOT

Two government sources answer this endpoint: DVLA (tax status, MOT status and the vehicle details) and DVSA (MOT test history and its own record of the vehicle). data.sources shows what each one answered: found, not_found or unavailable.

  • DVLA holds no record for some vehicles, often classic and historic vehicles, or registrations that have been moved to another vehicle or put on retention. The vehicle is then identified from DVSA: vehicle.source is dvsa, the make, model, colour, fuel, engine size and first-registration date come from the MOT record, and tax and mot are omitted because DVLA is the only source for them.
  • No MOT tests can be normal: vehicles over 40 years old can be exempt from the MOT, a new vehicle has its first test at three years, and MOT records online go back to 2005. mot_history.total_tests is then 0.
  • If neither source identifies the vehicle you get 404 VEHICLE_NOT_FOUND and are not charged.
  • A call is charged at the endpoint price whenever either source returns data, even if the other could not be reached. For example, if DVLA is temporarily unavailable but DVSA knows the vehicle, you get the vehicle details and MOT history with sources.dvla set to unavailable and no tax block.
  • If neither source returns data and one of them could not be reached (an error or a timeout), you get 502 UPSTREAM_ERROR and are not charged, so you can retry.
{
  "vrm": "EBY776J",
  "sources": { "dvla": "not_found", "mot": "found" },
  "vehicle": {
    "make": "ASTON MARTIN",
    "model": "DB6",
    "colour": "BLUE",
    "year_of_manufacture": 1971,
    "first_registration_date": "1971-08-01",
    "engine_capacity": 4200,
    "fuel_type": "PETROL",
    "source": "dvsa"
  },
  "mot_history": { "total_tests": 20, "tests": [ ... ] }
}

Market data

/v1/market/active, /v1/market/sold, /v1/market/insights and /v1/pricing/insights look at cars like the one you send: live adverts from UK dealer websites, and adverts that have recently sold.

Request fields

  • vrm (required): the registration.
  • rows (active and sold listings): how many listings to return. Default 50, maximum 200.
  • match (active and sold listings, optional): which optional filters to use - see below.
  • price (pricing insights, optional): your asking price in pounds, to see where it sits in the market.

Which cars are compared

  • Always matched: make, model, year, trim (the trims that match the car) and number of doors.
  • Matched unless you say otherwise: gearbox (transmission) and body type (body_type). To turn one off, send match with the ones you want to keep, for example "match": "transmission" keeps the gearbox and drops body type. Without match, both apply.
  • Fuel type, engine size and drive are not matched yet. If you list them in match, the response names them in match_not_applied.
  • match_criteria in the response lists what was matched for your request.
  • The search is never widened for you. If only a few cars match, you get those few.

Listings and totals

  • Listings come cheapest first, and only adverts that show a price are included.
  • total_found is how many matching adverts there are, not how many were returned. When there are more than we fetch, it is worked out from the share of fetched adverts that matched, and total_estimated is true.
  • The insights count live supply the same way: supply.active_listings with supply.estimated, and market_overview.active_supply with active_supply_estimated.
  • Sold figures in the insights (sold in the last 90 days, days to sell, sold prices) cover every sold advert for the same make, model, trims and year. Price figures leave out adverts with no price.
  • The summary in sold listings covers only the cars in that response, which are the cheapest. For sold prices across the whole market, use the insights.
  • With price, pricing insights ranks your price against every matching advert with a price. compared_with says how many, and estimated is true when that number is estimated.
curl -X POST https://www.dealerpricing.co.uk/api/v1/market/active \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"vrm": "SA22TLZ", "rows": 100}'

{
  "success": true,
  "data": {
    "vrm": "SA22TLZ",
    "total_found": 87,
    "total_estimated": true,
    "listings_returned": 61,
    "match_criteria": "make,model,year,variant,transmission,body_type,doors",
    "listings": [
      { "price": 9495, "miles": 18250, "year": 2022, "variant": "Life Tsi",
        "transmission": "Manual", "days_on_market": 12, ... }
    ]
  },
  "meta": { "request_id": "...", "cost_pence": 15, "balance_pence": 985, "is_sandbox": false }
}

Charging

A registration our vehicle data cannot identify returns 404 VEHICLE_NOT_FOUND and is not charged. If one of our data sources fails you get 502 UPSTREAM_ERROR, also not charged, and you can retry.

What changed in October 2026

  • Listings responses could hold no more than 10 cars. They now return up to rows (maximum 200).
  • total_found and the supply figures were counts of returned cars. They are now market totals, so supply levels, the market rating and price positions change.
  • Gearbox matching now works: a manual car is no longer compared with automatics, or the other way round.
  • Adverts without a price are no longer listed first.
  • match_criteria used to repeat a default list; it now shows what was really matched.
  • A registration we cannot identify used to return an empty, charged answer. It is now a free 404.
  • Sandbox market data is taken again, the new way, the first time each sandbox registration is asked for.

Service history (asynchronous)

A digital service history comes from the vehicle's manufacturer, and that request is not answered instantly: most complete within a few minutes, around one in ten takes over two hours, and a request is abandoned after four days. So POST /v1/vehicle/service-history accepts the request and answers 202; you then either poll the status URL or give us a callback_url and we call you when it is done. Registration only - the provider has no VIN lookup. Once completed you receive the extracted records and, when the manufacturer supplied one, a link to the PDF (pdf_url).

Manufacturer coverage and charging

Digital service history is currently available for vehicles from the following manufacturers only: Audi, BMW, Cupra, Ford, Jaguar, Land Rover, Lexus, Mazda, Mercedes-Benz, MG, MINI, Nissan, Porsche, Renault, Rolls-Royce, SEAT, Skoda, Smart, Toyota and Volkswagen. Mercedes covers Mercedes-Benz and Mercedes-AMG; Volkswagen covers VW.

A search that finds no records is refunded in full. A vehicle from a manufacturer outside the list above, or a covered vehicle with no digital records, returns record_count: 0 and the price comes back automatically (refunded_pence,refund_reason: "no_records"). Searches that return records are charged. We do not check the make before submitting.

1. Request

curl -X POST https://www.dealerpricing.co.uk/api/v1/vehicle/service-history \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"vrm": "SA22TLZ", "callback_url": "https://example.com/hooks/dealerpricing"}'

HTTP/1.1 202 Accepted
{
  "success": true,
  "data": {
    "request_id": "6c1f4e2a-...",
    "vrm": "SA22TLZ",
    "status": "pending",
    "message": "Request accepted. The manufacturer is being asked ...",
    "requested_at": "2026-09-08T09:00:00.000Z",
    "check_after_seconds": 30,
    "status_url": "https://www.dealerpricing.co.uk/api/v1/vehicle/service-history/6c1f4e2a-...",
    "callback_url": "https://example.com/hooks/dealerpricing",
    "cost_pence": 200,
    "refunded_pence": 0
  },
  "meta": { "request_id": "...", "cost_pence": 200, "balance_pence": 1800, "is_sandbox": false }
}

Every request runs a fresh search of the manufacturer network, so you always get the latest records. If a search for that plate is already in progress we add your request to it rather than asking twice. Posting the same plate again while your request is open returns the open request (202) and is not charged, although your balance must still cover the price to make the call - polling belongs on the status endpoint below. As everywhere in this API, fields with no value (completed_at, error, service_history while pending) are omitted.

2. Poll the status (free)

curl https://www.dealerpricing.co.uk/api/v1/vehicle/service-history/6c1f4e2a-... \
  -H "X-API-Key: your_api_key_here"

{
  "success": true,
  "data": {
    "request_id": "6c1f4e2a-...",
    "vrm": "SA22TLZ",
    "status": "completed",
    "completed_at": "2026-09-08T09:04:12.000Z",
    "service_history": {
      "record_count": 2,
      "services": [
        { "date": "2024-03-11", "date_raw": "11/03/2024", "type": "Service / Repair",
          "mileage": 18420, "mileage_raw": "18,420 mi", "workshop": "OEM data",
          "notes": "Scheduled Service Completed 2 Years (20000 ml) ..." },
        { "date": "2022-03-04", "date_raw": "04/03/2022", "type": "Service / Repair",
          "mileage": 13, "mileage_raw": "13 mi", "workshop": "OEM data", "notes": "Pre-Delivery Inspection" }
      ],
      "retrieved_at": "2026-09-08T09:04:12.000Z"
    },
    "pdf_url": "https://www.dealerpricing.co.uk/api/service-history/pdf/9b2d...?t=...",
    "pdf_url_expires_in_seconds": 604800,
    "readable_until": "2026-09-15T09:04:12.000Z",
    ...
  }
}

status moves through pending (the manufacturer is being asked) → processing (records found, being extracted) → completed, or ends as failed. Wait at least check_after_seconds between polls. Status checks do not cost anything and do not count towards your balance: reading a request you have already paid for never runs a new search. A completed request includes pdf_url when the manufacturer supplied a PDF. The link opens only that vehicle's PDF and stops working after at most 7 days (pdf_url_expires_in_seconds); call the status endpoint again for a new link, free of charge. A finished request (completed or failed) can be read for 7 days after it finished, shown as readable_until. After that the status endpoint answers 410 REQUEST_EXPIRED, any PDF link stops working, and a new request (a new, charged search) is needed. Save the records and the PDF on your side if you need them for longer.

3. Or receive a callback

Pass an https callback_url and we POST the finished request to it once - the same JSON as the status endpoint, wrapped as below. Respond with any 2xx within 10 seconds; otherwise we retry after 1, 10 and 60 minutes. Callbacks are a notification you can trust only after confirming with the status endpoint using your API key, so never act on the body alone.

POST https://example.com/hooks/dealerpricing
X-DealerPricing-Event: service_history.completed
X-DealerPricing-Request-Id: 6c1f4e2a-...

{ "event": "service_history.completed", "request_id": "6c1f4e2a-...", "sent_at": "...", "data": { ...status response... } }

Charging

  • The endpoint price is taken when the request is accepted (cost_pence).
  • It is refunded in full when the search finds no records (record_count: 0, including manufacturers outside the coverage list) and when the request fails because of a fault (status failed: the provider or our pipeline could not complete it, or it was abandoned after four days). refunded_pence shows the refund. Searches that return records are charged.
  • A request is charged at the price in force when it is accepted; any refund gives back exactly that amount.
  • Each new request is a fresh search and is charged; status checks and new PDF links for a request you already made are free for 7 days after it finished.
  • Up to 25 requests may be in progress on an account at once (429 beyond that).

Sandbox

Sandbox keys go through the same lifecycle so you can build the polling or callback handling properly: 202 pending first, then completed about 20 seconds later with frozen records and, for plates that have one, a pdf_url to a sample manufacturer PDF so you can test the download. Sandbox requests are free. Only the plates listed under Service history available answer; other sandbox plates return 404.

Error Codes

CodeHTTP StatusDescription
INVALID_API_KEY401API key is invalid or not found
INSUFFICIENT_BALANCE402Account balance too low
RATE_LIMIT_EXCEEDED429Too many requests on this key
PACKAGE_LIMIT_EXCEEDED429Your package's limit across all live keys was reached (per second, minute or day)
ENDPOINT_DAILY_CAP_REACHED429Your package's daily cap for this endpoint was reached; it resets at midnight UK time
INVALID_VRM400VRM format is invalid
VEHICLE_NOT_FOUND404No data found for this VRM (or no request with that id)
INVALID_REQUEST400A parameter is not acceptable (e.g. a VIN on a registration-only endpoint, or a non-https callback_url)
REQUEST_EXPIRED410A service history request finished more than 7 days ago and can no longer be read - make a new request
UPSTREAM_ERROR502A data source did not respond - safe to retry

You only pay for successful lookups. A request that ends in a 404, 500 or 502 is not charged, and the error response carries the request_id and your current balance_pence.

On a monthly package your monthly fee comes back as plan credit, which pays for calls before your balance. While you have plan credit, responses also carry plan_credit_pence (what is left) and from_plan_credit_pence (how much of cost_pence it paid). A refund for a failed call goes back to whichever of the two it was paid from.