Read-only access to 43,000+ motorcycle specifications. Every request needs an API key - request one. Use of this API is governed by the API Terms, separate from the site's own Terms & Privacy.
https://www.motorcycledb.com/api/v1
Every endpoint below is relative to this - e.g. GET /bikes means GET https://www.motorcycledb.com/api/v1/bikes.
Send your key on every request, either as a Bearer token or in an X-Api-Key header:
Authorization: Bearer mdb_your_key_here X-Api-Key: mdb_your_key_here
Requests without a valid key return 401.
Paid plans have a monthly request limit that resets on your billing date each month; unused requests don't carry over. Trial keys have a fixed number of requests in total. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining, and paid keys also get X-RateLimit-Reset (UTC, when the next month starts). Once a Builder or Business plan's limit is reached, booster credits are used automatically and X-Booster-Credits-Remaining shows what is left.
Every call counts as one request, except a detail=full search, which counts one request per bike returned (a page of 50 complete bikes is 50 requests). A summary search counts as one request whatever its page size. If a detail=full page would cost more than you have left, it returns 429 insufficient_requests and only the call itself is counted (one request): lower per_page and try again.
An OpenAPI 3 description of every endpoint and field is at /api/v1/openapi.json (no key needed) - import it into Postman, Insomnia or a client generator.
Python (pip install requests):
import requests
r = requests.get(
"https://www.motorcycledb.com/api/v1/bikes",
headers={"X-Api-Key": "mdb_your_key_here"},
params={"q": "mt09", "year_min": 2021, "detail": "full"},
timeout=10,
)
r.raise_for_status()
for bike in r.json()["data"]:
print(bike["model"], bike["year"], bike["spec"]["performance"]["power_hp"], "hp")
JavaScript (browser or Node 18+; keep your key on the server):
const url = new URL("https://www.motorcycledb.com/api/v1/bikes");
url.search = new URLSearchParams({ make: "Ducati", category: "Naked", sort: "-year" });
const res = await fetch(url, { headers: { "X-Api-Key": process.env.MDB_API_KEY } });
if (!res.ok) throw new Error((await res.json()).error.message);
const { data, meta } = await res.json();
console.log(`${meta.total} bikes`, data.map(b => `${b.model} ${b.year}`));
Search/list bikes. Query parameters (all optional, combine freely):
| Parameter | Description |
|---|---|
q | Free-text make + model search. Ignores spaces and hyphens (mt09 finds MT-09); several words must all match (yamaha r1 finds YZF-R1). A model year, seat height (under 750mm seat), engine size (over 600cc) or a category on its own (naked 2024) in q fills that filter when you haven't set it; meta.interpreted shows what was taken out |
make | Manufacturer. An exact name (any case) if one exists, otherwise a partial match: Harley finds Harley-Davidson |
model | Model name, partial, ignoring spaces and hyphens |
year, year_min, year_max | Exact model year, or a range |
category | Category name, any case, e.g. Naked, Adventure - full list at /categories. An unknown name returns 400 with the valid values |
powertrain | ice or electric |
cc_min, cc_max | Engine capacity range, cc |
hp_min, hp_max | Power range, hp |
abs | true = ABS fitted as standard |
seat_height_max | Maximum seat height in mm |
seat_height_min | Minimum seat height in mm |
sort | id (default), name, year, capacity_cc, power_hp, seat_height_mm or popularity. Prefix - for descending, e.g. -popularity for most-viewed first. Bikes without the value sort last |
detail | summary (default, the fields below) or full: every result comes with its typed spec, description and dates, as in /bikes/{id} |
page | Default 1 |
per_page | Default 20, max 100 (max 50 with detail=full, each bike counting as one request) |
curl -H "X-Api-Key: mdb_your_key_here" \ "https://www.motorcycledb.com/api/v1/bikes?q=mt09&year=2024"
{
"data": [
{ "id": 66877, "make": "Yamaha", "model": "Yamaha MT-09 SP", "year": 2024,
"category": "Naked", "capacity_cc": 890, "power_hp": 119,
"seat_height_mm": 820, "url": "https://www.motorcycledb.com/yamaha/mt09sp/2024/",
"image_url": "https://www.motorcycledb.com/images5/Yamaha/Yamaha-MT-09-55451.webp",
"thumb_url": "https://www.motorcycledb.com/images5/Yamaha/Yamaha-MT-09-55451-thumbnail.webp" },
{ "id": 67350, "make": "Yamaha", "model": "Yamaha MT-09", "year": 2024,
"category": "Naked", "capacity_cc": 890, "power_hp": 117.4,
"seat_height_mm": 825, "url": "https://www.motorcycledb.com/yamaha/mt09/2024/",
"image_url": "https://www.motorcycledb.com/images/Yamaha/Yamaha-MT-09-33262.webp",
"thumb_url": "https://www.motorcycledb.com/images/Yamaha/Yamaha-MT-09-thumb-33262.webp" }
],
"meta": { "page": 1, "per_page": 20, "total": 2, "total_pages": 1 }
}
Everything on record for one bike:
spec - typed values (numbers and fixed codes), grouped into engine, performance, electric, drivetrain, chassis, dimensions, fuel and other. The unit is in each key name (torque_nm, wet_weight_kg); null means not on record. Every group and key is always present, so the shape never changes between bikes.image_url / thumb_url - the bike's main photo, and photos - up to 6 photos (url + thumb_url each), the same ones the bike page shows. Search results include image_url and thumb_url too (photos with detail=full). null / empty when the bike has no photo.description and notable_features - the bike page's summary text.updated_at - when this bike's specs last changed; verified_at - when they were last checked against a published source (null if not yet).character_profile - editorial "how it rides" scores and best-for guidance derived from the spec sheet.Fixed codes: powertrain ICE/Electric; engine_cycle 2/4 (stroke); cooling air/oil_air/liquid; transmission manual/automatic; abs_type yes/dual_channel/switchable/optional/unspecified.
curl -H "X-Api-Key: mdb_your_key_here" \ "https://www.motorcycledb.com/api/v1/bikes/61964"
{
"data": {
"id": 61964, "make": "Yamaha", "model": "Yamaha YZF-R1", "year": 2020,
"url": "https://www.motorcycledb.com/yamaha/yzf-r1/2020/",
"image_url": "https://www.motorcycledb.com/images/Yamaha/Yamaha-YZF-R1-33511.webp",
"thumb_url": "https://www.motorcycledb.com/images/Yamaha/Yamaha-YZF-R1-thumb-33511.webp",
"photos": [
{ "url": "https://www.motorcycledb.com/images/Yamaha/Yamaha-YZF-R1-33511.webp",
"thumb_url": "https://www.motorcycledb.com/images/Yamaha/Yamaha-YZF-R1-thumb-33511.webp" }, ...
],
"description": "The 2020 Yamaha YZF-R1 is a track-focused superbike ...",
"notable_features": null,
"spec": {
"category": "Sport", "category_2": null,
"engine": { "powertrain": "ICE", "capacity_cc": 998, "engine_config": "In-line", "cylinders": 4,
"engine_cycle": 4, "bore_mm": 79, "stroke_mm": 50.9, "compression_ratio": 13,
"valves_per_cyl": 4, "cooling": "liquid", "fuel_system": "injection", ... },
"performance": { "power_hp": 200, "power_kw": 146, "power_rpm": 13500,
"torque_nm": 112.4, "torque_rpm": 11500, "top_speed_kmh": 270, ... },
"electric": { "motor_kw": null, "battery_kwh": null, "range_km": null, ... },
"drivetrain": { "transmission": "manual", "gear_count": 6, "final_drive": "chain", ... },
"chassis": { "front_brake_mm": 320, "abs_type": "yes", "front_tyre": "120/70-ZR17",
"rake_deg": 24, "trail_mm": 102, ... },
"dimensions": { "seat_height_mm": 856, "wheelbase_mm": 1405, "wet_weight_kg": 203.2, ... },
"fuel": { "fuel_capacity_l": 17, "fuel_consumption_l100": 6.9, "co2_gkm": 161, ... },
"other": { "warranty_months": 12, "colour_options": "Yamaha Blue, Rapid Red" }
},
"updated_at": "2026-09-29", "verified_at": null,
"character_profile": {
"scores": { "speed": 100, "agility": 83, "comfort": 28, "offroad": 2, "touring": 22, "road": 72 },
"fit_for": { "track": 99, "city": 30, "cornering": 96, "daily": 28, "touring": 22, "passenger": 18 },
"best_for": "...", "built_for": "...", "not_ideal_for": "...",
"note": "Editorial estimate derived from the spec sheet using the same model for every bike - not lab tested."
}
}
}
Every category name /bikes?category= accepts, most common first, with bike counts.
{ "data": [ { "name": "Naked", "bike_count": 9227 }, { "name": "Scooter", "bike_count": 8703 }, ... ] }
Paginated manufacturer list. q filters by name (partial, ignoring spaces and hyphens) - the quick way to find a make's id.
curl -H "X-Api-Key: mdb_your_key_here" "https://www.motorcycledb.com/api/v1/makes?q=yamaha"
{
"data": [
{ "id": 4116, "name": "Yamaha", "model_count": 3602, "url": "https://www.motorcycledb.com/yamaha/" }
],
"meta": { "page": 1, "per_page": 20, "total": 1, "total_pages": 1 }
}
Models for one manufacturer - one entry per model, spanning all its production years.
curl -H "X-Api-Key: mdb_your_key_here" "https://www.motorcycledb.com/api/v1/makes/4116/models?per_page=1"
{
"data": [
{ "name": "Yamaha FZ1-S Fazer", "year_from": 2006, "year_to": 2016,
"model_years_on_record": 11, "url": "https://www.motorcycledb.com/yamaha/fz1s-fazer/" }
],
"meta": { "page": 1, "per_page": 1, "total": 850, "total_pages": 850 }
}
Safety recall lookup (US NHTSA campaigns). Query parameters: make (exact), model (partial), year, page, per_page.
curl -H "X-Api-Key: mdb_your_key_here" "https://www.motorcycledb.com/api/v1/recalls?make=Can-Am&per_page=1"
{
"data": [
{ "campaign_number": "26V575000", "component": "ELECTRICAL SYSTEM: INSTRUMENT CLUSTER/PANEL",
"summary": "...", "consequence": "...", "remedy": "...", "report_date": "2026-09-07",
"bike": { "id": 71815, "make": "Can-Am", "model": "Can-Am Spyder F3", "year": 2024,
"url": "https://www.motorcycledb.com/can-am/spyder-f3/2024/" } }
],
"meta": { "page": 1, "per_page": 1, "total": 60, "total_pages": 60 }
}
Decode a VIN and, when it matches something in our catalog, get the
direct link to it. Full 17-character VINs and partial VINs (at least
the first 8 characters) both work. Decoded entirely from locally-held
NHTSA vPIC data - coverage is US-market motorcycles only, and not
every decoded make/model has a matching catalog page yet (match
is null in that case, which is a normal outcome, not an error).
When there is a match, match holds its level, make,
model, exact_year_matched and url.
curl -H "X-Api-Key: mdb_your_key_here" \ "https://www.motorcycledb.com/api/v1/vin/1HD1KB4177Y653770"
{
"data": {
"vin": "1HD1KB4177Y653770", "make": "Harley-Davidson", "model": "Street Glide",
"model_year": 2007, "model_year_confident": true, "match": null
}
}
Every error has the same shape:
{ "error": { "code": "rate_limit_exceeded", "message": "Monthly limit of 25000 requests reached and no booster credits left. ..." } }
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_category, invalid_sort, invalid_detail, invalid_powertrain, invalid_abs, invalid_cc_min (etc.), missing_vin, invalid_vin | A parameter isn't valid - the message says what is accepted |
| 401 | missing_api_key, invalid_api_key | No key sent, or the key isn't recognised |
| 403 | revoked_api_key | The key has been revoked |
| 403 | trial_used | A trial key has used all its requests |
| 403 | no_active_plan | The key's plan has ended - choose a plan on your account page |
| 404 | not_found | No bike/make with that id, or a VIN that can't be decoded |
| 429 | rate_limit_exceeded | Over the plan's monthly limit with no booster credits left |
| 429 | insufficient_requests | A detail=full page would cost more requests than you have left (only the call itself was counted) |