
You need a reliable way to read the Dutch APK (MOT) expiry date for a license plate and wire it into dealer tools, marketplaces, fleet operations, or insurance workflows. By the end of this guide, you’ll query the Vehicle API RDW endpoint, parse apk_expires safely, handle errors and nulls, and design caching and reminder logic that won’t wake you up the night before a deadline.
What the RDW APK expiry field is and when to use it
apk_expires is the APK (Algemene Periodieke Keuring) inspection expiry date for a Dutch registration (kenteken). It’s essential for:
- Dealer and marketplace listings: display next APK due date to buyers.
- Fleet and insurance: schedule renewals, prevent compliance gaps, and price risk.
- AI agents: reason about time-to-expiry and trigger reminders.
This field is returned as part of the Dutch plate lookup that sources open data from the RDW. Dutch plates never include a VIN in RDW open data, so vin is always null in this response. Dashes in the plate are optional (K-875-HJ and K875HJ both work). Unknown plates return a 404 not_found.
Endpoint and authentication
Base URL: https://vehicles-api.com/api/v1
Endpoint: POST /plate/nl
Authentication: include your key as a header. Both of the following are accepted; use one:
- X-API-Key: YOUR_API_KEY
- Authorization: Bearer YOUR_API_KEY
All responses, including errors, use a consistent envelope: a top-level status plus either data and source on success, or error.code and error.message on failure. The source for this endpoint is plate_nl.
To start a 7-day free trial (Starter is $19/mo), sign up here: Register. You can explore more at vehicles-api.com and browse the reference Documentation.
Official cURL request
Copy and paste this request as is. It targets the Dutch plate lookup and returns apk_expires with other RDW attributes.
curl -X POST "https://vehicles-api.com/api/v1/plate/nl" -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{"plate":"K875HJ"}'
JavaScript example: fetch apk_expires and build a reminder window
This example uses fetch to call the same endpoint, extract apk_expires, and compute a 30-day reminder window. Adjust your reminder policy as needed.
async function lookupDutchPlate(plate) {
const url = "https://vehicles-api.com/api/v1/plate/nl";
const res = await fetch(url, {
method: "POST",
headers: {
"X-API-Key": "YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({ plate })
});
const payload = await res.json();
// Envelope handling
if (payload.status !== "success") {
const code = payload?.error?.code || "unknown_error";
const message = payload?.error?.message || "Unknown error";
throw new Error(`${code}: ${message}`);
}
const data = payload.data;
// RDW never publishes VIN for Dutch plates
// apk_expires is an ISO-like date string (YYYY-MM-DD)
const apkExpires = data.apk_expires; // e.g., "2027-01-04"
// Simple 30-day reminder window
let reminderStart = null;
if (apkExpires) {
const expires = new Date(apkExpires + "T00:00:00Z");
const msInDay = 24 * 60 * 60 * 1000;
reminderStart = new Date(expires.getTime() - 30 * msInDay).toISOString().slice(0, 10);
}
return {
source: payload.source, // "plate_nl"
plate: data.plate,
apk_expires: apkExpires,
reminder_start: reminderStart,
make: data.make,
model: data.model,
first_registration: data.first_registration,
open_recall: data.open_recall
};
}
// Example usage:
lookupDutchPlate("K875HJ")
.then(console.log)
.catch(console.error);
Official JSON response
This is the official example returned by the endpoint above. The apk_expires field is the focus of this guide.
{
"status": "success",
"source": "plate_nl",
"data": {
"plate": "K875HJ",
"vin": null,
"year": 2021,
"make": "TOYOTA",
"model": "AYGO",
"body": "hatchback",
"color": "GRIJS",
"doors": 4,
"seats": 4,
"vehicle_type": "Personenauto",
"engine": {
"cylinders": 3,
"displacement_cc": 998,
"fuel": "Benzine"
},
"mass_kg": 815,
"apk_expires": "2027-01-04",
"first_registration": "2021-01-04",
"open_recall": false
}
}
Fields you will actually use
Key fields for APK-related features:
- data.apk_expires: The APK expiry date as a date string in the example format YYYY-MM-DD. Use this for due date displays, filters, and reminder scheduling. Treat it as a calendar date without timezone math unless you convert to midnight UTC for internal comparisons.
- data.plate: The normalized plate you requested. Store this for caching and reconciliation, especially if users enter plates with dashes.
- data.first_registration: Registration date (YYYY-MM-DD). Useful for context near the expiry date and for lifecycle logic.
- data.make, data.model, data.year: Display labels and internal joins to catalog data.
- data.open_recall: Boolean indicating whether there is an open recall reported. Recalls are based on US-market safety data and are matched by make, model, and year. If you operate in the Netherlands, treat this as informational and disclose scope (US-market coverage).
Additional context fields (often shown in UI): body, color, doors, seats, vehicle_type, engine.cylinders, engine.displacement_cc, engine.fuel, mass_kg. Units are explicit in the keys (kilograms, cubic centimeters). RDW does not publish VINs for these records, so data.vin is always null.
Response envelope, errors, and unknown plates
Every response follows the same envelope:
- status: success or error
- source: plate_nl for this endpoint when status=success
- data: the object shown above when status=success
- error: present only when status=error, with error.code and error.message
Unknown or unlisted Dutch plates return a 404 not_found error. Handle this explicitly so your UI can display “plate not found” without retry loops. If you omit or pass an invalid key, the API will return an error with status=error (401/403 depending on context). If you exceed rate limits or quotas, the API will return an error indicating you’ve hit a limit (do not assume codes; inspect error.code and error.message).
Coverage notes and what this API is not
RDW plate data applies to Dutch registrations, and Dutch plates never include a VIN via RDW open data (vin is always null). APK expiry and related RDW attributes are provided as shown. This is not a vehicle history report; you will not receive title events, accident history, or ownership counts.
Beyond RDW, Vehicle API also provides:
- VIN decoding from NHTSA vPIC: works worldwide but with fullest detail for US-market vehicles.
- US safety coverage such as recalls, owner complaints, and NCAP ratings: US-market only, matched by make, model, and year.
If your application mixes APK data with US safety features, disclose scope clearly and avoid implying non-US coverage for safety items. For product navigation, see vehicles-api.com or the Documentation.
Caching, rate limits, and quotas
Plan limits and rate limits vary by subscription. Do not hardcode assumptions; read the headers and the error payload for limit signals, and consult the Documentation for current quotas.
Recommended caching approach for RDW APK expiry:
- Cache by normalized plate (strip dashes, uppercase). Store the entire data object together with the source and a retrieval timestamp.
- Treat apk_expires and first_registration as stable until out-of-band changes occur. Refresh on a schedule appropriate for your workflow (e.g., monthly or when a user opens a detail page), or when users report discrepancies.
- Design your cache to accept nulls: vin is always null from RDW. Do not backfill VINs from other sources into this RDW envelope; keep provenance clear.
- Avoid flooding the API with repeated requests for the same plate when rendering paginated inventory; prefetch and reuse cached responses server-side.
If your app includes search or bulk updates, consider batching your server-side lookups and using exponential backoff when you encounter transient errors. For mission-critical flows, log the full response envelope so you can diagnose with the exact error.code and error.message.
Building features on top of apk_expires
Dealer and marketplace UI
- Show “APK valid until 2027-01-04” directly from data.apk_expires.
- Add color, body, and seating directly from the same payload to avoid parallel sources.
- Make and model come normalized (e.g., TOYOTA AYGO), which you can map to your catalog as-is.
Fleet compliance
- Derive a reminder policy anchored to apk_expires. Many teams schedule reminders at 30 and 7 days before expiry; compute these dates in UTC and display them in local time.
- Display first_registration alongside expiry in dashboards to give operators lifecycle context.
Insurance pricing and underwriting
- Use proximity to apk_expires for internal rules (e.g., bind quote with condition to complete APK before start date).
- If you also surface open_recall, explain that safety data coverage is US-market only and matched by make, model, and year.
MCP server option for agents and tools
If you’re building AI agents, you can call the same data via the MCP server at MCP. The underlying data and fields are the same; your agent will still receive the envelope with status, source=plate_nl, and data including apk_expires.
Practical details and gotchas
- Date format: apk_expires and first_registration appear as YYYY-MM-DD in the official example. Parse as calendar dates and avoid timezone shifts when comparing at day precision.
- Normalization: Accept plates with or without dashes. Store the normalized plate returned in data.plate.
- Nulls: vin is always null for RDW plates. Write your serializers and database schema to allow nulls.
- Error handling: Branch on status. If status !== "success", read error.code and error.message; do not assume any single HTTP code implies a specific envelope.
- No vehicle history: This endpoint does not include title records, accidents, or ownership history.
- US-only safety data: open_recall refers to US-market safety data matched by make, model, and year, not by VIN, and is informational when used with RDW plates.
Python example: server-side lookup with clean error paths
If you prefer Python on the backend, this minimal Flask-friendly function shows envelope checks and safe parsing for apk_expires.
import requests
API_URL = "https://vehicles-api.com/api/v1/plate/nl"
API_KEY = "YOUR_API_KEY"
def get_apk_expiry_nl(plate: str) -> dict:
headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
}
payload = {"plate": plate}
r = requests.post(API_URL, json=payload, headers=headers, timeout=10)
try:
body = r.json()
except ValueError:
raise RuntimeError(f"unexpected_response: non-JSON payload, http={r.status_code}")
if body.get("status") != "success":
code = (body.get("error") or {}).get("code", "unknown_error")
msg = (body.get("error") or {}).get("message", "Unknown error")
raise RuntimeError(f"{code}: {msg}")
data = body["data"]
# apk_expires is a date string like '2027-01-04'
return {
"plate": data.get("plate"),
"apk_expires": data.get("apk_expires"),
"first_registration": data.get("first_registration"),
"make": data.get("make"),
"model": data.get("model"),
"year": data.get("year"),
"open_recall": data.get("open_recall"),
"source": body.get("source"),
}
# Example call:
# result = get_apk_expiry_nl("K875HJ")
# print(result)
Testing and rollout checklist
- Unit tests: mock the response envelope for both success and error. Include a 404 not_found case for unknown plates.
- Date logic: verify comparisons at day precision. Avoid off-by-one mistakes caused by timezone conversions.
- Resilience: implement retries with jitter only on network failures or 5xx; do not retry 404 not_found.
- Observability: log status, source, and error.code/error.message on failures. Redact your API key.
- Cache strategy: cache by normalized plate and refresh on an interval appropriate to your product needs. RDW vin remains null; don’t expect it later.
- Scope disclosure: if you surface open_recall next to RDW data, label it as US-market safety data matched by make, model, and year.
FAQ
Does RDW return a VIN for Dutch plates?
No. RDW open data never includes a VIN. The vin field in this endpoint is always null.
Is apk_expires always present?
It is included when available from RDW open data. Handle nulls defensively in case a record is incomplete.
How do I handle unknown plates?
Unknown plates return a 404 not_found with status=error in the envelope. Do not retry; prompt the user to verify the plate.
Are recalls and crash ratings Dutch-specific?
No. Safety data (recalls, owner complaints, NCAP ratings) are US-market only and are matched by make, model, and year, not by VIN in this RDW response.
What are the rate limits and quotas?
They depend on your plan. Inspect API responses for limit errors and check the Documentation for current quotas.
Ready to add APK expiry to your listings or compliance workflows? Start your 7-day free trial now: Register. You can also integrate via the MCP server for agents and internal tools. Explore more at vehicles-api.com.
