Developers
SC01Check API
Put Tesla VIN reports — Supercharging status, Full Self-Driving, battery, factory options, recalls — plus CARFAX and AutoCheck history into your own site, inventory system or listing feed. One REST endpoint set, JSON in and out, billed from the same credits you already buy.
Same credits
No API plan, no separate wallet, no minimum. A call costs exactly what the same action costs on the site.
Cache-aware
A VIN already in our system answers without a fresh upstream pull, and re-reading a report you own is always free.
Built for lots
Batch up to 50 VINs a call. If credits run out mid-batch it stops and tells you exactly which VINs are left.
On this page
Getting started · Endpoint index · What things cost · Endpoints in detail · Understanding the payload · A complete integration · Submitting listings · Errors · Versioning & stability · Things worth knowing
Getting started
Create a key at API keys in your account. Keys look like sc01_live_… and are shown once — we store a hash, so a lost key must be revoked and replaced rather than recovered.
Send it as a bearer token on every request:
curl -H 'Authorization: Bearer sc01_live_YOUR_KEY' \
https://sc01check.com/api/v1/ping {
"ok": true,
"pong": true,
"account": "you@dealership.com",
"key": "Inventory sync",
"credits_charged": 0,
"credits_remaining": 480
} Base URL https://sc01check.com/api/v1 · HTTPS only · JSON only. Every successful response carries credits_charged and credits_remaining, so you can reconcile spend without a second call.
Endpoint index
Everything, on one screen. Base https://sc01check.com/api/v1.
| Endpoint | Cost | What it is for |
|---|---|---|
| GET /ping | free | Check a key works. |
| GET /account | free | Credit balance and 30-day usage. |
| GET /vin/{VIN}/json | 1 credit | Full payload to render in your own pages. |
| GET /vin/{VIN}/link | 1 credit | A link to the report here, for a button on your listing. |
| GET /vin/{VIN} | 1 credit | Same as /json. Add ?share=1 to also get a link. |
| GET /vin/{VIN}/history | free | What history exists, what you own, exact price each. |
| POST /history/{VIN} | varies | Buy CARFAX and/or AutoCheck. |
| GET /history/{VIN}/{kind} | free | Retrieve a report you own; ?format=html for the document. |
| POST /batch | 1 credit / VIN | Up to 50 VINs at once. |
| POST /vin/{VIN}/listing | free | Put a car on our public for-sale board. |
Re-reading anything this account has already paid for is always free — retries after a timeout never double-charge.
What things cost
| Action | Cost |
|---|---|
| VIN report, VIN not in our system | 1 credit |
| VIN report, VIN already in our system | 1 credit (no upstream call) |
| Re-reading a VIN your account has already run | Free |
| Forced fresh pull (?fresh=1) | 1 credit |
| CARFAX or AutoCheck, not on file | See /vin/{VIN}/history |
| CARFAX or AutoCheck already on file | Discounted — same endpoint quotes it |
| Re-reading a report you own | Free, forever |
Quote before you buy: GET /vin/{VIN}/history returns the exact price for each report on that VIN, and how many records the provider holds — so you never spend on an empty document. Credits never expire.
Endpoints
GET /api/v1/ping
Checks a key. Costs nothing. Use it in your deploy smoke test.
GET /api/v1/account
Credit balance plus your last 30 days of API usage.
{
"ok": true,
"account": { "email": "you@dealership.com", "credits": 480 },
"usage_30d": { "calls": 1204, "credits": 96 }
}
Pick your integration style
Two shapes of the same lookup, so you do not have to read a flag to get what you want. They cost the same, because the same work happens behind both.
| Endpoint | Gives you | Use when |
|---|---|---|
| GET /vin/{VIN}/json | The full payload — report, display, highlights | You are rendering the car's details inside your own pages. |
| GET /vin/{VIN}/link | A URL and the vehicle name | You just want a “View EV report” button on your listing that opens the full report here — no rendering work at all. |
curl -H 'Authorization: Bearer sc01_live_YOUR_KEY' \
https://sc01check.com/api/v1/vin/5YJSA1E26HF000000/link {
"ok": true,
"vin": "5YJSA1E26HF000000",
"url": "https://sc01check.com/s/8fK2n4Qb7Lm9",
"vehicle": "2017 Tesla Model S 100D",
"charged": "paid",
"credits_charged": 1,
"credits_remaining": 479
} The link is permanent, needs no account to open, and the same URL comes back on later calls for that VIN — safe to store next to your listing. Label it however you like: “View EV report”, “Supercharging status”, “Free VIN report”.
GET /api/v1/vin/{VIN}
The main call. Returns the full decoded report. Add ?fresh=1 to force a new upstream pull instead of serving what we hold.
curl -H 'Authorization: Bearer sc01_live_YOUR_KEY' \
https://sc01check.com/api/v1/vin/5YJSA1E26HF000000 {
"ok": true,
"vin": "5YJSA1E26HF000000",
"charged": "paid",
"report": {
"display_name": "2017 Model S 100D",
"summary": { "model": "MODELS", "trim": "100D", "model_year": 2017,
"paint": "PPSW", "interior": "IBE00", "wheels": "WT19" },
"supercharging": { "code": "SC01", "label": "Free Supercharging (transfers)",
"transferable": true },
"fsd": { "owned": true, "verdict": "owned" },
"autopilot": { "level": "fsd", "label": "Full Self-Driving" },
"battery": { … },
"headlights": { "matrix": true },
"seating": { "count": 5 },
"options": [ … ],
"recalls": [ … ]
},
"credits_charged": 1,
"credits_remaining": 479
}
You do not have to decode anything
Tesla's own data is codes and internal enums — whether Full Self-Driving is owned or a monthly subscription is not a field, it is a conclusion drawn from several. Working that out is our job, not yours, so every VIN response carries three layers and you pick one:
| Layer | Use it when |
|---|---|
| highlights | You want badges on a listing page. An ordered array of things worth saying out loud, already filtered — loop and print. |
| display | You want specific fields. Flat, documented, each one { value, label }. |
| report | You want everything, including raw option codes and our internal structure. |
The contract: value is a stable enum — filter, store and branch on it. label is a sentence for humans and may be reworded, so never key logic off it.
highlights — badges, ready to render
"highlights": [
{ "key": "supercharging", "label": "Free Supercharging — transfers", "tone": "good" },
{ "key": "fsd", "label": "Full Self-Driving — owned", "tone": "good" },
{ "key": "headlights", "label": "Matrix headlights", "tone": "good" },
{ "key": "seats", "label": "5-seat", "tone": "neutral" },
{ "key": "recalls", "label": "1 open recall", "tone": "caution" }
] tone is good / neutral / caution — colour them however you like. A car with nothing notable returns an empty array rather than a row of "None" chips, so you can render it blind.
display — the field reference
| Field | value can be | Notes |
|---|---|---|
| supercharging | free_transferable · free_not_transferable · free_unknown_transfer · pay_per_use · unknown | The question this site exists for. Only free_transferable means a buyer keeps it. |
| fsd | owned · owned_not_transferable · subscription · removed · none · unknown | The one people get wrong. subscription ends when the car sells — it is not an included feature. Also carries transfers and our raw verdict. |
| autopilot | fsd · enhanced · basic · unknown | Capability tier, independent of who paid for it. |
| headlights | true · false · null | Matrix headlights. null means no confirmed answer for that build — we do not guess. |
| paint, interior, wheels | option code, or null | value is the raw code for filtering, label is the human name. |
| seats | integer, or null | 5, 6 or 7. |
| battery_health | number, or null | Percent of original capacity where we can establish it. |
| original_msrp | cents, or null | List price of the configuration as it transfers. Tesla's own total includes anything the car currently has access to, so on a car with SUBSCRIBED Full Self-Driving that figure is thousands too high for a buyer — this one has it removed. When something was removed you also get list_total (Tesla's figure) and excludes. |
| open_recalls | integer | Count; the full list is in report.recalls. |
| vehicle, model_year, model, trim | strings / integer | vehicle is the printable name. |
"display": {
"vehicle": { "value": "2017 Tesla Model S 100D", "label": "2017 Tesla Model S 100D" },
"supercharging": { "value": "free_transferable",
"label": "Free Supercharging — transfers to the next owner",
"code": "SC01" },
"fsd": { "value": "owned",
"label": "Full Self-Driving — owned outright, transfers with the car",
"transfers": true, "verdict": "OWNED_OUTRIGHT", "confidence": "high" },
"autopilot": { "value": "fsd", "label": "Full Self-Driving" },
"headlights": { "value": true, "label": "Matrix headlights" },
"paint": { "value": "PPSW", "label": "Pearl White Multi-Coat" },
"interior": { "value": "IBE00", "label": "Black Premium Interior" },
"wheels": { "value": "WT19", "label": "19\" Silver Wheels" },
"seats": { "value": 5, "label": "5-seat" },
"battery_health":{ "value": 92.4, "label": "92.4% of original capacity" },
"original_msrp": { "value": 11350000, "label": "$113,500 as originally configured" },
"open_recalls": { "value": 1, "label": "1 open recall" }
}
Linking back: ?share=1
Add ?share=1 to a VIN call and the response gains share_url — a permanent, free-to-read link to the full rendered report on our site, no account needed for whoever opens it. Put it on your listing as "full VIN report" and your buyer gets the whole thing, including the Supercharging explanation, without you rebuilding any of it. The same URL comes back on later calls for that VIN, so it is stable to store.
GET /api/v1/vin/{VIN}/history
What history exists for a VIN, what you already own, and what each would cost. Costs nothing to ask.
{
"ok": true,
"vin": "5YJSA1E26HF000000",
"reports": {
"carfax": { "owned": false, "on_file": true, "record_count": 24,
"price": { "cents": 400, "credits": 16 } },
"autocheck": { "owned": false, "on_file": false, "record_count": 11,
"price": { "cents": 800, "credits": 32 } }
}
}
POST /api/v1/history/{VIN}
Orders history reports, paid from your credits.
curl -X POST -H 'Authorization: Bearer sc01_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"kinds":["carfax","autocheck"]}' \
https://sc01check.com/api/v1/history/5YJSA1E26HF000000 {
"ok": true,
"vin": "5YJSA1E26HF000000",
"ordered": ["carfax", "autocheck"],
"already_owned": [],
"retrieve": "https://sc01check.com/api/v1/history/5YJSA1E26HF000000/{kind}",
"credits_charged": 48,
"credits_remaining": 431
} Body fields: kinds (array, defaults to both) and mode — auto (default; buys what you lack at the best available price), access (only what is already on file), or fresh (force a new pull even if we hold a copy).
GET /api/v1/history/{VIN}/{kind}
Retrieves a report you own — carfax or autocheck. Free and unlimited once owned. Add ?format=html for the provider's full document, which you can render in an iframe or store; omit it for metadata only.
{
"ok": true,
"vin": "5YJSA1E26HF000000",
"kind": "carfax",
"provider": "CARFAX",
"year_make_model": "2017 Tesla Model S",
"record_count": 24,
"fetched_at": "2026-08-04 19:22:10",
"bytes": 318442
}
POST /api/v1/batch
Up to 50 VINs in one call — the dealer inventory path.
curl -X POST -H 'Authorization: Bearer sc01_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"vins":["5YJSA1E26HF000000","7SAYGDEE9PF000000"]}' \
https://sc01check.com/api/v1/batch Each VIN gets its own result object, so one bad VIN never fails the batch. If credits run out partway the response is a 402 carrying stopped_early, everything processed so far, and remaining_vins — top up and resend exactly that array.
A complete integration
What a dealer sync actually looks like end to end: decode the lot, keep a link on every listing page, and take cars off our board when they sell. Roughly thirty lines.
PHP
<?php
const SC01_KEY = 'sc01_live_YOUR_KEY';
const SC01_BASE = 'https://sc01check.com/api/v1';
function sc01(string $method, string $path, ?array $body = null): array {
$ch = curl_init(SC01_BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . SC01_KEY,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
CURLOPT_TIMEOUT => 30,
]);
$raw = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return [$status, json_decode((string) $raw, true) ?: []];
}
// 1. Decode the lot, 50 at a time.
foreach (array_chunk($yourVins, 50) as $chunk) {
[$status, $r] = sc01('POST', '/batch', ['vins' => $chunk]);
if ($status === 402) { // out of credits: resume later, exactly here
error_log('SC01Check credits exhausted; ' . count($r['remaining_vins']) . ' VINs left');
break;
}
foreach ($r['results'] as $row) {
if (empty($row['ok'])) { continue; } // bad VIN, keep going
// 2. Store the badges and the link against your listing.
saveToYourDb($row['vin'], [
'headline' => $row['display']['vehicle']['label'],
'badges' => $row['highlights'], // [{key,label,tone}, …]
'sc_status' => $row['display']['supercharging']['value'],
'fsd_status' => $row['display']['fsd']['value'],
]);
}
}
// 3. Give each listing a "View EV report" button.
[, $link] = sc01('GET', '/vin/' . $vin . '/link');
echo '<a href="' . htmlspecialchars($link['url']) . '">View EV report</a>';
// 4. Optionally put the car on the public board.
sc01('POST', '/vin/' . $vin . '/listing', [
'listing_url' => 'https://yourdealership.com/inventory/' . $stockNumber,
'price' => 48995,
'miles' => 31200,
]);
// 5. When it sells, take it down.
sc01('POST', '/vin/' . $vin . '/listing', ['public' => false]);
Python
import requests
KEY = "sc01_live_YOUR_KEY"
BASE = "https://sc01check.com/api/v1"
H = {"Authorization": f"Bearer {KEY}"}
def chunks(seq, n):
for i in range(0, len(seq), n):
yield seq[i:i + n]
for chunk in chunks(your_vins, 50):
r = requests.post(f"{BASE}/batch", json={"vins": chunk}, headers=H, timeout=30)
data = r.json()
if r.status_code == 402: # out of credits
print("resume with:", data["remaining_vins"])
break
for row in data["results"]:
if not row.get("ok"):
continue
save_to_your_db(
vin = row["vin"],
headline = row["display"]["vehicle"]["label"],
badges = row["highlights"],
sc_status = row["display"]["supercharging"]["value"],
fsd_status = row["display"]["fsd"]["value"],
)
# A link for the listing page
link = requests.get(f"{BASE}/vin/{vin}/link", headers=H).json()["url"]
# Put it on the public board, and pull it when sold
requests.post(f"{BASE}/vin/{vin}/listing", headers=H,
json={"listing_url": listing_url, "price": 48995, "miles": 31200})
requests.post(f"{BASE}/vin/{vin}/listing", headers=H, json={"public": False})
Rendering the badges
highlights is built to be looped without inspection — it is already filtered to what is worth showing, and empty when a car has nothing notable.
<?php foreach ($car['badges'] as $b): ?>
<span class="badge badge--<?= htmlspecialchars($b['tone']) ?>">
<?= htmlspecialchars($b['label']) ?>
</span>
<?php endforeach; ?>
How often to sync
- Once per car, when it arrives. A VIN's factory build never changes — paint, wheels, seats, matrix headlights and original MSRP are fixed for life. Store them.
- Re-check before a price drop or a long listing using ?fresh=1. Supercharging and FSD status CAN change while a car sits, because they follow the owner's account, not the metal.
- Never poll. Re-reading a VIN you already ran is free, so a nightly full re-sync costs nothing — but it also tells you nothing new. Sync arrivals, not inventory.
Every failure uses this shape and one of the codes above — there is no endpoint that returns an HTML error page, and no code outside this list. Branch on error.code, show error.message to your own staff; the messages are written to be read by a human debugging an integration. A 402 and a 429 are the only two worth retrying automatically.
Versioning & stability
- The version is in the path. /api/v1 keeps its promises: we add fields, we do not remove or rename them, and we do not change what an existing value means. Anything that would break you gets a /v2.
- Parse leniently. New fields will appear in responses — treat unknown keys as harmless rather than erroring on them.
- value enums are the contract. label strings are for humans and get reworded; never branch on them.
- A field can be null when we do not have a confident answer — that is deliberate, not missing data. Render nothing rather than guessing, the same way we do.
- Deprecations, if they ever happen, get an email to every account with an active key and at least 90 days.
Optional: put your cars on our board
If you are listing cars for sale anyway, you can push them onto our public for-sale board as part of the same sync. Buyers browsing there read the full report — plus any CARFAX or AutoCheck you have pulled — free, and the listing links back to wherever you sell.
POST /api/v1/vin/{VIN}/listing
curl -X POST -H 'Authorization: Bearer sc01_live_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"listing_url":"https://yourdealership.com/inventory/12345",
"price":48995,"miles":31200}' \
https://sc01check.com/api/v1/vin/5YJSA1E26HF000000/listing {
"ok": true,
"vin": "5YJSA1E26HF000000",
"public_status": "pending",
"message": "Submitted. A person checks the link before it appears — usually the same day."
} | Field | Required | Notes |
|---|---|---|
| price | Yes | Asking price in dollars. |
| miles | Yes | Odometer. |
| listing_url | One of these | Where the car is for sale — your own site is fine. |
| keysavvy_url | One of these | If you use KeySavvy for the transaction. |
| public | No | Defaults true. Send false to update the details but keep the car off the board — or to pull one that has sold. |
- Run /vin/{VIN}/json or /link first — a board listing shows the car's report, so we need to have it. Posting a listing for a VIN we have not loaded returns 409 vin_not_loaded rather than quietly running a lookup you did not ask for.
- Listing costs nothing. No credits, ever.
- New listings are reviewed by a person before they appear — we open the link. Editing the link on a live listing sends it back for another look; price and mileage edits do not.
- When a car sells, send {"public": false}. A board full of sold cars is worthless to everyone, and buyers can report stale listings.
Errors
Errors are always JSON, never an HTML page, and always shaped the same way:
{
"ok": false,
"error": { "code": "insufficient_credits",
"message": "Not enough credits for that order." }
} | Status | Code | Meaning |
|---|---|---|
| 400 | invalid_vin | Not a valid 17-character VIN. Nothing charged. |
| 400 | lookup_failed | Valid VIN we cannot decode — a non-Tesla on a Tesla endpoint, or an unsupported model. Nothing charged. |
| 401 | unauthorized | Missing, malformed, revoked or unknown key. |
| 402 | insufficient_credits | Buy more credits and retry. Partial batches tell you where to resume. |
| 403 | not_owned | You asked for a report this account has not bought. |
| 404 | not_found | Unknown endpoint or version. |
| 409 | vin_not_loaded | Listing posted for a VIN we have not loaded. Call /vin/{VIN}/json first. |
| 400 | listing_incomplete | A board listing needs price, mileage and at least one link. |
| 400 | invalid_kind | Report kind must be carfax or autocheck. |
| 400 | invalid_listing | A listing field would not parse — usually a link that is not a web address, or a price with stray characters. The message names the field. |
| 400 | invalid_request | Malformed body. Batch wants {"vins": […]}. |
| 400 | too_many | More than 50 VINs in one batch. Split it. |
| 400 | order_failed | A history order could not be placed for a reason other than credits — the message says which. |
| 400 | listing_failed | The listing saved but could not be submitted to the board. Retry; the details are kept. |
| 405 | method_not_allowed | Right path, wrong verb — GET to retrieve, POST to order or submit. |
| 429 | rate_limited | 60 requests a minute per key. Honour Retry-After. |
| 503 | not_ready | Report bought but the provider has not delivered yet. Retry shortly; you are not charged again. |
Things worth knowing
- Rate limit: 60 requests a minute per key, and X-RateLimit-Remaining comes back on every response. Need more for a large import? Ask — it is a number in a config file, not a plan upgrade.
- Idempotence: re-requesting a VIN or report your account already owns is free and returns the same data, so a retry after a timeout costs nothing. Safe to make your client retry.
- Non-Tesla VINs are decoded too, via the government build record and recalls, and CARFAX/AutoCheck work on any VIN. Supercharging and FSD fields are Tesla-only and are simply absent otherwise — we do not invent them.
- Caching: please cache what you pull. A VIN's factory build never changes, and re-reading your own reports is free anyway.
- Display: if you show CARFAX or AutoCheck content to your users, your agreement is with those providers as much as with us — render the document as delivered rather than extracting pieces of it.
- Keys are per account. Everything a key spends comes from that account's credits, and every call is logged against the key, so you can hand a key to a contractor and revoke it without touching the rest.
Building something and need an endpoint that is not here? Say so — most of what the site does can be exposed, and the list above grew from people asking.