apiauto is a typed Python client for the apiauto.space REST API: used-car
listings from Encar (South Korea), Che168 (China) and the Copart and IAAI auctions (USA) in
one JSON record format, without writing a scraper. Search the catalogs by make, model, year, price and
mileage, page through whole markets, follow a feed of new cars, sold cars and price changes, and read
quota headers — from sync or async code. It is an unofficial client: the project is not affiliated with
Encar, Che168/Autohome, Copart or IAAI; the data comes through the apiauto.space API.
- Sync
ClientandAsyncClienton top of httpx, the only dependency - Iterators that follow the cursor for you, with
max_itemsthat never over-spends the quota - Automatic retries on 429 and 503 (honouring
Retry-After) and on 5xx and network errors - One exception class per API error:
QuotaExceededError,RateLimitError,NotFoundError... TypedDicttypes for every response,py.typed, checked withmypy --strict- Python 3.10+
pip install apiauto-spaceThe distribution is called apiauto-space on PyPI; the module you import is apiauto.
Open the Telegram bot @apiautospace_bot and take a free demo key: one day, 100 data requests, up to 20 cars per request, all three markets. Paid plans are on the pricing page.
Save the key in the APIAUTO_KEY environment variable — the client reads it from there:
export APIAUTO_KEY="your-key"Keep the key on your server. The API does not accept browser requests (CORS is off), and a key in page code is visible to every visitor.
import apiauto
client = apiauto.Client() # reads APIAUTO_KEY
page = client.cars("kr", brand="Kia", model="Sorento", year_min=2021, limit=20)
for car in page["items"]:
print(car["id"], car["title"], car["price"]["amount"], car["price"]["currency"])Markets are kr (Encar, Korea), cn (Che168, China) and us (Copart and IAAI, USA). Every record has
the same keys in every market; a value the source does not provide is None.
page = client.cars(
"kr",
brand="Hyundai",
model="Grandeur",
year_min=2020,
year_max=2023,
price_min=10_000_000, # in the market's currency: KRW for Korea; skips monthly lease payments
price_max=30_000_000,
mileage_max=80_000, # km
sort="-first_seen", # newest listings first (the default)
limit=20,
)
print(page["count"], "cars match") # None when more than 10,000 matchPrices in dollars at the ECB rate work in every market: price_usd_min=15000, price_usd_max=25000.
The full list of filters: brand, model, generation (Korea), trim, year_min, year_max,
price_min, price_max, price_usd_min, price_usd_max, mileage_max, fuel, transmission,
body_type, has_photos, is_active, q (words of the title), sort, limit, cursor.
for car in client.cars("cn", brand="BYD", fuel="electric", has_photos=True, limit=10)["items"]:
print(car["title"], car["price"]["amount"], "CNY", car["location"]["city"])for lot in client.cars("us", brand="Toyota", model="Camry", year_min=2020, limit=10)["items"]:
raw = lot["raw"] # auction-only fields as the source sends them
print(lot["id"], lot["vin"], lot["mileage_km"], raw.get("damage_main"), raw.get("sale_date"))Cars that are not in the copy yet can be searched live at the auction data provider (Pro and Max plans,
a separate monthly counter): client.us_live(make="Toyota", model="Camry", year_min=2020).
for car in client.iter_cars("kr", brand="Genesis", limit=100, max_items=1000):
save(car)iter_cars follows next_cursor until the last page. With max_items the last request asks only for
the cars still needed, so the quota is not spent on records that would be thrown away. The demo allows
limit up to 20; paid plans up to 100.
car = client.car("encar:43020893")
print(car["is_active"], car["listing_url"])Ids look like encar:…, che168:…, copart:… and iaai:… and never change. A sold or removed car
stays available for 14 days with is_active=False.
import time
from datetime import datetime, timezone
since = datetime.now(timezone.utc)
while True:
time.sleep(3600) # the market copy is refreshed hourly
polled_at = datetime.now(timezone.utc)
for event in client.iter_changes("kr", since, limit=100):
if event["type"] == "added":
save(client.car(event["car_id"]))
elif event["type"] == "disappeared":
mark_sold(event["car_id"])
else: # price_drop or price_up
update_price(event["car_id"], event["new_price"])
since = polled_atsince takes a datetime (a naive one is treated as UTC) or an ISO 8601 string. Filter by
type="price_drop" (or a list of types) and min_pct=5. The feed comes with the Pro, Max and demo
plans; each event counts as one record. A complete script is in
examples/sync_changes.py.
These calls are free.
client.brands("cn") # [{"name": "Mercedes-Benz", "count": 20185}, ...]
client.models("kr", "Hyundai") # the make's models with car counts
client.dictionaries()["fuel"] # {"gasoline": ["gasoline", "petrol", ...], "diesel": [...], ...}
for market in client.markets():
print(market["market"], market["active_cars"], market["data_age_seconds"], market["stale"])The quota counts returned cars (records), not requests; the demo counts data requests.
usage = client.usage() # free
print(usage["plan"], usage["quota_used"], "/", usage["quota_limit"], usage["quota_unit"])
client.cars("us", limit=5)
info = client.last_response
print(info.quota_remaining, info.quota_reset, info.rate_remaining, info.data_age_seconds)client.last_response is a frozen ResponseInfo read from the X-Quota-*, X-RateLimit-* and
X-Data-Age-Seconds headers of the last response; a header that was not sent is None.
import asyncio
import apiauto
async def main() -> None:
async with apiauto.AsyncClient() as client:
page = await client.cars("cn", brand="Toyota", limit=10)
async for car in client.iter_cars("kr", brand="Kia", max_items=50):
print(car["title"])
asyncio.run(main())AsyncClient has the same methods as Client; iter_cars and iter_changes return async iterators.
try:
car = client.car("encar:1")
except apiauto.NotFoundError:
car = None
except apiauto.QuotaExceededError as error:
print("quota renews at", error.reset_at)
except apiauto.ApiautoError as error:
print(error.status_code, error.code, error.detail)| HTTP | Exception | Retried |
|---|---|---|
| 400 | BadRequestError |
no |
| 401 | AuthenticationError |
no |
| 402 | QuotaExceededError |
no |
| 403 | PermissionDeniedError |
no |
| 404 | NotFoundError |
no |
| 429 | RateLimitError |
yes, after Retry-After |
| 500, 502 | ServerError |
yes, exponential backoff |
| 503 | ServiceUnavailableError |
yes, after Retry-After |
| — | APIConnectionError (network, timeout) |
yes, exponential backoff |
All of them derive from ApiautoError. Retries follow the
API's error guide: max_retries=2 by default, a wait of at most
60 seconds; Client(max_retries=0) turns them off. Other options: Client(api_key=..., base_url=..., timeout=30.0, http_client=httpx.Client(...)).
car["photos"] are signed links that work for about a day. Store the car's id, not the links, and
fetch a fresh record with client.car(id) when you need the photos again. Photo requests are free and
may redirect to the original host, so follow redirects when downloading them.
Prices, dollar prices and percentages come as strings such as "23900000.00" so no precision is lost.
Convert them with Decimal:
from decimal import Decimal
total = sum(Decimal(car["price_usd"]) for car in page["items"] if car["price_usd"])A real Encar car from the documentation, shortened: two photo
links and a few raw fields.
{
"id": "encar:43020893",
"market": "kr",
"source": "encar",
"source_id": "43020893",
"title": "2020 Kia Mohave The Master 3.0 4WD 7-seater Masters",
"brand": "Kia",
"model": "Mohave",
"generation": "Mohave The Master",
"trim": "3.0 4WD 7-seater Masters",
"year": 2020,
"mileage_km": 93724,
"fuel": "diesel",
"transmission": null,
"engine_cc": null,
"body_type": null,
"color": null,
"vin": null,
"price": {"amount": "23900000.00", "currency": "KRW"},
"price_usd": "18033.96",
"location": {"country": "KR", "region": "Gyeonggi", "city": null},
"photos": [
"https://api.apiauto.space/img/encar:43020893/1.jpg?exp=…&sig=…",
"https://api.apiauto.space/img/encar:43020893/2.jpg?exp=…&sig=…"
],
"photo_count": 4,
"listing_url": "https://fem.encar.com/cars/detail/43020893",
"is_active": true,
"first_seen_at": "2026-10-26T04:37:32.553458Z",
"last_seen_at": "2026-10-26T04:37:32.553458Z",
"disappeared_at": null,
"updated_at": "2026-10-26T04:37:32.553458Z",
"source_mode": "replica",
"raw": {"badge": "디젤 3.0 4WD 7인승", "form_year": "202004", "sell_type": "normal", "has_accident": false}
}The fields each source adds are described on the Encar API, Che168 API, Copart API and IAAI API pages.
| Method | Endpoint | Returns | Quota |
|---|---|---|---|
cars(market, **filters) |
GET /v1/cars |
CarsPage: items, next_cursor, count |
per car |
iter_cars(market, *, max_items=None, **filters) |
GET /v1/cars |
iterator of Car |
per car |
car(car_id) |
GET /v1/cars/{id} |
Car |
1 |
changes(market, since, *, type, min_pct, limit, cursor) |
GET /v1/changes |
ChangesPage |
per event |
iter_changes(market, since, *, type, min_pct, limit, max_items) |
GET /v1/changes |
iterator of Change |
per event |
brands(market) |
GET /v1/references/brands |
list of {name, count} |
free |
models(market, brand) |
GET /v1/references/models |
list of {name, count} |
free |
dictionaries() |
GET /v1/references/dictionaries |
field → canonical value → synonyms | free |
markets() |
GET /v1/references/markets |
list of Market |
free |
usage() |
GET /v1/usage |
Usage |
free |
us_live(*, make, model, year_min, year_max, price_max, page) |
GET /v1/us/live |
list of Car |
own counter |
The API reference: apiauto.space/en/docs and the OpenAPI schema.
| Language | Package | Repository |
|---|---|---|
| Python | pip install apiauto-space |
apiauto-space/apiauto-python |
| JavaScript, TypeScript | npm install apiauto |
apiauto-space/apiauto-js |
| PHP | composer require apiauto/apiauto |
apiauto-space/apiauto-php |
| Go | go get github.com/apiauto-space/apiauto-go |
apiauto-space/apiauto-go |
| curl and examples | — | Encar, Che168, Copart, IAAI |
apiauto — Python-клиент для API Encar, Che168, Copart и IAAI, без парсинга: объявления о подержанных
автомобилях из Южной Кореи, Китая и с аукционов США приходят в одном формате JSON через
apiauto.space. Клиент умеет искать по марке, модели, году, цене и пробегу,
сам проходит по страницам курсором, повторяет запрос при 429 и 503 и работает в синхронном и асинхронном
коде. Бесплатный демо-ключ на сутки выдаёт Telegram-бот
@apiautospace_bot; сохраните его в переменной APIAUTO_KEY
и держите на сервере, не в браузере. Документация на русском: apiauto.space/docs.
pip install apiauto-spaceimport apiauto
client = apiauto.Client() # ключ из APIAUTO_KEY
for car in client.cars("kr", brand="Kia", year_min=2021, limit=20)["items"]:
print(car["title"], car["price"]["amount"], car["price"]["currency"])MIT © 2026 apiauto.space
This is an unofficial client of the apiauto.space API. It is not affiliated with, endorsed by or sponsored by Encar, Che168, Autohome, Copart or IAAI; their names are used only to describe where the listings come from, and all trademarks belong to their owners. Use of the data is subject to the apiauto.space terms.