Electricarly API
A REST interface for Electricarly data: brands, drives, and full car specs.
Base URL: https://api.electricarly.info
Format: JSON (Content-Type: application/json)
Authentication
Most endpoints require an API key passed via the X-API-Key header:
curl -H "X-API-Key: your-api-key-here" https://api.electricarly.info/api/brands
| Endpoint | Requires API Key |
|---|---|
GET /health |
❌ No |
GET /api/brands/* |
✅ Yes |
GET /api/drives/* |
✅ Yes |
GET /api/specs/* |
✅ Yes |
GET /api/images/* |
❌ No (signed URLs) |
POST /admin/reload |
✅ Yes |
Set the API_KEYS environment variable to enable authentication (comma-separated list).
Requesting API access
If you'd like access to the authenticated API endpoints, please send a request to: louizo79@gmail.com. Include a short description of your intended use and any non-production hostnames or IPs that will access the API.
Rate Limiting
All API requests are rate limited per 1-minute window.
| Scope | Limit | Notes |
|---|---|---|
/api/brands, /api/drives, /api/specs |
Depends on your API key's tier (see below) | Limited per X-API-Key, not per IP |
/api/images |
600 requests / minute | Limited per IP (no API key required) |
/admin/reload |
5 requests / minute | Requires API key |
| Everything else | 300 requests / minute | Limited per IP |
Per-key limits
Each API key has its own independent quota — one key's usage never affects another's.
| Tier | Requests / minute |
|---|---|
| Standard | 100 (default) |
| Custom / partner keys | Varies — see your key's agreement, or ask when requesting access |
Rate limit headers
Every response includes standard rate limit headers so you can track your usage:
| Header | Meaning |
|---|---|
RateLimit-Limit |
Your limit for the current window |
RateLimit-Remaining |
Requests remaining in the current window |
RateLimit-Reset |
Seconds until the window resets |
Exceeding the limit
If you exceed your limit, you'll receive a 429 Too Many Requests response:
{
"error": "rate_limited",
"message": "Too many requests, please try again later."
}
Wait until the window resets (see RateLimit-Reset) before retrying.
Health
GET /health
{
"ok": true,
"dataDir": "/path/to/output",
"loadedAt": "2024-11-01T12:34:56.789Z",
"counts": { "brands": 47, "drives": 47, "specs": 312 }
}
Image URL convention
Every image descriptor returned by the API has three fields:
| Field | Meaning | Example |
|---|---|---|
path |
On-disk path (POSIX) | data/images/specs/afeela/899_Afeela_1_AWD/main.jpg |
rel |
Relative to data/images/ |
specs/afeela/899_Afeela_1_AWD/main.jpg |
url |
API path to fetch the file | /api/images/specs/afeela/899_Afeela_1_AWD/main.jpg |
Rule: always use url in clients. path and rel are for reference / server-side work.
If ABSOLUTE_IMAGES=1, every url is rewritten to http(s)://<host>/api/images/... at serialization time, so cross-origin clients can use them directly.
Brands
GET /api/brands
| Query | Type | Default | Description |
|---|---|---|---|
q |
string | — | Substring match on name or slug |
sort |
string | name |
Sort field |
order |
string | asc |
asc or desc |
page |
integer | 1 |
Page number |
limit |
integer | 50 |
Items per page (max 200) |
{
"total": 47,
"page": 1,
"limit": 50,
"items": [
{
"slug": "tesla",
"brandId": "1",
"name": "Tesla",
"models": 17,
"drivetrains": 76,
"drivesCount": 76,
"logoPath": "data/images/brands/tesla.png",
"logoLocal": "brands/tesla.png",
"logoUrl": "/api/images/brands/tesla.png"
}
]
}
GET /api/brands/:slug
Returns the brand with its drives array inlined.
GET /api/brands/:slug/drives
| Query | Type | Default | Description |
|---|---|---|---|
q |
string | — | Substring on display/model name |
year |
string | — | Substring match on the year range |
mainOnly |
boolean | false |
Only main variants |
page |
integer | 1 |
Page number |
limit |
integer | 100 |
Items per page (max 500) |
Each drive has:
{
"driveId": "48",
"slug": "tesla_model_x_100d_2017",
"displayName": "Model X 100D",
"year": "2017 - 2019",
"isMain": true,
"thumbPath": "data/images/specs/tesla/48_Tesla_Model_X_100D/thumb.jpg",
"thumbLocal": "specs/tesla/48_Tesla_Model_X_100D/thumb.jpg",
"thumbUrl": "/api/images/specs/tesla/48_Tesla_Model_X_100D/thumb.jpg",
"url": { "slug": "tesla_model_x_100d_2017", "id": "48" }
}
GET /api/brands/:slug/specs
Paged list of specs for a brand.
Drives
GET /api/drives/:brandSlug/:driveId
Drive + spec summary.
{
"drive": { "driveId": "48", "displayName": "Model X 100D", "thumbUrl": "/api/images/…" },
"specSummary": {
"fullModelName": "Tesla Model X 100D",
"driveYear": "2017 - 2019",
"spotlight": { "power-hl": "386", "range-hl": "523" },
"thumbUrl": "/api/images/…"
}
}
Specs
GET /api/specs/:brandSlug/:driveId
Full spec object with spotlight, specs, sections, trims, images, and localImages.
{
"brandSlug": "afeela",
"driveId": "899",
"slug": "afeela_1_awd_2026",
"fullModelName": "Afeela 1 AWD",
"images": [
{
"path": "data/images/specs/afeela/899_Afeela_1_AWD/afeela-1-side-1.jpg",
"rel": "specs/afeela/899_Afeela_1_AWD/afeela-1-side-1.jpg",
"url": "/api/images/specs/afeela/899_Afeela_1_AWD/afeela-1-side-1.jpg"
}
],
"localImages": [
"specs/afeela/899_Afeela_1_AWD/afeela-1-side-1.jpg"
]
}
GET /api/specs/by-slug/:slug
Keyed by the o slug.
GET /api/specs/search
| Query | Type | Default | Description |
|---|---|---|---|
q |
string | — | Substring on full model name |
brand |
string | — | Restrict to one brand slug |
minRange |
number | — | Minimum WLTP range in km |
minPower |
number | — | Minimum power in kW |
page |
integer | 1 |
Page number |
limit |
integer | 25 |
Items per page (max 100) |
Each hit includes thumbUrl (first image) for quick rendering.
Images
GET /api/images/<rel-path>
<rel-path> is relative to data/images/:
GET /api/images/specs/afeela/899_Afeela_1_AWD/afeela-1-side-1.jpg
GET /api/images/brands/tesla.png
Returns the raw file with its content type. Path traversal is rejected (400) and missing files return 404.
Errors
All errors are JSON: { "error": "brand_not_found" }.
| Status | Meaning |
|---|---|
| 400 | Bad request (missing/traversal path) |
| 404 | Resource not found |
| 429 | Rate limit exceeded |
| 500 | Internal error |