ShopBaseComplete shop management for auto repair shops · $249/mo
Try ShopBase →

API Documentation

Open Labor Project exposes a REST API for automotive labor times, torque specs, fluid capacities, OBD-II DTCs, batteries, parts, and estimates. All endpoints return JSON. Authentication is a single header. Start with a free Hobbyist key, upgrade tiers when you outgrow it.

New here? Start hereQuickstartAuthenticationStoring your key safelyResponse shapeError codesRate limitsEndpoint referenceCode examplesCommon gotchas

New here? Start here.

If you've never used an API before, this section walks you through your first successful call — what to click, where to paste, and what success looks like. No assumptions.

What's an API?Think of it as a URL you ask for data from. You send a request (with your key), our server sends back JSON (a structured blob of text). Your code reads that JSON and does whatever with it — shows it on a page, saves it to a spreadsheet, feeds it to AI.
1Get your API key

Go to openlaborproject.com/developers and request a free Hobbyist key (no credit card needed). You'll get an email with a long string like 12345678-1234-1234-1234-123456789abc that's your key. Copy it somewhere safe (a sticky note in your password manager is fine for now; we'll show you the professional way to store it later).

2Open a terminal on your computer

A "terminal" is a text-only window where you type commands. Every OS has one built in — you don't need to install anything.

Windows
Press Win + R, type powershell, hit Enter. A dark blue window opens — that's PowerShell. Click in it so you can type.
Mac
Press Cmd + Space, type terminal, hit Enter. A small window with a prompt opens.
Linux
You almost certainly know how. If not: Ctrl + Alt + T on most desktops.
3Paste this command (replace the key first)

Copy the line below, paste it into your terminal, replace YOUR_KEY_HERE with the actual key you got in step 1, then hit Enter.

curl "https://openlaborproject.com/api/v1/labor-times?make=ford&model=f-150&year=2017" -H "x-api-key: YOUR_KEY_HERE"
Windows noteIn PowerShell, curl is an alias for a different command and may not handle this exactly right. If you see an error, replace curl with curl.exe — that runs the real curl shipped with Windows 10/11.
4What success looks like

You should see a wall of JSON dump into the terminal. Don't worry about reading it — if you see "data": and a bunch of jobs like "Brake Pads — Front", congrats, the API works for you. Move on to the code examples and start integrating.

If instead you see one of these:

  • "code": "MISSING_API_KEY" — you didn't replace YOUR_KEY_HERE with your actual key. Go back to step 3.
  • "code": "INVALID_API_KEY" — the key is wrong or has a typo. Copy it fresh from the email.
  • An HTML response (lots of <tags>) — Cloudflare blocked the request because the header didn't make it through. Check the Windows note above, or jump to Common gotchas.
  • curl: command not found — you're on a really old setup. Use the JavaScript example in Code examples instead.
That's it — you've made your first API call. The rest of this page is reference material. You don't need to read it top-to-bottom; jump to whatever you need.

Quickstart

1. Get a free key at openlaborproject.com/developers (no credit card for Hobbyist).

2. Send a test request:

curl "https://openlaborproject.com/api/v1/labor-times?make=ford&model=f-150&year=2017" \
  -H "x-api-key: YOUR_KEY_HERE"

You should get a JSON response with labor times grouped by engine variant. If you get 401 INVALID_API_KEY, double-check the key value. If you get 403 with HTML, you've hit Cloudflare — see Common gotchas.

Authentication

Every request needs an API key passed via one of two headers. Both work identically — pick whichever fits your HTTP client.

What's a header?An HTTP header is a small piece of metadata your code sends along with every request — like the "From:" line on a letter. The API key lives in a header named x-api-key so our server knows it's you. It is NOT a query parameter (?key=...) and NOT a script tag in HTML.

Option A — x-api-key header

x-api-key: 12345678-1234-1234-1234-123456789abc

Option B — Authorization: Bearer

Authorization: Bearer 12345678-1234-1234-1234-123456789abc

The Bearer form is convenient if you're using a generic API client (Postman, Insomnia, an SDK builder) that has a built-in Bearer auth field.

Keep the key secret. Treat it like a password — don't commit it to a public repo, don't embed it in client-side JavaScript, don't paste it into Slack channels with screen-share. We rotate keys on request if one leaks; contact [email protected].

Storing your key safely

The pro move is to put the key in an environment variable on your computer or server, then have your code read it from there. That way the key never appears in your source files — so it can't accidentally end up on GitHub.

What's an environment variable?A named value your operating system stores for you. Programs (including your code) can read it without you having to type the value into the program itself. Convention: name it OLP_API_KEY so it's obvious what it is.

Set the variable (one-time per machine)

Windows (PowerShell)
# Persistent (survives reboots):
[System.Environment]::SetEnvironmentVariable(
  'OLP_API_KEY','YOUR_KEY_HERE','User')

# Then close and reopen PowerShell.
# Verify it stuck:
$env:OLP_API_KEY
Mac / Linux (bash or zsh)
# Add this line to ~/.zshrc (Mac default)
# or ~/.bashrc (most Linux):
export OLP_API_KEY="YOUR_KEY_HERE"

# Reload your shell:
source ~/.zshrc    # or ~/.bashrc

# Verify:
echo $OLP_API_KEY
Node.js project (.env file)
# Create a file named .env in your project root:
OLP_API_KEY=YOUR_KEY_HERE

# Add ".env" to your .gitignore so it
# never gets committed!

# In your code:
require('dotenv').config();
process.env.OLP_API_KEY

Then use it in your code

Once the variable is set, your code reads it without ever knowing the actual key value:

// Node / JavaScript
fetch(url, { headers: { 'x-api-key': process.env.OLP_API_KEY } })

# Python
requests.get(url, headers={'x-api-key': os.environ['OLP_API_KEY']})

# Bash / curl
curl -H "x-api-key: $OLP_API_KEY" "$url"

Response shape

Every endpoint returns the same envelope. Success or failure, the three top-level keys are always present.

{
  "data":  { ... } | null,    // the payload on 2xx; null on 4xx/5xx
  "meta":  {
    "timestamp": "2026-06-09T18:00:00.000Z",
    // endpoint-specific extras (count, region, rateLimit, etc.)
  },
  "error": null | {
    "code":    "MACHINE_READABLE_CODE",
    "message": "Human-readable explanation."
  }
}

Inspect error.code (not the HTTP status alone) to branch on failure mode — codes are stable, messages may evolve.

Error codes

HTTPCodeWhat it means
400MISSING_PARAMSRequired query param missing. Read the message.
400INVALID_YEARYear must be YYYY or YYYY-YYYY.
401MISSING_API_KEYNo x-api-key or Bearer header sent.
401INVALID_API_KEYKey revoked, malformed, or doesn't exist.
402PAYMENT_REQUIREDSubscription past due. Update payment to restore access.
403TIER_LOCKEDFeature requires a higher tier (e.g. webhooks need Business+).
404NOT_FOUNDNo data for this vehicle/job combination.
405METHOD_NOT_ALLOWEDUse GET. All v1 endpoints are GET-only.
429RATE_LIMIT_EXCEEDEDDaily limit hit. Resets at midnight UTC.
429BURST_LIMIT_EXCEEDEDPer-minute limit hit. See Retry-After header.
500INTERNAL_ERROROur problem. Retry with backoff.

Rate limits

Two limits run in parallel: a daily cap (resets midnight UTC) and a per-minute burst cap (resets every full minute). Hitting either returns 429.

TierDailyPer minutePrice
Hobbyist (testing)710Free
Builder1,000300$49/mo
Business10,0001,000$149/mo
Enterprise50,000+5,000+$499+/mo

Headers returned on every response

X-RateLimit-Tier:              builder
X-RateLimit-Limit-Daily:       1000
X-RateLimit-Remaining-Daily:   847
X-RateLimit-Reset-Daily:       2026-06-10T00:00:00.000Z
X-RateLimit-Limit-Minute:      300
X-RateLimit-Remaining-Minute:  295
Retry-After:                   42         # only on 429 BURST_LIMIT_EXCEEDED

Watch Remaining-Daily and back off proactively rather than waiting for a 429. The legacy X-RateLimit-Limit / X-RateLimit-Remaining headers are still sent as daily-value aliases for backward compatibility.

Endpoint reference

All endpoints are GET, hosted at https://openlaborproject.com, and return JSON. Query params are URL-encoded.

GET/api/v1/labor-times

Labor hours per repair job for a vehicle.

Required:make, model, yearOptional:engine, job, region
/api/v1/labor-times?make=ford&model=f-150&year=2017&engine=3.5l-v6-ecoboost
Response shape
{
  "data": {
    "vehicle": { "make": "Ford", "model": "F-150", "yearRange": "2017-2017", "engine": "3.5L V6 EcoBoost" },
    "laborTimes": [
      { "job": "Brake Pads — Front", "jobSlug": "brake-pads-front", "category": "brakes",
        "hours": 1.3, "lowRange": 1.1, "highRange": 1.6, "confidence": "high" },
      // ...
    ]
  },
  "meta": { "count": 287, "region": "US", "timestamp": "2026-06-09T18:00:00.000Z" },
  "error": null
}
GET/api/v1/torque-specs

Torque values for fasteners on a job.

Required:make, model, yearOptional:engine, job, region
/api/v1/torque-specs?make=ford&model=f-150&year=2017&engine=3.5l-v6-ecoboost&job=brake-pads-front
Response shape
{
  "data": {
    "vehicle": { "make": "Ford", "model": "F-150", "engine": "3.5L V6 EcoBoost" },
    "torqueSpecs": [
      { "job": "Brake Pads — Front", "component": "Caliper Bracket Bolt",
        "nm": 175, "lbFt": 129, "angleDegrees": null, "sequence": null,
        "isCritical": true, "notes": "Use new bolts", "confidence": "oem_verified" }
    ]
  },
  "meta": { "count": 4, "region": "US" }
}
GET/api/v1/fluid-specs

Fluid types and capacities by system (engine oil, coolant, ATF, etc.).

Required:make, model, yearOptional:engine, region
/api/v1/fluid-specs?make=toyota&model=camry&year=2018
GET/api/v1/dtc-codes

OBD-II diagnostic trouble codes. Lookup by vehicle or by code.

Required:(make + model + year) OR codeOptional:engine, region
/api/v1/dtc-codes?code=P0301 or /api/v1/dtc-codes?make=honda&model=civic&year=2014
GET/api/v1/battery-specs

OE battery group size, CCA, AGM/flooded, replacement specs.

Required:make, model, yearOptional:engine, region
/api/v1/battery-specs?make=bmw&model=328i&year=2015
GET/api/v1/vehicles

Vehicle catalog — list engines/years for a make+model. Discovery helper for getting valid slugs to send to the other endpoints. Returns minimal fields.

Required:make, modelOptional:region
/api/v1/vehicles?make=ford&model=f-150
GET/api/v1/enginesBuilder+

Rich engine identification: OEM engine code, fuel type, forced induction, transmission, drivetrain — everything you need to identify the variant beyond NHTSA vPIC.

Required:make, modelOptional:year, region
/api/v1/engines?make=ford&model=f-150&year=2017
Response shape
{
  "data": {
    "make": "Ford",
    "model": "F-150",
    "engines": [
      {
        "yearStart": 2017, "yearEnd": 2017, "yearRange": "2017",
        "engine":          "3.5L V6 EcoBoost",
        "engineSlug":      "3.5l-v6-ecoboost",
        "engineCode":      "ECOBOOST-35",
        "fuelType":        "gasoline",
        "forcedInduction": "twin-turbo",
        "transmission":    "automatic",
        "drivetrain":      "4wd"
      }
    ]
  },
  "meta": { "count": 4, "region": "US" }
}
GET/api/v1/parts

OE/aftermarket part numbers and price ranges for a job on a vehicle.

Required:make, model, year, jobOptional:engine, region
/api/v1/parts?make=ford&model=f-150&year=2017&job=brake-pads-front
GET/api/v1/estimate

Combined labor + parts estimate for a job. Auto-generates labor if missing.

Required:make, model, year, jobOptional:engine, region
/api/v1/estimate?make=ford&model=f-150&year=2017&job=brake-pads-front
GET/api/v1/dvi-items

Digital Vehicle Inspection catalog — every inspection item we track.

Required:(none)Optional:category
/api/v1/dvi-items?category=brakes
GET/api/v1/dvi-lookup

Lookup labor/torque/fluid data for a single DVI inspection item on a vehicle.

Required:item, make, model, yearOptional:engine, region
/api/v1/dvi-lookup?item=brake-pads-front&make=ford&model=f-150&year=2017

Code examples

Same request — labor times for a 2017 Ford F-150 3.5L EcoBoost — in the three most common languages.

cURL

curl "https://openlaborproject.com/api/v1/labor-times?make=ford&model=f-150&year=2017&engine=3.5l-v6-ecoboost" \
  -H "x-api-key: $OLP_API_KEY"

JavaScript / Node (fetch)

const res = await fetch(
  'https://openlaborproject.com/api/v1/labor-times?make=ford&model=f-150&year=2017&engine=3.5l-v6-ecoboost',
  { headers: { 'x-api-key': process.env.OLP_API_KEY } }
);

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`OLP ${error.code}: ${error.message}`);
}

