Skip to content

About

Official docs, examples and OpenAPI spec for the SalvageAlert API: one REST API for IAA, Copart and GovDeals salvage auction data across the US, Canada, UK and Germany. Live lot search, 1.5M+ archived lots, VIN history, fee estimates and signed webhooks.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

SalvageAlert API: salvage auction data from IAA, Copart and GovDeals, through one REST API



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.


API version OpenAPI Free credits Status


Get an API key  ·  Reference docs  ·  Quick start  ·  Examples  ·  Postman  ·  Changelog


Contents


✦ Why SalvageAlert

One schema, eight sources

IAA US, Canada and UK, Copart US, Canada, UK and Germany, and GovDeals, all returned as one normalized Lot object. You write one parser.

Live and archived

/lots/search queries the auction houses live. /inventory pages through our de-duplicated archive of 1.5M+ lots, with a keyset cursor and updated_since deltas for mirroring.

Push, don't poll

Save a filter set as a watch and we POST HMAC-signed lot.matched, lot.updated and lot.sold events to your endpoint, with retries over about 35 hours.

VIN intelligence

Relisting history across every source, title-brand checks, batch lookups of up to 50 VINs, NHTSA decode, recalls, crash ratings and fuel economy.

Fees and money

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.

Honest metering

Cache hits cost 0 credits. A VIN with no history is free. Every response tells you what it cost (X-Credits-Charged) and what you have left (X-Credits-Balance).


🚀 Quick start

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.


🌍 Coverage

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.


📚 Endpoints at a glance

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.


📖 Guides

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

💻 Code examples

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)

💳 Credits, plans and rate limits

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.


🧯 Errors

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.


⚠️ Field conventions (read these first)

Four rules that will save you a debugging session:

  1. Timestamps are seconds, except two fields. Every *_at field is Unix seconds. sale_date_utc and auctionDateUTC are milliseconds. Divide those two by 1000; never guess by magnitude.
  2. Booleans aren't uniform on InventoryLot. make_offer, wholesale_only, former_rental and offsite are JSON booleans. has_keys, drives and starts are integers (0/1/null). null means "the source didn't say", not false.
  3. Casing depends on the endpoint. /lots/search and /lots/{source}/{lot_key} return the camelCase Lot. Everything else is snake_case.
  4. Money strings are for display. current_bid and buy_now may contain symbols and separators. Sort and compare on the numeric *_value fields.

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.


🔔 Webhooks

  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.


🖼 Screenshots

SalvageAlert developer API landing page

Developer portal: salvagealert.com/developers

SalvageAlert interactive API reference

API reference with cURL, Node and Python tabs

SalvageAlert salvage lot archive with filters

The catalogue behind /inventory: 1.5M+ lots

IAA and Copart fee calculator

Fee engine behind /fees/estimate: try it

Free VIN check and decoder

VIN check behind /vin/{vin}/history: free VIN check

SalvageAlert home page

SalvageAlert: salvage auction alerts for IAA and Copart


❓ FAQ

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.

🤝 Support



SalvageAlert

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.

About

Official docs, examples and OpenAPI spec for the SalvageAlert API: one REST API for IAA, Copart and GovDeals salvage auction data across the US, Canada, UK and Germany. Live lot search, 1.5M+ archived lots, VIN history, fee estimates and signed webhooks.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages