# CarAPI.dev - Complete API Documentation > Comprehensive automotive data API providing VIN decoding, vehicle history, stolen vehicle checks, valuations, and more. Website: https://carapi.dev Documentation: https://docs.carapi.dev API Base URL: https://api.carapi.dev/v1/ --- ## Table of Contents 1. [Authentication](#authentication) 2. [Rate Limits & Pricing](#rate-limits--pricing) 3. [Referral Credit](#referral-credit) 4. [Response Format](#response-format) 5. [Market Coverage](#market-coverage) 6. [API Endpoints](#api-endpoints) - [VIN Decode](#vin-decode) - [Plate to VIN](#plate-to-vin) - [Stolen Vehicle Check](#stolen-vehicle-check) - [Vehicle Inspection](#vehicle-inspection) - [Vehicle Listing](#vehicle-listing) - [Vehicle Photos](#vehicle-photos) - [Vehicle Payments](#vehicle-payments) - [Vehicle Valuation](#vehicle-valuation) - [Mileage History](#mileage-history) - [VIN OCR (Image)](#vin-ocr-image) - [License Plate OCR (Image)](#license-plate-ocr-image) - [Recalls](#recalls) - [EV Charging Stations](#ev-charging-stations) - [Time to Sell](#time-to-sell) - [Cost of Ownership](#cost-of-ownership) - [EV Route Planner](#ev-route-planner) - [EV Vehicle Catalog](#ev-vehicle-catalog) 7. [Account & Usage Endpoints (Free — Not Billed)](#account--usage-endpoints-free--not-billed) - [Account](#account) - [Endpoints Catalog](#endpoints-catalog) 8. [Error Handling](#error-handling) 9. [Support](#support) --- ## Authentication All API requests require authentication via the `token` query parameter. ``` GET https://api.carapi.dev/v1/{endpoint}?token=YOUR_API_KEY ``` Get your API key at: https://carapi.dev/dashboard --- ## Rate Limits & Pricing Rate limits are based on your subscription plan: | Plan | Requests/Month | Price | |--------------|----------------|----------| | Free | 100 | $0 | | Starter | 5,000 | $29/mo | | Professional | 25,000 | $99/mo | | Business | 100,000 | $299/mo | --- ## Referral Credit Customers can lower their monthly CarAPI.dev bill by referring other developers via [carapi.dev/refer](https://carapi.dev/refer). Each customer has a unique referral link they can share. ### What you get - **€10 in account credit** per qualified referral, applied automatically to your next CarAPI.dev invoice. - **This is invoice credit, NOT a cash payout.** The €10 cannot be withdrawn, transferred, or paid out to a bank account or card. It is only redeemable against future CarAPI.dev invoices. ### What the referred friend gets - **20% off their first invoice** when they sign up via your referral link and subscribe to a paid plan. ### How it works 1. Share your referral link from [carapi.dev/refer](https://carapi.dev/refer). 2. Your friend signs up and pays their first invoice. 3. €10 is added to your Stripe account balance and comes off your next invoice automatically. ### Rules - **Qualification**: credit is granted when the referred user pays their first invoice. There is a 30-day clawback window — if the friend cancels or is refunded within 30 days of their first paid invoice, the credit is reversed. After 30 days the credit is permanent. - **Limit**: up to 10 successful referrals per rolling 12-month window. Additional referrals beyond this are queued for review. - **Self-referrals**: blocked automatically. - **Free plan**: free-tier users can earn credit. The credit is held on the account and applied automatically the moment the user upgrades to a paid plan. There is no cash payout — credit is only redeemable against CarAPI.dev invoices. ### Example If you successfully refer 3 paying developers, you earn €30 in invoice credit, which automatically reduces your next CarAPI.dev invoice by €30. --- ## Response Format ### Success Response ```json { "field": "value", "data": { ... } } ``` ### Error Response ```json { "error": "Human readable error message" } ``` --- ## Market Coverage CarAPI.dev has endpoint-specific country availability, strongest in Central/Eastern Europe. ### Global Endpoints (No Restrictions) - **VIN Decode**: Any valid 17-character VIN (post-1981) - **Mileage History, Vehicle Listing, Vehicle Photos, Vehicle Payments**: Global ### Vehicle Valuation (24 Countries) CZ, SK, DE, AT, CH, FR, PL, RO, HU, HR, PT, BG, SI, RS, NL, LT, BE, ES, IE, IT, UK, NO, SE, US ### Stolen Vehicle Check (5 Countries) SK, CZ, SI, HU, RO ### Plate to VIN (6 Countries) PL, NO, SK, SE, CZ, US (US requires a 2-letter state code) ### Vehicle Inspection - **Available**: Slovakia (SK) and Czechia (CZ) - STK/EK records - **Coming Soon**: Germany (DE), United Kingdom (UK) ### EV Charging Stations (European Roaming Network) ~54,000 physical charging locations across Europe, strongest in IT, DE, NL, AT, BE, FR, CZ, PL, HU, RO, SK. Refreshed monthly. ### EV Route Planner (Europe) Latitude 35 to 62, longitude -11 to 35, up to 2500 km per route. Both endpoints outside that box return 400. --- ## API Endpoints --- ### VIN Decode Decode a Vehicle Identification Number (VIN) to retrieve detailed vehicle specifications including make, model, year, engine details, and more. **Endpoint:** `GET /v1/vin-decode/{vin}` **Response Time:** ~150ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | vin | string | Yes | 17-character Vehicle Identification Number (URL path parameter) | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/vin-decode/WBAKU210X00R62021?token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/vin-decode/WBAKU210X00R62021?token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/vin-decode/WBAKU210X00R62021" params = { "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response (200) ```json { "vin": "WBAKU210X00R62021", "specifications": { "make": "BMW", "model": "X6", "fuel": "petrol", "transmission": "automatic", "enginePower": 225, "enginePowerUnit": "kW", "bodyStyle": "SUV", "drivetrain": "ALL_WHEEL_DRIVE", "color": "GRAY", "registrationDate": "2016-05-04T00:00:00.000Z" }, "manufacturer": { "name": "BMW AG", "region": "Europe", "country": "Germany" }, "features": [ { "name": "ABS", "category": "SAFETY_SYSTEM" }, { "name": "ESP", "category": "SAFETY_SYSTEM" }, { "name": "BLIND_SPOT_MONITOR", "category": "SAFETY_SYSTEM" }, { "name": "ADAPTIVE_CRUISE_CONTROL", "category": "ASSISTANCE_SYSTEM" }, { "name": "PARKING_CAMERA", "category": "ASSISTANCE_SYSTEM" }, { "name": "CENTRAL_LOCKING", "category": "VEHICLE_SECURITY" }, { "name": "LEATHER_UPHOLSTERY", "category": "INTERIOR_FEATURE" }, { "name": "SUNROOF", "category": "INTERIOR_FEATURE" } ], "plateNumber": null } ``` #### Response Fields | Field | Type | Description | |-----------------------------|--------------|-------------| | vin | string | The original VIN number provided | | specifications | object | Vehicle specifications object | | specifications.make | string | Vehicle manufacturer (e.g., Honda, Toyota) | | specifications.model | string | Vehicle model name (e.g., Civic, Camry) | | specifications.fuel | string | Primary fuel type (petrol, diesel, electric, hybrid) | | specifications.transmission | string | Transmission type (manual, automatic) | | specifications.enginePower | number\|null | Engine power in kilowatts (kW). Null when unknown. | | specifications.enginePowerUnit | string\|null | Unit for enginePower — always "kW" when present, otherwise null | | specifications.bodyStyle | string\|null | Body style. One of: HATCHBACK, CABRIOLET, COMBI, COUPE, MPV, PICK_UP, SEDAN, SUV, VAN, MOTORCYCLE, TRUCK, BUS, OTHER | | specifications.drivetrain | string\|null | Drivetrain. One of: FRONT_WHEEL_DRIVE, REAR_WHEEL_DRIVE, ALL_WHEEL_DRIVE, OTHER | | specifications.color | string\|null | Exterior color. One of: BEIGE, WHITE, BLACK, RED, PURPLE, BROWN, BLUE, ORANGE, PINK, SILVER, GRAY, BURGUNDY, GREEN, YELLOW, OTHER | | specifications.registrationDate | string | First registration date (ISO 8601 format) | | manufacturer | object\|null | Manufacturer details derived from the VIN World Manufacturer Identifier (first 3 characters) | | manufacturer.name | string\|null | Manufacturer name (e.g., Audi AG) | | manufacturer.region | string | Manufacturing region (e.g., Europe, North America) | | manufacturer.country | string | Manufacturing country (e.g., Germany) | | features | array | Array of vehicle features. Each item is an object with name and category. | | features[].name | string | Feature identifier (e.g., ABS, SUNROOF, ADAPTIVE_CRUISE_CONTROL) | | features[].category | string\|null | Feature category. One of: SAFETY_SYSTEM, ASSISTANCE_SYSTEM, VEHICLE_SECURITY, INTERIOR_FEATURE | | plateNumber | object\|null | Associated plate number information (if available) | | plateNumber.country | string | Country code of the plate | | plateNumber.plateNumber | string | License plate number | #### Error Response (400) ```json { "error": "Invalid VIN: VIN must be exactly 17 characters long (got 16)." } ``` #### Error Codes - **400** Bad Request: Invalid VIN format or missing required parameters - **403** Forbidden: Invalid or missing API token - **404** Not Found: VIN not found in database - **500** Internal Server Error: Server encountered an error processing the request #### Important Notes - VIN must be exactly 17 characters long and contain only alphanumeric characters (except I, O, and Q) - Response times are typically under 150ms with global CDN caching - Each successful request consumes 1 API credit from your monthly allowance - Data is sourced from official manufacturer databases and updated regularly --- ### Plate to VIN Convert a license plate number to its corresponding Vehicle Identification Number (VIN). **Endpoint:** `GET /v1/plate-to-vin/{plateNumber}` **Response Time:** ~200ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |-------------|--------|----------|-------------| | plateNumber | string | Yes | License plate number (URL path parameter) | | country | string | Yes | Country code (query parameter). Must be one of: PL, NO, SK, SE, CZ, US | | state | string | Conditional | 2-letter US state code (query parameter). Required when country=US (US plates are only unique per state); ignored for other countries | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/plate-to-vin/ABC123?country=SK&token=YOUR_API_KEY" # US lookups require a 2-letter state code: curl -X GET \ "https://api.carapi.dev/v1/plate-to-vin/7ABC123?country=US&state=CA&token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/plate-to-vin/ABC123?country=SK&token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); // US lookups require a state code const usResponse = await fetch('https://api.carapi.dev/v1/plate-to-vin/7ABC123?country=US&state=CA&token=YOUR_API_KEY'); console.log(await usResponse.json()); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/plate-to-vin/ABC123" params = { "country": "SK", "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) # US lookups require a state code us_response = requests.get( "https://api.carapi.dev/v1/plate-to-vin/7ABC123", params={"country": "US", "state": "CA", "token": "YOUR_API_KEY"}, ) print(us_response.json()) ``` #### Success Response (200) ```json { "plateNumber": "ABC123", "country": "SK", "vin": "1HGBH41JXMN109186" } ``` #### US Success Response (200) ```json { "plateNumber": "7ABC123", "country": "US", "state": "CA", "vin": "1HGBH41JXMN109186" } ``` #### Response Fields | Field | Type | Description | |-------------|---------------|-------------| | plateNumber | string | The original plate number searched | | country | string | Country code (e.g., SK, CZ, US) | | state | string | 2-letter US state code (uppercase). Present only for US lookups | | vin | string \| null | Vehicle Identification Number (null if not found) | #### Error Response (400) ```json { "error": "Country parameter is required" } ``` #### Error Codes - **400** Bad Request: Missing country parameter or invalid format - **403** Forbidden: Invalid or missing API token - **500** Internal Server Error: Server encountered an error processing the request #### Supported Countries - Poland (PL) - Norway (NO) - Slovakia (SK) - Sweden (SE) - Czech Republic (CZ) #### Important Notes - Returns a single VIN number for the given license plate - Returns null if no VIN is found for the plate number - Each successful request consumes 1 API credit from your monthly allowance - Response data is cached for improved performance --- ### Stolen Vehicle Check Check if a vehicle has been reported as stolen in multiple European countries. **Endpoint:** `GET /v1/stolen-check/{vin}` **Response Time:** ~300ms | **Cost:** 2 credits #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | vin | string | Yes | 17-character Vehicle Identification Number | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/stolen-check/1HGBH41JXMN109186?token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/stolen-check/1HGBH41JXMN109186?token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/stolen-check/1HGBH41JXMN109186" params = { "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Clean Vehicle Response (200) ```json { "vin": "1HGBH41JXMN109186", "stolen": false, "countries": { "sk": false, "cz": false, "si": false, "hu": false, "ro": false } } ``` #### Stolen Vehicle Response (200) ```json { "vin": "WVWZZZ1JZ3D123456", "stolen": true, "countries": { "sk": true, "cz": false, "si": false, "hu": false, "ro": false } } ``` #### Response Fields | Field | Type | Description | |--------------|---------|-------------| | vin | string | The VIN number that was checked | | stolen | boolean | Overall stolen status (true if stolen in any country) | | countries | object | Stolen status breakdown by country | | countries.sk | boolean | Stolen status in Slovakia | | countries.cz | boolean | Stolen status in Czech Republic | | countries.si | boolean | Stolen status in Slovenia | | countries.hu | boolean | Stolen status in Hungary | | countries.ro | boolean | Stolen status in Romania | #### Error Response (400) ```json { "error": "Invalid VIN: VIN must be exactly 17 characters long (got 16)." } ``` #### Error Codes - **400** Bad Request: Invalid VIN format or missing required parameters - **403** Forbidden: Invalid or missing API token - **500** Internal Server Error: Server encountered an error processing the request #### Supported Countries - Slovakia (SK) - Czech Republic (CZ) - Slovenia (SI) - Hungary (HU) - Romania (RO) #### Important Notes - VIN must be exactly 17 characters long and contain only alphanumeric characters (except I, O, and Q) - The overall "stolen" field is true if the vehicle is reported stolen in any supported country - Each successful request consumes 2 API credits due to security database access - Data is sourced from official law enforcement databases and updated in real-time --- ### Vehicle Inspection Retrieve vehicle inspection data including technical inspection (STK) and emissions test (EK) validity dates. **Endpoint:** `GET /v1/inspection/{vin}` **Response Time:** Cached lookups are fast; cold lookups may take up to 65s | **Cost:** 1 credit **Coverage:** Slovakia (SK) and Czechia (CZ). #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | vin | string | Yes | 17-character Vehicle Identification Number | | country | string | Yes | Inspection country code: `SK` or `CZ` | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/inspection/1HGBH41JXMN109186?country=SK&token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/inspection/1HGBH41JXMN109186?country=SK&token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/inspection/1HGBH41JXMN109186" params = { "country": "SK", "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response (200) ```json { "vin": "1HGBH41JXMN109186", "country": "SK", "inspection": { "stkValidTo": "2024-12-15", "ekValidTo": "2025-06-20" } } ``` #### Response Fields | Field | Type | Description | |---------------------|--------|-------------| | vin | string | The VIN number that was checked | | country | string | Country code for the inspection system | | inspection | object | Inspection data object | | inspection.stkValidTo | string | STK (technical inspection) valid until date (`YYYY-MM-DD`) | | inspection.ekValidTo | string | EK (emissions inspection) valid until date (`YYYY-MM-DD`) | #### Error Response (400) ```json { "error": "Invalid country code. Supported countries: SK, CZ" } ``` #### Error Response (404) ```json { "error": "Vehicle inspection data not found" } ``` #### Error Codes - **400** Bad Request: Invalid VIN format, missing country parameter, or unsupported country - **403** Forbidden: Invalid or missing API token - **404** Not Found: Vehicle inspection data not found - **500** Internal Server Error: Server encountered an error processing the request - **502** Bad Gateway: Upstream inspection provider failed or returned an unexpected response - **503** Service Unavailable: Upstream inspection service unreachable or timed out — a cold lookup scrapes the national register, so retrying is worthwhile - **504** Gateway Timeout: Upstream inspection provider returned a gateway timeout #### Coverage **Currently Supported:** - Slovakia (SK) - STK & EK inspections - Czechia (CZ) - STK & EK inspections **Coming Soon:** - Germany (DE) - TÜV & AU - United Kingdom (UK) - MOT #### Important Notes - VIN must be exactly 17 characters long and contain only alphanumeric characters (except I, O, and Q) - STK = Stanica technickej kontroly (SK) / Stanice technické kontroly (CZ) - EK = Emisná kontrola (SK) / Emisní kontrola (CZ) - Each successful request consumes 1 API credit from your monthly allowance - Response data is cached for improved performance --- ### Vehicle Listing Search current vehicle listings on the market with filtering and pagination. **Endpoint:** `GET /v1/listing` **Response Time:** ~500ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | make | string | No | Filter by vehicle manufacturer (partial match) | | model | string | No | Filter by vehicle model (partial match) | | year | number | No | Filter by registration year (exact match) | | limit | number | No | Number of results per page (default: 10, max: 50) | | offset | number | No | Starting position for pagination (default: 0, max: 10000) | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/listing?make=Honda&limit=5&token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/listing?make=Honda&limit=5&token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/listing" params = { "make": "Honda", "limit": "5", "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response (200) ```json { "listings": [ { "vin": "19XFL1H76RE001117", "specifications": { "make": "Honda", "model": "Civic", "fuel": "petrol", "transmission": "automatic", "registrationDate": "2024-01-01T00:00:00.000Z" }, "availability": { "imagesCount": 0, "plateNumbersCount": 1, "historyItemsCount": 1 } } ], "pagination": { "limit": 5, "offset": 0 } } ``` #### Response Fields | Field | Type | Description | |----------------------------------------|-------------|-------------| | listings | array | Array of vehicle listing objects | | listings[].vin | string | Vehicle Identification Number | | listings[].specifications | object | Vehicle specifications | | listings[].specifications.make | string | Vehicle manufacturer | | listings[].specifications.model | string | Vehicle model | | listings[].specifications.fuel | string\|null | Fuel type (e.g., "petrol", "diesel", "electric") | | listings[].specifications.transmission | string\|null | Transmission type (e.g., "manual", "automatic") | | listings[].specifications.registrationDate | string | Registration date (ISO format) | | listings[].availability | object | Data availability counts | | listings[].availability.imagesCount | number | Number of available images | | listings[].availability.plateNumbersCount | number | Number of plate number records | | listings[].availability.historyItemsCount | number | Number of history records | | pagination | object | Pagination information | | pagination.limit | number | Number of records per page | | pagination.offset | number | Starting offset for current page | #### Error Codes - **403** Forbidden: Invalid or missing API token - **500** Internal Server Error: Server encountered an error processing the request #### Supported Makes (40 total) | Make | API Value | Popular Models | |------|-----------|----------------| | Acura | acura | MDX, RDX, TLX, Integra | | Alfa Romeo | alfa-romeo | Stelvio, Giulia, Giulietta | | Audi | audi | A4, A3, A6, Q5, Q3, A5, Q7 | | BMW | bmw | 3-Series, 5-Series, X3, X5, X1, 1-Series | | Cadillac | cadillac | Escalade, XT5, XT4, CT5 | | Chevrolet | chevrolet | Silverado, Equinox, Malibu, Tahoe | | Citroën | citroen | C3, C4, Berlingo, C5 Aircross | | Cupra | cupra | Formentor, Leon, Born, Ateca | | Dacia | dacia | Duster, Sandero, Jogger, Logan | | Dodge | dodge | Durango, Charger, Challenger | | Ferrari | ferrari | Roma, 296 GTB, SF90, Purosangue | | Fiat | fiat | 500, Panda, Tipo, 500X, Doblo | | Ford | ford | Focus, F-Series, Kuga, Fiesta, Mustang | | GMC | gmc | Sierra, Yukon, Terrain, Acadia | | Honda | honda | Civic, CR-V, Accord, HR-V, Jazz | | Hyundai | hyundai | Tucson, i30, Kona, Santa Fe, i20 | | Jaguar | jaguar | F-Pace, XF, XE, I-Pace | | Jeep | jeep | Grand Cherokee, Wrangler, Compass | | Kia | kia | Sportage, Ceed, Sorento, Niro, Soul | | Land Rover | land-rover | Range Rover, Defender, Discovery | | Lexus | lexus | RX, NX, ES, IS, UX, GX | | Mazda | mazda | CX-5, 3, 6, CX-30, CX-3, MX-5 | | Mercedes-Benz | mercedes-benz | C-Class, E-Class, GLC, A-Class | | MG | mg | ZS, HS, MG4, MG3, MG5 | | MINI | mini | Countryman, Cooper, Clubman | | Mitsubishi | mitsubishi | Outlander, ASX, Eclipse Cross | | Nissan | nissan | Qashqai, Juke, Leaf, X-Trail | | Opel | opel | Astra, Corsa, Mokka, Insignia | | Peugeot | peugeot | 3008, 2008, 208, 308, 5008 | | Porsche | porsche | 911, Cayenne, Macan, Panamera | | Ram | ram | 1500, 2500, 3500 | | Renault | renault | Clio, Megane, Captur, Scenic | | SEAT | seat | Leon, Ibiza, Arona, Ateca | | Škoda | skoda | Octavia, Fabia, Superb, Kodiaq, Karoq | | Subaru | subaru | Forester, Outback, XV, Impreza | | Suzuki | suzuki | Vitara, Swift, SX4, Jimny | | Tesla | tesla | Model 3, Model Y, Model S, Model X | | Toyota | toyota | Corolla, RAV4, Yaris, Camry, C-HR | | Volvo | volvo | XC60, XC40, XC90, V60, V90 | | VW | vw | Golf, Tiguan, Passat, Polo, T-Roc, ID.4 | #### Important Notes - Results are ordered by creation date (most recent first) - Make and model filters support partial matching (case-insensitive) - Default limit is 10 records, maximum is 50 per request - Use pagination (limit/offset) for large result sets - Each successful request consumes 1 API credit from your monthly allowance - Response data is cached for improved performance --- ### Vehicle Photos Retrieve available photos for a specific vehicle including exterior, interior, and detail shots. **Endpoint:** `GET /v1/photos/{vin}` **Response Time:** ~200ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | vin | string | Yes | 17-character Vehicle Identification Number | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/photos/1HGBH41JXMN109186?token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/photos/1HGBH41JXMN109186?token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/photos/1HGBH41JXMN109186" params = { "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response with Images (200) ```json { "vin": "1HGBH41JXMN109186", "photos": [ "https://images.carapi.dev/vehicles/1HGBH41JXMN109186/front.jpg", "https://images.carapi.dev/vehicles/1HGBH41JXMN109186/rear.jpg", "https://images.carapi.dev/vehicles/1HGBH41JXMN109186/side_left.jpg", "https://images.carapi.dev/vehicles/1HGBH41JXMN109186/side_right.jpg", "https://images.carapi.dev/vehicles/1HGBH41JXMN109186/interior.jpg" ] } ``` #### Success Response - No Images (200) ```json { "vin": "1HGBH41JXMN109186", "photos": [] } ``` #### Response Fields | Field | Type | Description | |--------|----------|-------------| | vin | string | The VIN number that was queried | | photos | string[] | Array of direct URLs to vehicle images | #### Error Response (400) ```json { "error": "Invalid VIN: VIN must be exactly 17 characters long (got 16)." } ``` #### Error Codes - **400** Bad Request: Invalid VIN format or missing required parameters - **403** Forbidden: Invalid or missing API token - **500** Internal Server Error: Server encountered an error processing the request #### Common Image Types **Exterior Views:** - Front view - Rear view - Side views (left/right) - Angle views **Interior Views:** - Dashboard - Seats - Trunk/cargo area - Engine bay #### Important Notes - VIN must be exactly 17 characters long and contain only alphanumeric characters (except I, O, and Q) - Images are ordered by creation date (most recent first) - Image URLs are direct links to high-quality images - Not all vehicles have photos available - check photos array length - Each successful request consumes 1 API credit from your monthly allowance - Response data is cached for improved performance --- ### Vehicle Payments Calculate monthly payment schedules for vehicle loans based on price, down payment, loan term, and interest rate. **Endpoint:** `GET /v1/payments/{vin}` **Response Time:** ~250ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |--------------|--------|----------|-------------| | vin | string | Yes | 17-character Vehicle Identification Number | | price | number | Yes | Vehicle price (> 0) | | downPayment | number | Yes | Down payment amount (>= 0, must be lower than price) | | loanTerm | number | Yes | Loan term in months (integer, 1-600) | | interestRate | number | Yes | Annual interest rate as percentage (0-100) | | currency | string | No | Target currency (default: EUR) | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/payments/JHMZE2H79AS019110?price=25000&downPayment=5000&loanTerm=60&interestRate=4.5&token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/payments/JHMZE2H79AS019110?price=25000&downPayment=5000&loanTerm=60&interestRate=4.5&token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/payments/JHMZE2H79AS019110" params = { "price": "25000", "downPayment": "5000", "loanTerm": "60", "interestRate": "4.5", "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response (200) ```json { "vin": "JHMZE2H79AS019110", "payments": [ { "amount": 5000, "currency": "EUR", "frequency": "one-time", "type": "down-payment", "description": "Initial down payment", "dueDate": "2025-09-12" }, { "amount": 372, "currency": "EUR", "frequency": "monthly", "type": "loan", "description": "Monthly loan payment 1/60", "dueDate": "2025-10-15" } ], "loanAmount": 20000, "totalPaid": 27320, "totalInterest": 2320, "monthlyPayment": 372, "currency": "EUR" } ``` #### Response Fields | Field | Type | Description | |-------------------------|--------|-------------| | vin | string | Vehicle Identification Number | | payments | array | Array of payment objects with detailed payment schedule | | payments[].amount | number | Payment amount | | payments[].currency | string | Payment currency | | payments[].frequency | string | Payment frequency (one-time, monthly, etc.) | | payments[].type | string | Payment type (down-payment, loan) | | payments[].description | string | Human-readable payment description | | payments[].dueDate | string | Payment due date (YYYY-MM-DD format) | | loanAmount | number | Total amount financed (price - down payment) | | totalPaid | number | Total amount paid over the full loan period | | totalInterest | number | Total interest paid over the loan period | | monthlyPayment | number | Monthly payment amount | | currency | string | Currency for all monetary values | #### Payment Calculation Formula ``` Monthly Payment = L × [r(1+r)^n] / [(1+r)^n - 1] Where: - L = Loan amount (vehicle price - down payment) - r = Monthly interest rate (annual rate / 12) - n = Number of monthly payments (loan term) ``` **Special cases:** - If interest rate = 0%, monthly payment = loan amount / loan term - If down payment >= vehicle price, no financing needed #### Error Response (400) ```json { "error": "Missing or invalid required parameter: price" } ``` #### Error Response (404) ```json { "error": "Vehicle not found" } ``` #### Error Codes - **400** Bad Request: Invalid VIN format, missing required parameters, or invalid parameter values - **403** Forbidden: Invalid or missing API token - **404** Not Found: Vehicle not found or no current price available - **500** Internal Server Error: Server encountered an error processing the request #### Supported Currencies - EUR (Euro) - GBP (British Pound) - CZK (Czech Koruna) - PLN (Polish Zloty) - HUF (Hungarian Forint) - + More via API #### Important Notes - VIN must be exactly 17 characters long and contain only alphanumeric characters (except I, O, and Q) - Returns a detailed payment schedule with all individual payments and due dates - Includes one down payment (due immediately) plus all monthly loan payments - All monetary values are rounded to 2 decimal places - Due dates are calculated starting from today (down payment) and 15th of each month (loan payments) - Each successful request consumes 1 API credit from your monthly allowance - Response data is cached for improved performance --- ### Vehicle Valuation Get current market valuation for a vehicle by make, model, year, country, and optional drivetrain / mileage filters. Returns 404 when no valuation is available for the supplied configuration. **Endpoint:** `GET /v1/vehicle-valuation` **Response Time:** ~300ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |-----------|---------|----------|-------------| | make | string | Yes | Vehicle manufacturer, case-insensitive (e.g., "bmw", "toyota", "volkswagen") | | model | string | Yes | Vehicle model, case-insensitive (e.g., "x6", "camry", "golf") | | year | integer | Yes | Model year (1900 — current year + 1) | | country | string | Yes | ISO 3166-1 alpha-2 country code for regional pricing (see supported countries) | | token | string | Yes | API authentication token | | fuel | string | No | Fuel type filter (case-insensitive). One of: `petrol`, `diesel`, `electric`, `hybrid`, `lpg`, `cng`, `hydrogen` | | kw | integer | No | Engine power in kW (0 — 2000). Narrows the valuation to a specific drivetrain variant | | mileage | integer | No | Current vehicle mileage in km (0 — 1,000,000). Refines the valuation for the supplied make/model/year/country | #### Model naming Valuations are keyed per canonical base model. For the best hit rate, send the canonical names: `vw` (not `volkswagen`), `mercedes-benz` (not `mercedes`), hyphenated BMW series (`3-series`, not `320d` or `3 series`), Mercedes classes (`s-class`, not `s560`), and base models without generation suffixes (`golf`, not `golf 7`). Trim-level valuations are not available — valuations always cover the base model. - A make outside the 40 supported makes returns a **400** — with a "did you mean" suggestion when a close match exists (e.g. `audo` → `Did you mean "audi"?`). - A model year that was never produced (e.g. Ford Fusion 2024 — production ended in 2020) returns a **404** with an explanatory message instead of a bare not-found. - A model queried in a market where it is not sold (e.g. Honda Prologue in FR) returns a **404** explaining the market restriction. #### Request Example (cURL) — basic ```bash curl -X GET \ "https://api.carapi.dev/v1/vehicle-valuation?make=bmw&model=x6&year=2019&country=CZ&token=YOUR_API_KEY" ``` #### Request Example (cURL) — narrow by fuel, power band, and mileage ```bash curl -X GET \ "https://api.carapi.dev/v1/vehicle-valuation?make=bmw&model=x3&year=2018&country=SK&fuel=diesel&kw=110&mileage=80000&token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const params = new URLSearchParams({ make: 'bmw', model: 'x6', year: '2019', country: 'CZ', // Optional drivetrain filters fuel: 'diesel', kw: '110', mileage: '80000', token: 'YOUR_API_KEY' }); const response = await fetch(`https://api.carapi.dev/v1/vehicle-valuation?${params}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/vehicle-valuation" params = { "make": "bmw", "model": "x6", "year": 2019, "country": "CZ", # Optional drivetrain filters "fuel": "diesel", "kw": 110, "mileage": 80000, "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response (200) — basic ```json { "make": "bmw", "model": "x6", "year": 2019, "valuationPrice": 45393, "currency": "EUR", "country": "CZ" } ``` #### Success Response (200) — with fuel/kW/mileage filters When `fuel`, `kw`, or `mileage` are supplied, the applied filters are echoed back on the response. ```json { "make": "bmw", "model": "x3", "year": 2018, "valuationPrice": 24500, "currency": "EUR", "country": "SK", "fuel": "diesel", "kw": 110, "mileage": 80000 } ``` #### Success Response (200) — US market (returned in USD) US valuations are priced from US market listings and returned in USD; all other markets are returned in EUR. ```json { "make": "ford", "model": "fusion", "year": 2020, "valuationPrice": 13019, "currency": "USD", "country": "US" } ``` #### Response Fields | Field | Type | Description | |----------------|--------|-------------| | make | string | Vehicle manufacturer (lowercase) | | model | string | Vehicle model (lowercase) | | year | number | Model year | | valuationPrice | number | Current market valuation price | | currency | string | Currency of the valuation price (USD for US, EUR for all other markets) | | country | string | Country code used for regional pricing | | fuel | string | Fuel filter applied (only present when supplied in the request) | | kw | number | Engine power filter applied in kW (only present when supplied) | | mileage | number | Mileage filter applied in km (only present when supplied in the request) | #### Error Response (404) ```json { "error": "Vehicle valuation not found for the specified make, model, and year" } ``` #### Error Codes - **400** Bad Request: Missing required parameters or invalid filter values. Specific messages include: - `Make parameter is required` - `Model parameter is required` - `Year parameter is required and must be a valid number` - `Year must be between 1900 and {currentYear + 1}` - `Country parameter is required` - `Invalid country code. Supported countries: ...` - `Unsupported fuel type: X. Supported fuel types: petrol, diesel, electric, hybrid, lpg, cng, hydrogen` - `Parameter kw must be an integer between 0 and 2000` - `Parameter mileage must be an integer between 0 and 1000000` - `Unsupported make "audo". Did you mean "audi"?` - `Unsupported make "zzz". Supported makes: acura, alfa-romeo, ...` - **403** Forbidden: Invalid or missing API token - **404** Not Found: No valuation is available for the supplied configuration. Explanatory variants: - `The ford fusion was not produced in 2024 — production ran 2002–2020.` - `The cadillac ct4 was not produced in 2019 — production started in 2020.` - `The honda prologue is not sold in FR. It is sold only in North America.` - **500** Internal Server Error: Server encountered an error processing the request - **503** Service Unavailable: External valuation service is temporarily unavailable #### Recommended client-side strategy If you want the tightest valuation possible but with a safe fallback, query in order of decreasing specificity and stop on the first 200: 1. `make + model + year + country + fuel + kw + mileage` 2. `make + model + year + country + fuel` 3. `make + model + year + country` Each step relaxes one constraint and trades accuracy for sample size. #### Caching Response data is cached for improved performance. #### Supported Countries (24) | Code | Country | Code | Country | |------|----------------|------|----------------| | CZ | Czech Republic | NL | Netherlands | | SK | Slovakia | LT | Lithuania | | DE | Germany | BE | Belgium | | AT | Austria | ES | Spain | | CH | Switzerland | IE | Ireland | | FR | France | IT | Italy | | PL | Poland | UK | United Kingdom | | RO | Romania | NO | Norway | | HU | Hungary | SE | Sweden | | HR | Croatia | US | United States | | PT | Portugal | BG | Bulgaria | | SI | Slovenia | RS | Serbia | #### Supported Makes (40 total) | Make | API Value | Popular Models | |------|-----------|----------------| | Acura | acura | MDX, RDX, TLX, Integra | | Alfa Romeo | alfa-romeo | Stelvio, Giulia, Giulietta | | Audi | audi | A4, A3, A6, Q5, Q3, A5, Q7 | | BMW | bmw | 3-Series, 5-Series, X3, X5, X1, 1-Series | | Cadillac | cadillac | Escalade, XT5, XT4, CT5 | | Chevrolet | chevrolet | Silverado, Equinox, Malibu, Tahoe | | Citroën | citroen | C3, C4, Berlingo, C5 Aircross | | Cupra | cupra | Formentor, Leon, Born, Ateca | | Dacia | dacia | Duster, Sandero, Jogger, Logan | | Dodge | dodge | Durango, Charger, Challenger | | Ferrari | ferrari | Roma, 296 GTB, SF90, Purosangue | | Fiat | fiat | 500, Panda, Tipo, 500X, Doblo | | Ford | ford | Focus, F-Series, Kuga, Fiesta, Mustang | | GMC | gmc | Sierra, Yukon, Terrain, Acadia | | Honda | honda | Civic, CR-V, Accord, HR-V, Jazz | | Hyundai | hyundai | Tucson, i30, Kona, Santa Fe, i20 | | Jaguar | jaguar | F-Pace, XF, XE, I-Pace | | Jeep | jeep | Grand Cherokee, Wrangler, Compass | | Kia | kia | Sportage, Ceed, Sorento, Niro, Soul | | Land Rover | land-rover | Range Rover, Defender, Discovery | | Lexus | lexus | RX, NX, ES, IS, UX, GX | | Mazda | mazda | CX-5, 3, 6, CX-30, CX-3, MX-5 | | Mercedes-Benz | mercedes-benz | C-Class, E-Class, GLC, A-Class | | MG | mg | ZS, HS, MG4, MG3, MG5 | | MINI | mini | Countryman, Cooper, Clubman | | Mitsubishi | mitsubishi | Outlander, ASX, Eclipse Cross | | Nissan | nissan | Qashqai, Juke, Leaf, X-Trail | | Opel | opel | Astra, Corsa, Mokka, Insignia | | Peugeot | peugeot | 3008, 2008, 208, 308, 5008 | | Porsche | porsche | 911, Cayenne, Macan, Panamera | | Ram | ram | 1500, 2500, 3500 | | Renault | renault | Clio, Megane, Captur, Scenic | | SEAT | seat | Leon, Ibiza, Arona, Ateca | | Škoda | skoda | Octavia, Fabia, Superb, Kodiaq, Karoq | | Subaru | subaru | Forester, Outback, XV, Impreza | | Suzuki | suzuki | Vitara, Swift, SX4, Jimny | | Tesla | tesla | Model 3, Model Y, Model S, Model X | | Toyota | toyota | Corolla, RAV4, Yaris, Camry, C-HR | | Volvo | volvo | XC60, XC40, XC90, V60, V90 | | VW | vw | Golf, Tiguan, Passat, Polo, T-Roc, ID.4 | #### Important Notes - Make and model parameters are case-insensitive and will be normalized to lowercase - Year must be between 1900 and the current year + 1 - Country parameter is optional - if not provided, default regional pricing will be used - Valuation prices are based on current market data and regional factors - Each successful request consumes 1 API credit from your monthly allowance - Response data is cached for improved performance --- ### Mileage History Track historical mileage records for a vehicle to detect odometer rollbacks or inconsistencies. **Endpoint:** `GET /v1/mileage-history/{vin}` **Response Time:** ~200ms | **Cost:** 1 credit #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | vin | string | Yes | 17-character Vehicle Identification Number (URL path parameter) | | token | string | Yes | API authentication token (query parameter) | #### Request Example (cURL) ```bash curl -X GET \ "https://api.carapi.dev/v1/mileage-history/1HGBH41JXMN109186?token=YOUR_API_KEY" ``` #### Request Example (JavaScript) ```javascript const response = await fetch('https://api.carapi.dev/v1/mileage-history/1HGBH41JXMN109186?token=YOUR_API_KEY', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log(data); ``` #### Request Example (Python) ```python import requests url = "https://api.carapi.dev/v1/mileage-history/1HGBH41JXMN109186" params = { "token": "YOUR_API_KEY" } response = requests.get(url, params=params) data = response.json() print(data) ``` #### Success Response (200) ```json { "vin": "JHMZE2870AS223772", "totalRecords": 3, "mileageHistory": [ { "mileage": 179650, "createdAt": "2025-09-09T13:59:09.872Z" }, { "mileage": 179645, "createdAt": "2025-09-05T00:00:00.000Z" }, { "mileage": 179647, "createdAt": "2025-09-05T00:00:00.000Z" } ] } ``` #### Response Fields | Field | Type | Description | |-----------------------------|--------|-------------| | vin | string | The VIN number that was searched | | totalRecords | number | Total number of mileage records found | | mileageHistory | array | Array of mileage history records | | mileageHistory[].mileage | number | Odometer reading in miles or kilometers | | mileageHistory[].createdAt | string | Timestamp when the mileage record was created (ISO format) | #### Error Response (404) ```json { "error": "No mileage history found for this VIN" } ``` #### Error Codes - **400** Bad Request: Invalid VIN format or missing required parameters - **403** Forbidden: Invalid or missing API token - **404** Not Found: No mileage history found for this VIN - **500** Internal Server Error: Server encountered an error processing the request - **503** Service Unavailable: External data provider temporarily unavailable #### Important Notes - VIN must be exactly 17 characters long and contain only alphanumeric characters - Mileage records are sorted by date with the most recent entries first - Each successful request consumes 1 API credit from your monthly allowance - Historical data availability varies by vehicle and reporting sources - Useful for maintenance tracking, fraud detection, and vehicle history verification --- ### VIN OCR (Image) Extract a 17-character VIN from a photo of a windshield etching, dashboard plate, door jamb sticker, or registration document. #### Endpoint ``` POST https://api.carapi.dev/v1/extract-vin?token=YOUR_API_KEY Content-Type: application/json ``` #### Body ```json { "image": ";base64, prefix>" } ``` | Field | Type | Required | Description | |-------|--------|----------|-------------| | image | string | yes | Base64-encoded image. The data-URL prefix is stripped automatically. Body limit is 15 MB. | | token | string | yes | API authentication token (query parameter). | #### cURL Example ```bash curl -X POST \ "https://api.carapi.dev/v1/extract-vin?token=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"image":"'"$(base64 -w0 vin.jpg)"'"}' ``` #### JavaScript Example ```javascript async function extractVin(file) { const dataUrl = await new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.readAsDataURL(file); }); const response = await fetch( 'https://api.carapi.dev/v1/extract-vin?token=YOUR_API_KEY', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image: dataUrl }) } ); return response.json(); } ``` #### Python Example ```python import base64, requests with open("vin.jpg", "rb") as f: image_b64 = base64.b64encode(f.read()).decode("ascii") response = requests.post( "https://api.carapi.dev/v1/extract-vin", params={"token": "YOUR_API_KEY"}, json={"image": image_b64}, timeout=65, ) print(response.json()) ``` #### Response (200) — VIN found ```json { "vin": "WBAVA31070NL12345", "confidence": 0.92 } ``` #### Response (200) — No VIN found ```json { "vin": null, "confidence": null } ``` #### Response Fields | Field | Type | Description | |------------|-----------------------|-------------| | vin | string \| null | 17-character uppercase VIN, or null when nothing found. Always passes internal `isVin()` validation. | | confidence | number \| null | Self-reported in [0, 1]; null on failure. | #### Error Codes - **400** Missing or invalid `image` field, or invalid base64 payload - **403** Invalid or missing API token - **413** Payload exceeds 15 MB body limit - **500** Internal server error #### Important Notes - A 200 response with `vin: null` is **not** an error — do not retry. - Each request consumes 1 API credit, even when the VIN is null. - Configure your HTTP client with a timeout of at least 65 seconds. - OCR results are not cached server-side. --- ### License Plate OCR (Image) Extract a license plate string from a vehicle photo. Optional `country` enforces region-specific format validation. #### Endpoint ``` POST https://api.carapi.dev/v1/extract-plate?token=YOUR_API_KEY Content-Type: application/json ``` #### Body ```json { "image": "", "country": "sk" } ``` | Field | Type | Required | Description | |---------|--------|----------|-------------| | image | string | yes | Base64-encoded image. Optional `data:image/;base64,` prefix is stripped. Body limit 15 MB. | | country | string | no | 2-letter region code (case-insensitive). Enforces region-specific plate format validation. | | token | string | yes | API authentication token (query parameter). | #### Supported Country Codes (27) sk, cz, de, at, pl, hu, ro, bg, hr, si, rs, fr, gb, it, es, pt, be, nl, se, no, dk, fi, ch, ie, gr, us, ca #### cURL Example ```bash curl -X POST \ "https://api.carapi.dev/v1/extract-plate?token=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"image":"'"$(base64 -w0 car.jpg)"'","country":"sk"}' ``` #### JavaScript Example ```javascript async function extractPlate(file, country) { const dataUrl = await new Promise((resolve) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.readAsDataURL(file); }); const response = await fetch( 'https://api.carapi.dev/v1/extract-plate?token=YOUR_API_KEY', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image: dataUrl, country }) } ); return response.json(); } ``` #### Python Example ```python import base64, requests with open("car.jpg", "rb") as f: image_b64 = base64.b64encode(f.read()).decode("ascii") response = requests.post( "https://api.carapi.dev/v1/extract-plate", params={"token": "YOUR_API_KEY"}, json={"image": image_b64, "country": "sk"}, timeout=65, ) print(response.json()) ``` #### Response (200) — Plate detected ```json { "plateNumber": "BA123AB", "confidence": 0.94 } ``` #### Response (200) — No plate found ```json { "plateNumber": null, "confidence": null } ``` #### Response Fields | Field | Type | Description | |-------------|-----------------|-------------| | plateNumber | string \| null | Uppercase plate string with no spaces or dashes; null when nothing found. | | confidence | number \| null | Self-reported in [0, 1]; null on failure. | #### Error Codes - **400** Missing image, invalid base64, or unsupported country - **403** Invalid or missing API token - **413** Payload exceeds 15 MB body limit - **500** Internal server error #### Important Notes - A 200 response with `plateNumber: null` is **not** an error — do not retry. - Plate strings are returned uppercase and without separators (`BA123AB`, not `BA-123 AB`). - Each request consumes 1 API credit, even when the plate is null. - Configure your HTTP client with a timeout of at least 65 seconds. - OCR results are not cached server-side. --- ### Recalls Look up U.S. recall records for a given make / model / year. Returns normalised recall summaries from the official safety database. Results are cached server-side (~8600s TTL). #### Endpoint ``` GET https://api.carapi.dev/v1/recalls?make=&model=&modelYear=&token=YOUR_API_KEY ``` #### Query Parameters | Param | Type | Required | Description | |-----------|--------|----------|-------------| | make | string | yes | Vehicle manufacturer (case-insensitive), URL-encoded. | | model | string | yes | Vehicle model (case-insensitive), URL-encoded. | | modelYear | string | yes | 4-digit year as a string (e.g. `2020`). | | token | string | yes | API authentication token. | #### cURL Example ```bash curl "https://api.carapi.dev/v1/recalls?token=YOUR_API_KEY&make=TOYOTA&model=RAV4&modelYear=2020" ``` #### JavaScript Example ```javascript const params = new URLSearchParams({ token: 'YOUR_API_KEY', make: 'TOYOTA', model: 'RAV4', modelYear: '2020' }); const response = await fetch( `https://api.carapi.dev/v1/recalls?${params.toString()}` ); const data = await response.json(); ``` #### Python Example ```python import requests response = requests.get( "https://api.carapi.dev/v1/recalls", params={ "token": "YOUR_API_KEY", "make": "TOYOTA", "model": "RAV4", "modelYear": "2020", }, ) print(response.json()) ``` #### Response (200) — Recalls found ```json { "count": 2, "hasRecalls": true, "recalls": [ { "manufacturer": "Toyota Motor Engineering & Manufacturing", "reportReceivedDate": "23/01/2020", "component": "FUEL SYSTEM, GASOLINE", "summary": "...", "consequence": "...", "remedy": "...", "modelYear": "2020", "make": "TOYOTA", "model": "RAV4", "parkIt": false, "parkOutside": false, "overTheAirUpdate": false } ] } ``` #### Response (200) — No recalls ```json { "count": 0, "hasRecalls": false, "recalls": [] } ``` #### Response Fields | Field | Type | Description | |------------------------------|---------|-------------| | count | number | Recall record count (upstream-reported). | | hasRecalls | boolean | True when at least one recall was found. | | recalls[].manufacturer | string | Issuing manufacturer. | | recalls[].reportReceivedDate | string | Upstream-formatted date string (e.g. `23/01/2020`). | | recalls[].component | string | Affected component (e.g. `AIR BAGS`). | | recalls[].summary | string | Defect summary. | | recalls[].consequence | string | Safety consequence. | | recalls[].remedy | string | Remedy / fix description. | | recalls[].modelYear | string | Model year. | | recalls[].make | string | Vehicle make. | | recalls[].model | string | Vehicle model. | | recalls[].parkIt | boolean | "Park it" advisory — do not drive until repaired. | | recalls[].parkOutside | boolean | "Park outside" advisory — fire risk. | | recalls[].overTheAirUpdate | boolean | Remedy delivered as an OTA software update. | #### Error Codes - **400** Missing `make`, `model`, or `modelYear` - **403** Invalid or missing API token - **502** Upstream recalls service failure (response includes `details`) - **500** Internal server error #### Important Notes - Coverage is best for vehicles sold in the U.S. market. - The `parkIt` and `parkOutside` flags signal severe defects — surface them prominently to end users. - Repeat lookups for the same `make+model+modelYear` are served from cache and do not re-hit the upstream. - Each request consumes 1 API credit. --- ### EV Charging Stations Find the nearest EV charging stations around a coordinate pair, with optional connector type, operator, and minimum power filters. Results are physical locations (charge points at the same site are grouped, with per-connector-type summaries) sorted by distance. Results are cached server-side (24h TTL; source data refreshes monthly). #### Endpoint ``` GET https://api.carapi.dev/v1/charging-stations?lat=&lon=&token=YOUR_API_KEY ``` #### Query Parameters | Param | Type | Required | Description | |----------------|---------|----------|-------------| | lat | number | yes | Search center latitude, -90 to 90. | | lon | number | yes | Search center longitude, -180 to 180. | | radius | number | no | Search radius in **kilometers**. Default 10, min 0.1, max 50. Out-of-range values are rejected with 400, never clamped. | | connectorType | enum | no | `type2`, `ccs`, or `chademo`. Only locations with at least one connector of this type. | | owner | string | no | Case-insensitive substring match on the operator name (1-100 chars), e.g. `ionity`. | | minPowerKw | number | no | Only locations with a connector at or above this power (kW). Combined with `connectorType`, applies to that type. | | limit | integer | no | Max locations to return. Default 5, min 1, max 10. | | token | string | yes | API authentication token. | #### cURL Example ```bash curl "https://api.carapi.dev/v1/charging-stations?token=YOUR_API_KEY&lat=50.1175&lon=14.4908&connectorType=ccs&minPowerKw=150" ``` #### JavaScript Example ```javascript const params = new URLSearchParams({ token: 'YOUR_API_KEY', lat: '50.1175', lon: '14.4908', connectorType: 'ccs', minPowerKw: '150' }); const response = await fetch( `https://api.carapi.dev/v1/charging-stations?${params.toString()}` ); const data = await response.json(); ``` #### Python Example ```python import requests response = requests.get( "https://api.carapi.dev/v1/charging-stations", params={ "token": "YOUR_API_KEY", "lat": 50.1175, "lon": 14.4908, "connectorType": "ccs", "minPowerKw": 150, }, ) print(response.json()) ``` #### Response (200) — Stations found ```json { "search": { "lat": 50.1175, "lon": 14.4908, "radiusKm": 10, "connectorType": "ccs", "owner": null, "minPowerKw": 150 }, "count": 1, "stations": [ { "name": "R378", "owners": ["CEZ, a. s."], "lat": 50.12241, "lon": 14.51215, "distanceKm": 1.62, "stationCount": 4, "address": { "street": "Kbelská 919/31", "city": "Praha 9 - Vysočany", "zip": "190 00", "country": "CZ" }, "maxPowerKw": 300, "connectors": { "ccs": { "maxPowerKw": 300, "minPricePerKwh": null, "parkingPricePerHour": null, "freeParkingMinutes": null, "plugs": 4 }, "type2": { "maxPowerKw": 22, "minPricePerKwh": null, "parkingPricePerHour": null, "freeParkingMinutes": null, "plugs": 1 } } } ] } ``` #### Response (200) — No stations in range ```json { "search": { "lat": 30, "lon": -40, "radiusKm": 10, "connectorType": null, "owner": null, "minPowerKw": null }, "count": 0, "stations": [] } ``` #### Response Fields | Field | Type | Description | |-----------------------------------------|--------------|-------------| | search | object | Echo of the search parameters with defaults applied (unused filters are null). | | count | number | Locations returned (max 10). | | stations[].name | string | Location name as published by the operator. | | stations[].owners | string[] | Operator name(s) — some sites host multiple operators. | | stations[].lat / lon | number | Location coordinates. | | stations[].distanceKm | number | **Great-circle (straight-line) distance** from the search point in km — NOT driving distance. 2 decimals. | | stations[].stationCount | number | Individual charge points (EVSEs) grouped into this location. | | stations[].address | object | `street`, `city`, `zip` (nullable), `country` (ISO-2). | | stations[].maxPowerKw | number\|null | Highest power at the location across all connector types. | | stations[].connectors | object | Per-type summary keyed by `type2` / `ccs` / `chademo` (rarely `other`). | | connectors..maxPowerKw | number\|null | Highest power for this connector type. | | connectors..minPricePerKwh | number\|null | Lowest known price per kWh in **EUR**; often null (operators may not publish pricing). | | connectors..parkingPricePerHour| number\|null | Parking price per hour in EUR, if any. | | connectors..freeParkingMinutes | number\|null | Free parking duration in minutes, if any. | | connectors..plugs | number\|null | Distinct charge-point configurations of this type at the location. | #### Error Codes - **400** Missing `lat`/`lon`, or an out-of-range/invalid filter (specific message per parameter) - **403** Invalid or missing API token - **502** Upstream charging data service failure (response includes `details`) - **500** Internal server error #### Important Notes - `distanceKm` is straight-line distance; the driving route will be longer. - Coverage is European (strongest: IT, DE, NL, AT, BE, FR, CZ, PL, HU, RO, SK); ~54,000 physical locations, refreshed monthly. - No pagination by design: at most 10 locations within at most 50 km per request. An empty area returns 200 with `count: 0`. - Prices are EUR; `minPricePerKwh` is null where the operator publishes no pricing through the roaming network. - Each request consumes 1 API credit. --- ### Time to Sell How long comparable listings stay on the market: a median days-on-market figure with the 25th and 75th percentiles around it. The natural companion to Vehicle Valuation — valuation answers *what is it worth*, this answers *how fast will it move*. #### How the number is derived Every listing is re-sighted on each crawl of its marketplace, so a listing that stops being re-sighted has left the market and the span it was visible is its days on market. - **Sold proxy**: a listing unseen for 21+ days *before its own source's latest crawl*. Anchoring to the source rather than wall-clock time stops a stalled scraper from declaring its whole inventory sold. - **Cohort window**: listings first seen in the last 12 months. Listings never re-sighted, and listings with no price, are excluded. - **Sample floor**: at least 30 delisted comparables. Below that the upstream progressively relaxes filters (mileage → kw → year widened to ±4) before returning 404. `fuel` is never relaxed. - **Sources**: up to 7 marketplaces per market, depending on the country. A figure is always pooled across every source carrying the cohort. #### Endpoint ``` GET https://api.carapi.dev/v1/time-to-sell?make=&model=&country=&token=YOUR_API_KEY ``` #### Query Parameters | Param | Type | Required | Description | |---------|---------|----------|-------------| | make | string | yes | Vehicle manufacturer, 1-50 chars (case-insensitive). Common aliases are resolved (`volkswagen` → `vw`). | | model | string | yes | Vehicle model, 1-100 chars. Matched as a **base model** — `A6` ≠ `A6 Allroad`; trim codes fold to their series (`320d` → `3-series`). | | country | enum | yes | ISO 3166-1 alpha-2. 13 supported: `CZ`, `SK`, `PL`, `UK`, `US`, `DE`, `NL`, `NO`, `IT`, `BE`, `ES`, `AT`, `FR`. | | year | integer | no | Model year, 1990 to next year. Widened to **±2 years**. | | kw | integer | no | Engine power in kW, 1-2000. Widened to **±20%** (at least ±15 kW). | | mileage | integer | no | Mileage in km, 0-2,000,000. Widened to **±25%** (at least ±20,000 km). | | fuel | enum | no | `petrol`, `diesel`, `electric`, `hybrid`, `lpg`, `cng`, `hydrogen`. Matched exactly, **never relaxed**. | | token | string | yes | API authentication token. | **Send point values, not ranges.** Describe the one concrete car you care about; the buffers above are applied server-side because near-identical cars sell at similar speeds. Exact matching starves the sample — a 2021 Audi RS6 in CZ has 6 delisted comparables on its own but 34 across the ±2-year band. #### cURL Example ```bash curl "https://api.carapi.dev/v1/time-to-sell?token=YOUR_API_KEY&make=skoda&model=octavia&country=CZ" ``` #### JavaScript Example ```javascript const params = new URLSearchParams({ token: 'YOUR_API_KEY', make: 'audi', model: 'a6', country: 'CZ', year: '2018', kw: '180', mileage: '120000', fuel: 'diesel' }); const response = await fetch( `https://api.carapi.dev/v1/time-to-sell?${params.toString()}` ); const data = await response.json(); ``` #### Python Example ```python import requests response = requests.get( "https://api.carapi.dev/v1/time-to-sell", params={ "token": "YOUR_API_KEY", "make": "audi", "model": "a6", "country": "CZ", "year": 2018, "kw": 180, "mileage": 120000, "fuel": "diesel", }, ) print(response.json()) ``` #### Response (200) — Whole model in one market ```json { "make": "skoda", "model": "octavia", "country": "CZ", "medianDaysToSell": 24, "p25Days": 10, "p75Days": 58 } ``` #### Response (200) — One concrete car A 2021 RS6 sent as `year=2021&kw=441`. The body has the same shape whatever you filter on — the optional parameters change *which cars* the numbers describe, not what comes back. ```json { "make": "audi", "model": "rs6", "country": "CZ", "medianDaysToSell": 39, "p25Days": 13, "p75Days": 79 } ``` #### Response (404) — Not enough comparables ```json { "error": "Insufficient market data for this vehicle configuration", "hint": "Try a broader query — omit mileage, kw or year, or use a market with more listings" } ``` #### Response Fields | Field | Type | Description | |--------------------------|----------|-------------| | make / model / country | string | The **canonical cohort** the query resolved to — may differ from what you sent (aliases and trim codes resolve, e.g. `volkswagen` → `vw`, `320d` → `3-series`). | | medianDaysToSell | number | Median days on market: half of comparable listings left the market faster than this, half slower. The headline number — quote this one if you quote only one. | | p25Days | number | 25th percentile, the **fast quarter**: a quarter of comparables were gone within this many days. A realistic best case for a sharply priced car, not a typical outcome. | | p75Days | number | 75th percentile, where the **slow quarter** begins: three quarters were gone by this point, and the remaining quarter took longer still, with no upper bound. The pessimistic case. | #### Reading the three numbers Days-on-market is **right-skewed** — a handful of cars go almost immediately, most take a few weeks, and a long tail sits unsold for months. That is why the response gives three points on the distribution rather than one average; an average would be dragged up by the tail and describe no car in particular. For an Octavia in CZ (`p25Days` 10, `medianDaysToSell` 24, `p75Days` 58): - 25% of comparable listings were gone within **10** days - 50% within **24** days - 75% within **58** days — so **a quarter were still listed after day 58** `p25Days`→`p75Days` is the **middle half** of the cohort. That spread is normally 4-6x wide, and it is the honest version of the answer; a lone median reads like a promise the data cannot make. **It is not a countdown.** The percentiles describe a cohort of past listings, not the specific car in front of you, and they do not decay as it sits on the market. Day 30 against a 24-day median does not mean a car is overdue — it means it is in the slower half, which a quarter of that cohort also was. A framing that survives contact with an end user: *"Cars like this typically sell in about 24 days. A quarter go within 10; a quarter take more than 58."* #### Market Coverage (13 countries) Deliberately narrower than Vehicle Valuation's 24 markets: this endpoint also needs enough *delistings* and a crawl cadence fine enough to resolve a listing's lifetime, which some high-volume markets do not have. - **Tier 1** (high volume, healthy re-sighting): CZ, SK, PL, UK, US, DE — CZ has the best data quality overall. - **Tier 2** (viable but thinner; expect more relaxation and more 404s on niche models): NL, NO, IT, BE, ES, AT, FR. - **NL resolution caveat**: 41% of Dutch delistings land in a single 6-day bucket — that is the crawl interval, not the market. Treat NL figures as accurate to roughly ±6 days: fine for ranking models against each other, not for precise promises. - Countries outside the list are rejected with 400. Some of them carry plenty of listings but re-sight too rarely or too uniformly to produce a meaningful number, and a confident-looking wrong answer is worse than a refusal. #### Error Codes - **400** Missing `make`/`model`/`country`, an unsupported country or fuel type, or a numeric filter out of range - **403** Invalid or missing API token - **404** Fewer than 30 comparable delistings even after relaxation; the body carries a `hint` naming the filter to drop - **503** Upstream market-data service unreachable or timed out - **500** Internal server error #### Important Notes - This is a **market-liquidity signal, not a sales record**. Delisting also captures withdrawn and expired listings — say it that way wherever you surface the number. The response body does not repeat this caveat; it is a property of the endpoint, not of any one answer. - Publish `p25Days` and `p75Days` with the median — the spread is genuinely wide (an RS6 in CZ runs 13 to 79 days) and a lone median oversells the precision. - **Narrow filters can be widened silently.** If your cohort falls under 30 delistings, `mileage` is dropped first, then `kw`, then `year` goes to ±4, and you get a 200 describing that wider set rather than a 404. Only send the optional filters you actually need, and treat a heavily filtered query as approximate. - A **404 is an expected outcome**, not a fault — it means the market genuinely lacks comparable delistings. Act on the `hint`; do not retry the same query. - Credits are charged per request, **including those 404s** (the same convention as every other endpoint). Since 404 is routine here, only send `year`/`kw`/`mileage` when you genuinely need the cohort narrowed. - Responses are cached upstream for 24 hours (the underlying cohort for 10 minutes), so repeat queries return in single-digit milliseconds; a cold cohort can take several seconds. - Each request consumes 1 API credit. --- ### Cost of Ownership What a car costs to run over a holding period: depreciation, fuel, insurance, maintenance, and taxes and fees, totalled and broken down per component and per year. The third question in the set — Vehicle Valuation answers *what is it worth*, Time to Sell answers *how fast will it move*, this answers *what will it cost me to keep*. #### Read the breakdown, not just the total Every component carries its own `confidence`, and they are not equal within a single answer: - **`measured`** — a published dataset covers this exact vehicle: EEA or EPA consumption for the model, year and kW band, an official fuel-price series, a depreciation curve fitted across live comparable listings. Typically `depreciation` and `fuel`. - **`estimated`** — authored constants, or data stretched to fit the cohort. Still useful for comparing cars against each other, weaker as an absolute figure. Typically `insurance`, `maintenance` and `taxesAndFees`. The same component flips between the two across vehicles: fuel is `measured` for a mainstream diesel with an EEA record and `estimated` for a rare import that only matched a neighbouring model year. `sampleSize` and `modelYearUsed` on the fuel component tell you which happened. **Render the breakdown — do not flatten the response into a single number.** #### Endpoint ``` GET https://api.carapi.dev/v1/cost-of-ownership?make=&model=&year=&country=&token=YOUR_API_KEY ``` #### Query Parameters | Param | Type | Required | Description | |-----------|---------|----------|-------------| | make | string | yes | Vehicle manufacturer, 1-50 chars (case-insensitive). Common aliases are resolved (`volkswagen` -> `vw`). | | model | string | yes | Vehicle model, 1-100 chars. Matched as a **base model**; trim codes fold to their series (`320d` -> `3-series`). | | year | integer | yes | Model year, 1990 to next year. **Required** — depreciation is a function of the car's age, so there is no cohort-wide fallback. | | country | enum | yes | `CZ`, `SK`, `PL` or `US` (case-insensitive). | | fuel | enum | no | `petrol`, `diesel`, `electric`, `hybrid`, `phev`, `lpg`. Omit it and the drivetrain is inferred from what the market sells. `lpg` is rejected for `US`. | | kw | integer | no | Engine power in kW, 1-2000. Narrows the depreciation cohort and picks the CZ/SK/PL insurance band. **Ignored for US insurance**, which is not power-rated. | | kmPerYear | integer | no | Annual distance in km, 1,000-200,000. Default 15,000 (CZ/SK/PL) or 19,300 (US, i.e. 12,000 miles). | | years | integer | no | Holding period in whole years, 1-15. Default 5. | | currency | enum | no | `EUR`, `CZK`, `PLN`, `USD`. Default EUR for CZ/SK/PL and USD for US. **Not constrained by country** — any of the four works for any market. | | token | string | yes | API authentication token. | Every rule above is enforced before the upstream call, so a malformed request fails in milliseconds rather than after a round trip. #### cURL Example ```bash curl "https://api.carapi.dev/v1/cost-of-ownership?token=YOUR_API_KEY&make=skoda&model=octavia&year=2019&country=CZ&kw=110" ``` #### JavaScript Example ```javascript const params = new URLSearchParams({ token: 'YOUR_API_KEY', make: 'skoda', model: 'octavia', year: '2019', country: 'CZ', fuel: 'diesel', kw: '110', kmPerYear: '20000', years: '3', currency: 'CZK' }); const response = await fetch( `https://api.carapi.dev/v1/cost-of-ownership?${params.toString()}` ); const data = await response.json(); const { currency } = data.assumptions; console.log(`${data.totals.perMonth} ${currency}/month over ${data.assumptions.years} years`); for (const [name, part] of Object.entries(data.breakdown)) { console.log(` ${name}: ${part.total} ${currency} (${part.confidence})`); } ``` #### Python Example ```python import requests response = requests.get( "https://api.carapi.dev/v1/cost-of-ownership", params={ "token": "YOUR_API_KEY", "make": "toyota", "model": "camry", "year": 2021, "country": "US", "kmPerYear": 19300, "years": 5, }, ) data = response.json() for name, part in data["breakdown"].items(): print(f" {name:15} {part['total']:>7} ({part['confidence']})") ``` #### Response (200) — 2019 Skoda Octavia in CZ, 5 years ```json { "vehicle": { "make": "Skoda", "model": "Octavia", "year": 2019, "fuel": "diesel", "kw": 110, "requested": { "make": "skoda", "model": "octavia" } }, "country": "CZ", "assumptions": { "kmPerYear": 15000, "years": 5, "currentValue": 12800, "currency": "EUR", "fuelPriceFlat": true }, "totals": { "total": 18092, "perYear": 3618, "perMonth": 302, "perKm": 0.241 }, "breakdown": { "depreciation": { "total": 6400, "confidence": "measured", "method": "market-cross-section", "valueBasis": "median", "notes": ["Fitted across 1,180 CZ listings of this model aged 4-13 years."] }, "fuel": { "total": 5112, "confidence": "measured", "consumptionL100km": 4.8, "consumptionKwh100km": null, "provider": "eea", "fuelKey": "diesel", "modelYearUsed": 2019, "kwBand": [100, 120], "sampleSize": 42, "pricePerUnit": 1.42, "priceUnit": "litre", "priceCurrency": "EUR", "notes": ["Type-approval consumption; real-world use typically runs 10-20% higher."] }, "insurance": { "total": 2100, "confidence": "estimated", "annualPremium": 420, "kwBand": [92, 120], "notes": ["A comprehensive policy typically runs 2-3x this."] }, "maintenance": { "total": 3900, "confidence": "estimated", "notes": ["Servicing, wear parts and tyres, rising with age. Excludes accident repairs."] }, "taxesAndFees": { "total": 580, "confidence": "estimated", "items": [ { "label": "Motorway vignette", "total": 460, "recurring": true }, { "label": "Technical + emissions inspection", "total": 120, "recurring": true } ], "notes": ["Passenger cars have paid no annual road tax in CZ since 2022."] } }, "perYear": [ { "year": 1, "calendarYear": 2026, "vehicleAge": 7, "depreciation": 1700, "fuel": 1022, "insurance": 420, "maintenance": 620, "taxesAndFees": 116, "total": 3878 }, { "year": 2, "calendarYear": 2027, "vehicleAge": 8, "depreciation": 1450, "fuel": 1022, "insurance": 420, "maintenance": 700, "taxesAndFees": 116, "total": 3708 }, { "year": 3, "calendarYear": 2028, "vehicleAge": 9, "depreciation": 1250, "fuel": 1023, "insurance": 420, "maintenance": 780, "taxesAndFees": 116, "total": 3589 }, { "year": 4, "calendarYear": 2029, "vehicleAge": 10, "depreciation": 1050, "fuel": 1022, "insurance": 420, "maintenance": 880, "taxesAndFees": 116, "total": 3488 }, { "year": 5, "calendarYear": 2030, "vehicleAge": 11, "depreciation": 950, "fuel": 1023, "insurance": 420, "maintenance": 920, "taxesAndFees": 116, "total": 3429 } ] } ``` #### Response (200) — 2021 Toyota Camry in US, 5 years Same shape, different regime. Abbreviated to the parts that change: EPA consumption, liability-only insurance that ignores `kw`, and sales tax landing entirely in year 1. ```json { "vehicle": { "make": "Toyota", "model": "Camry", "year": 2021, "fuel": "petrol", "kw": 151, "requested": { "make": "toyota", "model": "camry" } }, "country": "US", "assumptions": { "kmPerYear": 19300, "years": 5, "currentValue": 21500, "currency": "USD", "fuelPriceFlat": true }, "totals": { "total": 27490, "perYear": 5498, "perMonth": 458, "perKm": 0.285 }, "breakdown": { "insurance": { "total": 3900, "confidence": "estimated", "annualPremium": 780, "kwBand": null, "notes": ["US premiums are not power-rated, so kw does not affect this component."] }, "taxesAndFees": { "total": 2090, "confidence": "estimated", "items": [ { "label": "Sales tax on vehicle value (~6%)", "total": 1290, "recurring": false }, { "label": "Registration and title", "total": 800, "recurring": true } ], "notes": [ "National expected values across 50 state regimes — not a bill for any one state.", "Sales tax falls entirely in year 1, following the Edmunds True Cost to Own convention." ] } } } ``` #### Response (404) — a component could not be estimated ```json { "error": "Insufficient data to estimate cost of ownership for this vehicle", "missing": "consumption", "hint": "No EEA consumption record for this model, year and kW band. Omit kw, or try an adjacent model year." } ``` `missing` is one of `make`, `model`, `consumption`, `fuelPrice`, `depreciation` or `insurance`. `make`/`model` mean the vehicle is unknown to the market; the rest mean the vehicle is known but one component has no data behind it. #### Response Fields | Field | Type | Description | |-------------|--------|-------------| | vehicle | object | The car as the upstream resolved it (`make`, `model`, `year`, `fuel`, `kw`), plus `requested` — the make and model as you sent them, after case and alias normalization (volkswagen → vw) but before the upstream's own resolution. **Match your own records against `requested`**, not `vehicle.make`, allowing for that normalization. | | country | string | Market the figures describe. | | assumptions | object | `kmPerYear`, `years`, `currentValue`, `currency`, `fuelPriceFlat`. Enough to reproduce or re-scale the figures. | | totals | object | `total`, `perYear`, `perMonth`, `perKm`. `total` is the sum of the five components and of the `perYear` rows. | | breakdown | object | The five components — `depreciation`, `fuel`, `insurance`, `maintenance`, `taxesAndFees` — each with `total`, `confidence` and `notes`. | | perYear | array | One row per requested year with the five components and the row total. | All money values are **integers** in `assumptions.currency`, with one exception: `totals.perKm` carries 3 decimals. #### What's included, what's excluded | Component | Included | Not included | |---------------|----------|--------------| | depreciation | Value lost between today's market price and the resale price at the end of the horizon, fitted across live comparable listings. | Any assumption that you sell at trade-in or auction rather than privately. | | fuel | Energy for the resolved drivetrain at the annual distance, priced from a published series and held flat. | Price inflation or forecast movement — see `fuelPriceFlat`. | | insurance | The mandatory cover only: MTPL in CZ/SK/PL, liability at state minimums in the US. | Collision, comprehensive, GAP, and any driver-specific loading or no-claims discount. | | maintenance | Scheduled servicing, wear parts and tyres, rising with age and distance. | Accident repairs, unscheduled failures, warranty work. | | taxesAndFees | Statutory charges, itemised: vignettes and roadworthiness tests in CZ/SK/PL; sales tax, registration and title in the US. | City entry and congestion charges, tolls, parking. | **Financing interest is never included, in any market.** A car bought on credit costs more than this endpoint says, by an amount only the caller knows. #### Market Coverage (4 countries) Narrower than Time to Sell's 13 markets and far narrower than Vehicle Valuation's 24: a total needs all five components to exist locally — a fuel-price series, an insurance premium model and the tax regime encoded — not just enough listings to fit a curve. | Markets | Insurance scope | Taxes and fees | Consumption | |------------|-----------------|----------------|-------------| | CZ, SK, PL | MTPL (third-party liability), banded on engine power — `kw` moves this figure | Vignettes and roadworthiness tests; no annual passenger-car road tax | EEA | | US | Liability-only at state minimums; not power-rated, so `kw` is ignored here | ~6% sales tax on the car's value plus registration and title | EPA | **US tax and fee figures are national expected values across 50 state regimes**, following the Edmunds True Cost to Own convention, including roughly 6% sales tax on the car's value charged once in year 1. Actual sales tax ranges from 0% (Oregon, Montana, New Hampshire, Delaware) to over 7%, and registration varies as widely. Treat the US total as a basis for comparing cars, not as a bill for a given state. #### Error Codes - **400** Missing `make`/`model`/`year`/`country`, an unsupported country, fuel type or currency, a fuel type the market does not support (`lpg` in US), or a numeric parameter out of range - **403** Invalid or missing API token - **404** A whole component could not be estimated; the body carries `missing` and a `hint` - **502** The upstream cost-data service answered with an unexpected status or payload - **503** Upstream cost-data service unreachable or timed out - **500** Internal server error #### Important Notes - These are **estimates, not quotes**. Nothing is priced for a specific vehicle, driver or policy; insurance in particular assumes a clean record and mandatory cover only. - **Render `confidence` per component.** A total whose depreciation is measured and whose insurance is estimated is not the same object as one where both are measured, and only the breakdown says which you have. - `perYear` is **not `totals.perYear` repeated**. Depreciation is steepest in the first years and one-off charges (US sales tax, registration) land in year 1, so a 1-year horizon is never one fifth of a 5-year one. - `fuelPriceFlat` is always true today: the pump price is held at its observed value rather than forecast. `pricePerUnit` gives you the base to model a rising-price scenario yourself. - Consumption figures are type-approval (EEA) or combined-cycle (EPA) ratings. Real-world use typically runs 10-20% higher, so the fuel component is a floor rather than a mid-point. - Prices are quoted **per litre even in the US**, so markets stay directly comparable. Divide by 3.785 to sanity-check against a per-gallon price. - A **404 is cached per query**, so retrying the same request unchanged returns the same answer. Act on `missing` and `hint` instead. - Each request consumes 1 API credit, including the 404s and the 400s. --- ### EV Route Planner Plan a drivable EV trip with the charging stops needed to complete it: which stations, arrival and departure state of charge, how long each charge takes, energy used, and total trip time. Answers the question a range figure cannot — *can this car make this trip today, and where do I stop?* #### How the plan is built The planner routes origin to destination, simulates the battery along that route, and inserts charging stops where the state of charge would fall below your reserve. It optimises **total trip time**, not stop count — it will prefer two short charges on a fast charging curve over one long one when that gets you there sooner. - **Consumption model**: starts from the vehicle's rated Wh/km, then adjusts for speed, grade and ambient temperature. Cold weather both raises consumption and slows charging, so `ambientC` materially changes the plan. - **Charging curve**: charge time is modelled against the vehicle's DC curve and the station's peak power, not a flat kW figure. - **Feasibility**: a trip with no reachable charger on some stretch returns **200** with `feasible: false` and a `gap` naming where it breaks — it is an answer, not an error. #### Endpoint ``` GET https://api.carapi.dev/v1/ev-route?originLat=&originLon=&destLat=&destLon=&vehicleId=&token=YOUR_API_KEY ``` #### Query Parameters | Param | Type | Required | Description | |------------------|---------|----------|-------------| | originLat | number | yes | Origin latitude. Must be inside the European service area (35 to 62). | | originLon | number | yes | Origin longitude. Must be inside the European service area (-11 to 35). | | destLat | number | yes | Destination latitude. | | destLon | number | yes | Destination longitude. | | vehicleId | string | no | Id from `/v1/ev-vehicles`. Mutually exclusive with the custom vehicle params below. | | batteryKwh | number | no | Custom vehicle usable battery, 5-300. Required with `consumptionWhKm` when `vehicleId` is omitted. | | consumptionWhKm | number | no | Custom vehicle flat consumption in Wh/km, 80-400. | | connector | enum | no | `ccs`, `chademo` or `type2`. Default `ccs`. | | maxDcKw | number | no | Custom vehicle peak DC power, 20-1000. Default 150. | | initialSoc | number | no | State of charge at departure, % 5-100. Default 90. Must exceed `reserveSoc`. | | minArrivalSoc | number | no | Minimum state of charge on arrival, % 0-80. Default 10. | | reserveSoc | number | no | Buffer never planned into, % 0-50. Default 10. | | ambientC | number | no | Ambient temperature °C, -40 to 50. Default 20. Cold adds stops. | | maxDetourKm | number | no | How far off-route a charger may sit, 0.5-10 km. Default 5. | | optimize | enum | no | `time` only today. `cost` and `balanced` arrive in a later version. | | includeGeometry | boolean | no | Return the route polyline. Default false. | | token | string | yes | API authentication token. | **Send either `vehicleId` or the custom trio, never both** — mixing them returns 400 rather than silently picking one. #### cURL Example ```bash curl "https://api.carapi.dev/v1/ev-route?token=YOUR_API_KEY&originLat=50.087&originLon=14.421&destLat=52.52&destLon=13.405&vehicleId=tesla-model-3-lr-2021" ``` #### JavaScript Example ```javascript const params = new URLSearchParams({ token: 'YOUR_API_KEY', originLat: '50.087', originLon: '14.421', destLat: '52.52', destLon: '13.405', vehicleId: 'tesla-model-3-lr-2021', ambientC: '-5' }); const response = await fetch( `https://api.carapi.dev/v1/ev-route?${params.toString()}` ); const plan = await response.json(); // Required by the data licenses — display this wherever you show the plan console.log(plan.attribution.join(' · ')); ``` #### Python Example ```python import requests resp = requests.get( "https://api.carapi.dev/v1/ev-route", params={ "token": "YOUR_API_KEY", "originLat": 50.087, "originLon": 14.421, "destLat": 52.52, "destLon": 13.405, "vehicleId": "tesla-model-3-lr-2021", "ambientC": -5, }, ) plan = resp.json() print(plan["summary"]["totalMin"], "min") for stop in plan["stops"]: print(stop["name"], stop["chargeMin"], "min") ``` #### Response (200) — Prague to Berlin, one stop ```json { "feasible": true, "summary": { "distanceKm": 351.2, "driveMin": 227, "chargeMin": 19, "totalMin": 246, "chargeActiveMin": 12, "plugOverheadMin": 7, "offRouteMin": 3, "offRouteKm": 1.8, "energyKwh": 64.3, "arrivalSocPct": 15, "chargeStops": 1, "estChargeCostEur": 21.7, "stopsWithKnownPrice": 1 }, "warnings": [], "stops": [ { "name": "EnBW Senftenberger Str. 29, Schipkau", "owners": ["EnBW"], "lat": 51.5233, "lon": 13.9341, "address": { "street": "Senftenberger Str. 29", "city": "Schipkau", "zip": "01998", "country": "DE" }, "maxPowerKw": 300, "stationCount": 8, "routeKm": 219, "arrivalSocPct": 18, "departSocPct": 62, "chargeMin": 19, "kwhAdded": 33.4, "estCostEur": 21.7, "detourKm": 1.2, "alternatives": [] } ], "legs": [ { "fromKm": 0, "toKm": 219, "distanceKm": 219, "driveMin": 132, "energyKwh": 38.6 }, { "fromKm": 219, "toKm": 351.2, "distanceKm": 132.2, "driveMin": 95, "energyKwh": 25.7 } ], "vehicle": { "id": "tesla-model-3-lr-2021", "label": "Tesla Model 3 Long Range Dual Motor 2021", "usableBatteryKwh": 75, "consumptionWhPerKm": 160, "dcMaxKw": 250, "dcPorts": ["ccs"] }, "attribution": [ "© OpenStreetMap contributors", "openrouteservice.org", "Vehicle data from Open EV Data" ], "disclaimer": "Estimates only. Consumption and charge times vary with conditions; verify charger status before relying on a stop." } ``` #### Response (200) — Trip is not possible ```json { "feasible": false, "summary": null, "warnings": [], "stops": [], "legs": [], "gap": { "fromKm": 412, "toKm": 598, "neededKwh": 31.4 }, "reason": "No charger within range on this stretch", "attribution": ["© OpenStreetMap contributors", "openrouteservice.org"], "disclaimer": "Estimates only. Consumption and charge times vary with conditions; verify charger status before relying on a stop." } ``` #### Response Fields | Field | Type | Description | |-------|------|-------------| | feasible | boolean | Whether the trip can be completed. `false` is a 200, not an error. | | summary.distanceKm | number | Total driving distance including detours to chargers | | summary.driveMin | integer | Time moving | | summary.chargeMin | integer | Time stopped, active charging plus plug overhead | | summary.totalMin | integer | Door-to-door trip time — the figure the planner minimises | | summary.chargeActiveMin | integer | Of `chargeMin`, the part actually delivering energy | | summary.plugOverheadMin | integer | Fixed per-stop cost of pulling in, plugging and leaving | | summary.offRouteMin / offRouteKm | number | Detour cost of reaching the chosen chargers | | summary.energyKwh | number | Total energy consumed | | summary.arrivalSocPct | number | State of charge on arrival | | summary.chargeStops | integer | Number of charging stops | | summary.estChargeCostEur | number\|null | Estimated charging cost, null when no stop has published pricing | | summary.stopsWithKnownPrice | integer | How many stops the cost estimate is based on — read it alongside `estChargeCostEur` | | warnings[] | string | Non-fatal caveats, e.g. the charging route being longer than the direct one | | stops[] | object | Each charging stop: station identity, position, `arrivalSocPct`, `departSocPct`, `chargeMin`, `kwhAdded`, `detourKm` | | legs[] | object | Driving segments between stops | | vehicle | object | The vehicle the plan was computed for, resolved from `vehicleId` or your custom params | | attribution[] | string | **Required credit — see below** | | disclaimer | string | Estimate caveat to surface with the plan | | gap | object | On `feasible: false`, where the trip breaks and how much energy is missing | | reason | string | On `feasible: false`, why | | geometry | string | Encoded route polyline, only when `includeGeometry=true` | #### Attribution is required The plan is built from OpenStreetMap (ODbL), openrouteservice and Open EV Data. All three require credit, so `attribution` is part of the response contract rather than metadata. **Render the returned array** rather than hardcoding today's list — sources will be added, and a copied list silently goes stale. #### Error Codes - **400** Missing or out-of-range coordinate, a route outside Europe or longer than 2500 km, mixing `vehicleId` with custom vehicle params, or an unknown `connector`/`optimize` value - **403** Invalid or missing API token - **502** Upstream routing or charging data unavailable, including an upstream that stalled past our timeout - **503** Routing provider quota exhausted. Carries `retryAfterSeconds` and a `Retry-After` header; the budget is shared by all callers and resets daily, so retrying sooner will not succeed - **500** Internal server error #### Important Notes - **`feasible: false` is a successful answer.** Handle it as a result with a `gap`, not as a failure to retry. - **Charging data refreshes monthly.** Verify a charger is live before relying on a stop — the `disclaimer` field says so, and so should your UI. - **`ambientC` is the highest-leverage optional parameter.** Winter planning at -5°C can add a stop that a 20°C plan does not show. - Send point values, not ranges: describe the one trip you care about. - Cached upstream, so a repeated corridor returns in ~100ms against ~2s for a cold plan. - Each request consumes 1 API credit. --- ### EV Vehicle Catalog The electric vehicles the route planner supports, with the figures it plans against. Take an `id` from here and pass it as `vehicleId` to `/v1/ev-route` instead of describing a car by hand. #### Endpoint ``` GET https://api.carapi.dev/v1/ev-vehicles?brand=&token=YOUR_API_KEY ``` #### Query Parameters | Param | Type | Required | Description | |-------|--------|----------|-------------| | brand | string | no | Case-insensitive brand filter, e.g. `Tesla`. Max 100 characters. | | q | string | no | Free-text filter across brand, model and variant. Max 100 characters. | | token | string | yes | API authentication token. | Omit both filters to retrieve the whole catalog. #### cURL Example ```bash curl "https://api.carapi.dev/v1/ev-vehicles?token=YOUR_API_KEY&brand=Tesla" ``` #### JavaScript Example ```javascript const res = await fetch( 'https://api.carapi.dev/v1/ev-vehicles?q=model%203&token=YOUR_API_KEY' ); const { vehicles, attribution } = await res.json(); // Feed an id straight into the route planner const vehicleId = vehicles[0].id; // Required by the catalog license — display it alongside the data console.log(attribution); ``` #### Python Example ```python import requests resp = requests.get( "https://api.carapi.dev/v1/ev-vehicles", params={"token": "YOUR_API_KEY", "brand": "Tesla"}, ) for v in resp.json()["vehicles"]: print(v["id"], v["usableBatteryKwh"], "kWh") ``` #### Response (200) ```json { "attribution": "Vehicle data from Open EV Data", "count": 2, "vehicles": [ { "id": "tesla-model-3-lr-2021", "brand": "Tesla", "model": "Model 3", "variant": "Long Range Dual Motor", "releaseYear": 2021, "usableBatteryKwh": 75, "consumptionWhPerKm": 160, "dcMaxKw": 250, "dcPorts": ["ccs"] }, { "id": "tesla-model-y-lr-2022", "brand": "Tesla", "model": "Model Y", "variant": "Long Range Dual Motor", "releaseYear": 2022, "usableBatteryKwh": 75, "consumptionWhPerKm": 168, "dcMaxKw": 250, "dcPorts": ["ccs"] } ] } ``` #### Response Fields | Field | Type | Description | |-------|------|-------------| | attribution | string | **Required credit for the catalog** — a license condition, not metadata | | count | integer | Number of vehicles matching the filters | | vehicles[].id | string | Pass this as `vehicleId` to `/v1/ev-route` | | vehicles[].brand | string | Manufacturer | | vehicles[].model | string | Model name | | vehicles[].variant | string\|null | Trim or drivetrain variant, where the catalog distinguishes one | | vehicles[].releaseYear | integer\|null | Model year | | vehicles[].usableBatteryKwh | number | Usable capacity — what the planner works with, below the nominal pack size | | vehicles[].consumptionWhPerKm | number | Rated consumption, the planner baseline before speed, grade and temperature | | vehicles[].dcMaxKw | number | Peak DC charging power | | vehicles[].dcPorts | string[] | DC connectors the vehicle accepts, e.g. `ccs` | #### Error Codes - **400** `brand` or `q` longer than 100 characters - **403** Invalid or missing API token - **502** Upstream catalog unavailable - **500** Internal server error #### Important Notes - The catalog is small enough to fetch whole and cache on your side; it changes when models are added, not daily. - A vehicle absent from the catalog is not a blocker — describe it with `batteryKwh` and `consumptionWhKm` on the route planner instead. - Display the `attribution` string wherever you surface these specs. - Each request consumes 1 API credit. --- ## Account & Usage Endpoints (Free — Not Billed) These two introspection endpoints let you read your own quota/usage and the service's endpoint catalog **without spending a credit**. They require an API token (`?token=`) like every other endpoint, but they are **never billed** — the only governor is a rate limit. `/v1/account` is the right way to read your remaining balance instead of stashing the `X-RateLimit-Remaining` header from a paid call. ### Account Read your account's plan, entitlement and live usage in a single response. **Endpoint:** `GET /v1/account` **Cost:** Free (never debits a credit) | **Cache:** `no-store` Works **even when your monthly quota is exhausted** — a 100%-used account is the one that most needs to read its reset date — so this endpoint returns `200` where the billed endpoints would return `429`. #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | token | string | Yes | API authentication token (query param) | #### Request Example (cURL) ```bash curl -X GET "https://api.carapi.dev/v1/account?token=YOUR_API_KEY" ``` #### Response Example (200) ```json { "plan": "starter", "status": "active", "subscriptionStatus": "active", "usage": { "used": 3421, "limit": 10000, "remaining": 6579, "resetDate": "2026-07-01T00:00:00.000Z" }, "rateLimitPerMinute": 60 } ``` #### Response Fields - **plan** — your plan tier (e.g. `free`, `starter`, `professional`). - **status** — plan status (`active`, …). - **subscriptionStatus** — Stripe subscription status; `null` for free plans without a subscription. - **usage.used** — credits used this billing period. - **usage.limit** — your effective monthly credit limit. - **usage.remaining** — `max(0, limit - used)`. - **usage.resetDate** — when the current period ends and usage resets (ISO 8601), or `null`. - **rateLimitPerMinute** — your plan's per-minute rate limit for the paid data endpoints. #### Rate Limiting Free endpoints have no quota cost, so a rate limit is their only abuse governor: **1 request/second per account, with a burst of 5.** Exceeding it returns `429` with a `Retry-After` header and a message naming the free-endpoint limit (so it isn't confused with your paid quota). #### Error Codes - **403** Invalid or missing API token, or suspended account - **404** No plan found for this account - **429** Free account endpoint rate limit exceeded - **500** Internal server error --- ### Endpoints Catalog List every active endpoint together with its per-call credit cost — handy for showing pricing in your own UI or validating a slug before calling it. **Endpoint:** `GET /v1/endpoints` **Cost:** Free (never debits a credit) | **Cache:** `public, max-age=300` The catalog is identical for every customer and changes rarely, so it is served from memory and is safe to cache client- or CDN-side. #### Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | token | string | Yes | API authentication token (query param) | #### Request Example (cURL) ```bash curl -X GET "https://api.carapi.dev/v1/endpoints?token=YOUR_API_KEY" ``` #### Response Example (200) ```json { "endpoints": [ { "slug": "vin-decode", "name": "VIN Decode", "category": "vehicle", "description": "Decode a 17-character VIN into full vehicle specifications.", "creditCost": 1 } ] } ``` #### Response Fields - **endpoints[].slug** — stable endpoint identifier. - **endpoints[].name** — human-readable name. - **endpoints[].category** — grouping category, or `null`. - **endpoints[].description** — short description, or `null`. - **endpoints[].creditCost** — credits debited per successful call. #### Error Codes - **403** Invalid or missing API token, or suspended account - **429** Rate limit exceeded - **500** Internal server error --- ## Error Handling All endpoints return consistent error responses: ```json { "code": "invalid_token", "error": "Human readable error message" } ``` Authentication, quota and rate-limit errors (403, 429 and auth-layer 5xx) include a stable, machine-readable `code` field: `invalid_token`, `account_suspended`, `subscription_inactive`, `plan_not_found`, `quota_exceeded`, `rate_limited` or `internal_error`. Endpoint-specific validation errors (400, 404) carry only the `error` message. Use the free `/v1/account` endpoint to check remaining quota; it keeps working even when monthly usage reaches 100%. ### Common HTTP Status Codes | Code | Status | Description | |------|----------------------|-------------| | 200 | OK | Request successful | | 400 | Bad Request | Invalid parameters or request format | | 403 | Forbidden | Invalid or missing API token, suspended account, or inactive subscription | | 404 | Not Found | Resource not found | | 429 | Too Many Requests | Monthly quota exhausted (with resetDate) or rate limit exceeded (with Retry-After) | | 500 | Internal Server Error | Server error | | 503 | Service Unavailable | External service temporarily unavailable | --- ## Support - **Website:** https://carapi.dev - **Documentation:** https://docs.carapi.dev - **Email:** support@carapi.dev - **Dashboard:** https://carapi.dev/dashboard --- *Last updated: 2026-08-10*