const { data, meta } = await res.json();
console.log(`${data.laborTimes.length} jobs for the ${data.vehicle.engine}`);

// Back off if we're close to the daily cap
const remaining = parseInt(res.headers.get('X-RateLimit-Remaining-Daily'), 10);
if (remaining < 50) console.warn('Approaching daily limit:', remaining);

Python (requests)

import os, requests

r = requests.get(
    'https://openlaborproject.com/api/v1/labor-times',
    params={'make': 'ford', 'model': 'f-150', 'year': 2017, 'engine': '3.5l-v6-ecoboost'},
    headers={'x-api-key': os.environ['OLP_API_KEY']},
    timeout=15,
)

if r.status_code == 429:
    retry = int(r.headers.get('Retry-After', '60'))
    raise SystemExit(f'Rate limited, retry in {retry}s')

r.raise_for_status()
data = r.json()['data']
print(f"{len(data['laborTimes'])} jobs for the {data['vehicle']['engine']}")

Common gotchas

Cloudflare bot challenge (403 with HTML body)

If you get a 403 response that looks like HTML instead of JSON, you've hit our Cloudflare bot challenge — meaning your request arrived without the API key header. Cloudflare lets anything with a valid x-api-key or Authorization: Bearer header through. Most often: a proxy or SDK stripped the header. Confirm by inspecting the request right before it's sent.

Make and model slugs are lowercase, hyphenated

ford, not Ford. f-150, not F-150 or F150. Pull the slug list from /api/v1/vehicles if you're unsure.

Year can be single or range

year=2017 matches that single model year. year=2015-2020 matches any vehicle whose year range overlaps. Anything else returns 400 INVALID_YEAR.

Engine slug is the canonical form, not a display string

3.5l-v6-ecoboost, not 3.5L V6 EcoBoost. Hit /api/v1/vehicles?make=…&model=… to list engine slugs for a model.

404 NOT_FOUND vs. empty result

We return 404 with NOT_FOUND when the vehicle/job combo has no data — not a 200 with an empty array. Branch on error.code to distinguish "we don't have it yet" from "your request was malformed".

Premium features and grandfathered keys

Some features (advanced filtering, bulk lookup, webhooks, bulk export) require a paid tier and return 403 TIER_LOCKED on Hobbyist. The error payload includes feature and required_tier so you can prompt an upgrade.

Stuck? Email [email protected] with the failing request URL, the response body, and the response headers — most issues are diagnosed in one round-trip.