The official guide, examples and OpenAPI spec for the SalvageAlert Developer API.
Search live IAA, Copart and GovDeals inventory across the US, Canada, the UK and Germany, look up VIN histories, estimate buyer fees, and get signed webhooks when a lot matches your filters. No scrapers to maintain.
Get an API key · Reference docs · Quick start · Examples · Postman · Changelog
- Why SalvageAlert
- Quick start
- Coverage
- Endpoints at a glance
- Guides
- Code examples
- Credits, plans and rate limits
- Errors
- Field conventions (read these first)
- Webhooks
- Screenshots
- FAQ
- Support
|
IAA US, Canada and UK, Copart US, Canada, UK and Germany, and GovDeals, all returned as one normalized |
|
Save a filter set as a watch and we POST HMAC-signed |
|
Relisting history across every source, title-brand checks, batch lookups of up to 50 VINs, NHTSA decode, recalls, crash ratings and fuel economy. |
All-in buyer-fee estimates for IAA and Copart in CA, US, UK and DE, including provincial and state sales tax and VAT. Daily FX reference rates. |
Cache hits cost 0 credits. A VIN with no history is free. Every response tells you what it cost ( |
1. Get a key. Create a free account. 50 credits are granted on signup, no card needed. Then copy your sa_live_… key from the API dashboard.
export SALVAGEALERT_KEY="sa_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"2. Check your account. This call is free.
curl https://salvagealert.com/api/v1/me \
-H "Authorization: Bearer $SALVAGEALERT_KEY"{
"user_id": "u_abc123",
"email": "you@example.com",
"plan": "free",
"credit_balance": 50,
"rate_limit_per_minute": 60
}3. Search lots. 1 credit, or 0 on a cache hit.
curl -X POST https://salvagealert.com/api/v1/lots/search \
-H "Authorization: Bearer $SALVAGEALERT_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "copart_us",
"makes": ["TOYOTA"],
"year_min": 2020,
"title_types": ["salvage", "clean"],
"page_size": 25
}'{
"source": "copart_us",
"page": 1,
"page_size": 25,
"total_estimate": 4812,
"filtered_out": 3,
"cached": false,
"lots": [
{
"stockNum": "61877786",
"vin": "JTEPU5JR8N6036225",
"year": 2022,
"make": "TOYOTA",
"model": "4RUNNER",
"primaryDamage": "FRONT END",
"odometerUnit": "mi",
"isBuyNow": true,
"buyNowPrice": "$9,950",
"auctionDateUTC": 1787234400000,
"isAuctionClosed": false,
"imageUrl": "https://…",
"detailUrl": "https://www.copart.com/lot/61877786"
}
]
}That's it. The full step-by-step walkthrough is in docs/getting-started.md.
| Source ID | Marketplace | Country | Currency | /lots/search |
Lot by stock # | /inventory |
Photos API |
|---|---|---|---|---|---|---|---|
copart_us |
Copart USA | 🇺🇸 | USD | ✅ | ✅ | ✅ | ✅ |
copart_ca |
Copart Canada | 🇨🇦 | CAD | ✅ | ✅ | ✅ | ✅ |
copart_uk |
Copart UK | 🇬🇧 | GBP | ✅ | ✅ | ✅ | ✅ |
copart_de |
Copart Germany | 🇩🇪 | EUR | ✅ | ✅ | ✅ | — |
iaai_us |
IAA USA | 🇺🇸 | USD | ✅ | ✅ | ✅ | ✅ |
iaai_ca |
IAA Canada | 🇨🇦 | CAD | ✅ | ✅ | ✅ | ✅ |
iaai_uk |
IAA UK (SYNETIQ) | 🇬🇧 | GBP | ✅ | via /inventory/{source}/{lot_key} |
✅ | — |
govdeals_us |
GovDeals (US gov. surplus) | 🇺🇸 | USD | ✅ | via /inventory/{source}/{lot_key} |
✅ | — |
Every lot is priced in its source's own currency. Only /stats/market normalizes to USD. More in docs/sources.md.
Base URL: https://salvagealert.com/api/v1. Authentication: Authorization: Bearer sa_live_….
Lots and VINs
| Method | Path | What it does | Credits |
|---|---|---|---|
POST |
/lots/search |
Live search on one source | 1 (0 cached) |
GET |
/lots/{source}/{lot_key} |
One live lot by stock number | 1 |
GET |
/lots/{source}/{lot_key}/images |
Every gallery photo URL | 4 |
GET |
/lots/{source}/{lot_key}/images.zip |
All photos as a ZIP | 8 |
GET |
/vin/{vin}/history |
Every lot a VIN has appeared as | 1 (0 if unknown) |
GET |
/vin/{vin}/title-check |
Title-brand / salvage-history check | 1 |
POST |
/vin/batch |
History for up to 50 VINs | 1 per VIN found |
Archived inventory
| Method | Path | What it does | Credits |
|---|---|---|---|
GET |
/inventory |
Browse / mirror the archive (page or cursor) | 1 |
GET |
/inventory/{source}/{lot_key} |
One archived lot by identity | 1 (0 on miss) |
GET |
/inventory/sources |
Per-source counts and freshness | 0 |
GET |
/auctions |
The auction calendar | 0 |
Vehicle data, market, fees and currency
| Method | Path | What it does | Credits |
|---|---|---|---|
GET |
/vehicles/vin/{vin} |
NHTSA VIN decode | 0.2 |
GET |
/vehicles/safety |
Recalls and crash-test ratings | 0.2 |
GET |
/vehicles/fuel-economy |
Fuel economy | 0.2 |
GET |
/stats/market |
Price snapshot for a make / model | 1 |
GET |
/fees/estimate |
All-in buyer fees for a hammer price | 0.2 |
GET |
/fx |
Daily reference exchange rates | 0.2 |
Watches and webhooks
| Method | Path | What it does | Credits |
|---|---|---|---|
GET POST |
/watches |
Saved filter sets that fire events | 0 |
PATCH DELETE |
/watches/{id} |
Pause, resume, relabel, delete | 0 |
GET POST |
/vin-watches |
Watch one vehicle by VIN | 0 |
GET PATCH DELETE |
/vin-watches/{id} |
Read, edit, delete a VIN watch | 0 |
POST |
/vin-watches/{id}/run |
Run a VIN watch now | 0 |
GET POST |
/webhooks |
List / create webhook endpoints | 0 |
GET PATCH DELETE |
/webhooks/{id} |
Manage one endpoint | 0 |
POST |
/webhooks/{id}/test |
Send a test delivery | 0 |
GET |
/webhooks/{id}/deliveries |
Delivery log | 0 |
POST |
/webhooks/{id}/deliveries/{deliveryId}/redeliver |
Redeliver one past delivery | 0 |
Account, keys and reference data
| Method | Path | What it does | Credits |
|---|---|---|---|
GET |
/me |
Profile, plan, balance, limits | 0 |
GET |
/usage |
Usage statistics per day | 0 |
GET POST |
/api-keys |
List / mint keys | 0 |
DELETE |
/api-keys/{id} |
Revoke one key | 0 |
POST |
/api-keys/rotate |
Revoke every key, issue a new one | 0 |
GET |
/matches, /matches.csv |
Your match history (JSON / CSV) | 0 |
GET PATCH |
/matches/{id} |
Favorite, dismiss, annotate a match | 0 |
GET |
/makes, /models, /yards |
Catalogues for building filters | 0 |
The machine-readable contract is openapi/openapi.json (mirrored from the live spec). Import it into Postman, Insomnia, Bruno, or a code generator.
| Guide | You'll learn |
|---|---|
| Getting started | Key, first call, reading the response headers |
| Authentication and keys | Bearer auth, minting, revoking, rotating |
| Searching lots | Every /lots/search filter, paging, caching |
| Inventory and mirroring | Keyset cursor, updated_since deltas, keeping a local copy |
| VIN data | History, title checks, batch lookups, decode, recalls |
| Webhooks | Watches, events, signature verification, retries |
| Fees and taxes | All-in cost estimates per marketplace |
| Field conventions | Timestamps, booleans, casing, money |
| Errors | Every error code and what to do about it |
| Credits and rate limits | Costs, headers, back-off |
| Sources | What each marketplace supports |
Runnable, dependency-free examples live in examples/:
| cURL | Node.js (18+) | Python (3.9+) | |
|---|---|---|---|
| Account check | me.sh | me.mjs | me.py |
| Search lots | search.sh | search.mjs | search.py |
| VIN history | vin-history.sh | vin-history.mjs | vin_history.py |
| Fee estimate | fees.sh | — | fees.py |
| Mirror the archive | — | mirror-inventory.mjs | mirror_inventory.py |
| Create a watch + webhook | watch.sh | — | — |
| Webhook receiver (verifies HMAC) | — | webhook-server.mjs | webhook_server.py |
Node.js: search in 15 lines
const res = await fetch('https://salvagealert.com/api/v1/lots/search', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SALVAGEALERT_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ source: 'iaai_ca', makes: ['HONDA'], year_min: 2019 }),
});
if (!res.ok) throw new Error((await res.json()).error.code);
const { lots, total_estimate } = await res.json();
console.log(`${total_estimate} lots, charged ${res.headers.get('x-credits-charged')} credit(s)`);
for (const lot of lots) {
console.log(lot.year, lot.make, lot.model, lot.primaryDamage, new Date(lot.auctionDateUTC));
}Python: search in 15 lines
import json, os, urllib.request
from datetime import datetime, timezone
req = urllib.request.Request(
"https://salvagealert.com/api/v1/lots/search",
data=json.dumps({"source": "copart_uk", "makes": ["BMW"], "year_min": 2020}).encode(),
headers={"Authorization": f"Bearer {os.environ['SALVAGEALERT_KEY']}",
"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as res:
body = json.load(res)
for lot in body["lots"]:
when = lot["auctionDateUTC"] and datetime.fromtimestamp(lot["auctionDateUTC"] / 1000, timezone.utc)
print(lot["year"], lot["make"], lot["model"], lot.get("ukCategory"), when)Every /v1 call debits credits from one balance. A subscription includes a monthly grant, and one-off credit packs (1k / 5k / 20k) top it up at any time.
| Plan | Price | API credits / month | Requests / minute |
|---|---|---|---|
| Free | $0 | 50 on signup | 60 |
| Starter | $19.99/mo · $199/yr | 500 | 120 |
| Unlimited | $39.99/mo · $399/yr | 5,000 | 300 |
Current prices are on the pricing page. Each response carries:
| Header | Meaning |
|---|---|
X-Credits-Charged |
What this call cost |
X-Credits-Balance |
What you have left |
X-Credits-Warning |
Present (low) once you're under 10% of your grant |
X-RateLimit-Limit / -Remaining / -Reset |
Your per-minute window |
Retry-After |
On a 429, seconds until a slot frees |
X-Request-Id |
Quote it when you contact support |
More detail: docs/credits-and-rate-limits.md.
Every non-2xx response has the same envelope. Switch on error.code, never on message.
{
"error": { "code": "insufficient_credits", "message": "This call costs 1 credit; your balance is 0." },
"request_id": "6f1c2a7e-3b0d-4d7e-9a51-2f7f0c1b9e44",
"balance": 0,
"credits_url": "https://salvagealert.com/app/api",
"plan_url": "https://salvagealert.com/pricing"
}| Status | Typical codes | Billed? |
|---|---|---|
400 |
invalid_source, invalid_location, invalid_title_type, too_many_vins… |
Never |
401 |
missing_credentials, invalid_credentials, account_inactive |
Never |
402 |
insufficient_credits |
Never |
403 |
api_disabled, plan_gate |
Never |
500 / 502 |
internal_error, upstream_unavailable (retry with back-off) |
Never |
404 |
not_found, lot_not_found, no_data |
Unknown VIN: never. Live lot miss: a reduced miss fee |
429 |
rate_limit (honour Retry-After) |
Never |
The full list is in docs/errors.md.
Four rules that will save you a debugging session:
- Timestamps are seconds, except two fields. Every
*_atfield is Unix seconds.sale_date_utcandauctionDateUTCare milliseconds. Divide those two by 1000; never guess by magnitude. - Booleans aren't uniform on
InventoryLot.make_offer,wholesale_only,former_rentalandoffsiteare JSON booleans.has_keys,drivesandstartsare integers (0/1/null).nullmeans "the source didn't say", notfalse. - Casing depends on the endpoint.
/lots/searchand/lots/{source}/{lot_key}return the camelCaseLot. Everything else is snake_case. - Money strings are for display.
current_bidandbuy_nowmay contain symbols and separators. Sort and compare on the numeric*_valuefields.
And one promise from us: nothing in v1 is removed or changes meaning. New fields arrive without notice, so ignore keys you don't recognise. Details: docs/conventions.md.
your watch (filters) SalvageAlert your server
┌────────────────────┐ new lot ┌────────────┐ POST + HMAC ┌──────────────┐
│ copart_us · TOYOTA │ ──────────▶ │ matcher │ ─────────────▶ │ /hooks/sa │
│ 2020+ · salvage │ │ + queue │ ◀── 2xx ────── │ verify, 200 │
└────────────────────┘ └────────────┘ retry ≤ 7x └──────────────┘
Generic deliveries are signed Stripe-style:
X-Salvagealert-Signature: t=1761062400,v1=5f2b9c…
The signature is HMAC-SHA256(secret, "<t>.<raw body>"), hex-encoded. Verify it against the raw request body, compare in constant time, and reject old timestamps:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(parts.v1 ?? '');
return a.length === b.length && timingSafeEqual(a, b);
}Discord and Slack URLs are auto-detected and get native message formatting, but they aren't signed. Pass "format": "generic" to keep the HMAC. Full guide with retry schedule and event shapes: docs/webhooks.md.
![]() Developer portal: salvagealert.com/developers |
![]() API reference with cURL, Node and Python tabs |
![]() The catalogue behind |
![]() Fee engine behind |
![]() VIN check behind |
![]() SalvageAlert: salvage auction alerts for IAA and Copart |
Is this an official IAA or Copart API?
No. SalvageAlert is an independent service and isn't affiliated with IAA, Copart or GovDeals. We collect, normalize and archive public auction listings and serve them through one consistent API.
How fresh is the data?
/lots/search queries the source live, with a short shared cache. Cached pages are free and flagged with "cached": true. /inventory reads our continuously crawled archive, and /inventory/sources reports newest_change_at and a stale flag for each source.
Do lots come with full VINs?
Copart lots carry full, unmasked VINs across all four countries. IAA masks part of the VIN on public listings, so IAA lots have a full VIN only where an authenticated crawl reached them.
Can I mirror the whole catalogue?
Yes. That's what the
cursor and updated_since modes of /inventory are for. Walk it once with the cursor, then pull deltas. See the mirroring guide and the example.
Are hammer (sold) prices available?
Most salvage lots sell on approval and the auctions don't publish the final price. We report the best price we hold: the Buy Now price, or else the highest pre-bid.
/stats/market aggregates those and says so.
Is there an SDK?
The API is small enough that
fetch or requests is all you need, as the examples show. If you want typed clients, generate them from the OpenAPI spec, for example with npx @openapitools/openapi-generator-cli generate -i openapi/openapi.json -g typescript-fetch -o sdk.
- Reference docs: salvagealert.com/developers/docs
- Dashboard (keys, usage, webhooks): salvagealert.com/app/api
- Contact: salvagealert.com/contact or support@salvagealert.com. Include the
request_id. - Bugs in these docs or examples: open an issue.
- Security: see SECURITY.md.
Home · Browse salvage lots · VIN check · Fee calculator · Copart vs IAA · Pricing · Developers
Examples and docs in this repository are MIT-licensed. The SalvageAlert API itself is governed by the Terms of Service.
SalvageAlert is independent and not affiliated with IAA, Copart or GovDeals. All trademarks belong to their owners.





