Documentation
¶
Overview ¶
Package apiauto is a 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.
It uses only the standard library. The key comes from the APIAUTO_KEY environment variable or from WithAPIKey; a free demo key comes from https://t.me/apiautospace_bot?start=demo.
client, err := apiauto.NewClient()
if err != nil {
log.Fatal(err)
}
page, err := client.Cars(ctx, apiauto.MarketKR, &apiauto.CarsParams{Brand: "Kia", Limit: 20})
Failed requests return an *Error; test it with errors.Is against ErrNotFound, ErrQuotaExceeded and the other sentinels, or read its fields with errors.As.
The client is safe for concurrent use. It is the official client from the apiauto.space team; the project is not affiliated with Encar, Che168/Autohome, Copart or IAAI.
Index ¶
- Constants
- Variables
- func Bool(value bool) *bool
- type Car
- type CarsPage
- type CarsParams
- type Change
- type ChangeType
- type ChangesPage
- type ChangesParams
- type Client
- func (c *Client) Brands(ctx context.Context, market Market) ([]ReferenceItem, error)
- func (c *Client) Car(ctx context.Context, id string) (*Car, error)
- func (c *Client) Cars(ctx context.Context, market Market, params *CarsParams) (*CarsPage, error)
- func (c *Client) Changes(ctx context.Context, market Market, since time.Time, params *ChangesParams) (*ChangesPage, error)
- func (c *Client) Dictionaries(ctx context.Context) (Dictionaries, error)
- func (c *Client) GoString() string
- func (c *Client) IterCars(ctx context.Context, market Market, params *CarsParams, maxItems int) iter.Seq2[Car, error]
- func (c *Client) IterChanges(ctx context.Context, market Market, since time.Time, params *ChangesParams, ...) iter.Seq2[Change, error]
- func (c *Client) LastResponse() *ResponseInfo
- func (c *Client) Markets(ctx context.Context) ([]MarketStatus, error)
- func (c *Client) Models(ctx context.Context, market Market, brand string) ([]ReferenceItem, error)
- func (c *Client) String() string
- func (c *Client) USLive(ctx context.Context, params *USLiveParams) ([]Car, error)
- func (c *Client) Usage(ctx context.Context) (*Usage, error)
- type Dictionaries
- type Error
- type Location
- type Market
- type MarketStatus
- type Option
- type Price
- type ReferenceItem
- type ResponseInfo
- type Sort
- type USLiveParams
- type Usage
- type UsageMarket
Examples ¶
Constants ¶
const ( // Version is the version of this library. Version = "0.1.0" // DefaultBaseURL is the API root. DefaultBaseURL = "https://api.apiauto.space/v1" // APIKeyEnv is the environment variable the key is read from. APIKeyEnv = "APIAUTO_KEY" )
Variables ¶
var ( // ErrConnection: the request got no response: a network failure or a timeout. ErrConnection = errors.New("apiauto: connection error") // ErrBadRequest is 400: a bad parameter, a limit above the plan's, or offset given. ErrBadRequest = errors.New("apiauto: bad request") // ErrAuthentication is 401: no key, a wrong or revoked key, or the subscription has ended. ErrAuthentication = errors.New("apiauto: authentication error") // ErrQuotaExceeded is 402: the period quota is spent; ResetAt says when it renews. ErrQuotaExceeded = errors.New("apiauto: quota exceeded") // ErrPermissionDenied is 403: the market or feature is not in the plan. ErrPermissionDenied = errors.New("apiauto: permission denied") // ErrNotFound is 404: no such car or path. ErrNotFound = errors.New("apiauto: not found") // ErrRateLimit is 429: more requests per second than the plan allows; see RetryAfter. ErrRateLimit = errors.New("apiauto: rate limit") // ErrServer is any 5xx: a server-side failure. ErrServer = errors.New("apiauto: server error") // It also matches ErrServer. ErrServiceUnavailable = errors.New("apiauto: service unavailable") // ErrCursorLoop: the API gave a cursor it had already given, so the iteration stopped. It is // not an [*Error]. ErrCursorLoop = errors.New("apiauto: the API repeated a cursor") )
Sentinels for errors.Is. Every failed request returns an *Error that matches one of them.
Functions ¶
func Bool ¶
Bool returns a pointer to value, for CarsParams.HasPhotos and CarsParams.IsActive.
Types ¶
type Car ¶
type Car struct {
// ID is source:source_id, such as encar:43020893, and never changes.
ID string `json:"id"`
Market Market `json:"market"`
// Source is encar, che168, copart or iaai.
Source string `json:"source"`
// SourceID is the listing or lot number at the source.
SourceID string `json:"source_id"`
Title string `json:"title"`
Brand string `json:"brand"`
Model string `json:"model"`
// Generation is set in Korea only.
Generation string `json:"generation"`
Trim string `json:"trim"`
Year *int `json:"year"`
MileageKM *int `json:"mileage_km"`
Fuel string `json:"fuel"`
Transmission string `json:"transmission"`
EngineCC *int `json:"engine_cc"`
BodyType string `json:"body_type"`
Color string `json:"color"`
VIN string `json:"vin"`
Price Price `json:"price"`
// PriceUSD is the price in dollars at the ECB daily rate, a decimal string.
PriceUSD string `json:"price_usd"`
Location Location `json:"location"`
Photos []string `json:"photos"`
PhotoCount int `json:"photo_count"`
ListingURL string `json:"listing_url"`
IsActive bool `json:"is_active"`
FirstSeenAt time.Time `json:"first_seen_at"`
LastSeenAt time.Time `json:"last_seen_at"`
DisappearedAt *time.Time `json:"disappeared_at"`
UpdatedAt time.Time `json:"updated_at"`
// SourceMode is replica, or live for [Client.USLive].
SourceMode string `json:"source_mode"`
// Raw holds the fields only the source has, as it sends them, such as damage_main or sale_date.
Raw map[string]any `json:"raw"`
}
Car is one car record, the same fields in every market. A text field the source does not give is empty and a number is nil, never a guess. Photos are signed links that work for about a day: store the ID, not the links.
type CarsPage ¶
type CarsPage struct {
Items []Car `json:"items"`
// NextCursor goes into [CarsParams.Cursor] for the next page; empty on the last one.
NextCursor string `json:"next_cursor"`
// Count is the number of cars matching the filters when there are at most 10,000; nil above.
Count *int `json:"count"`
}
CarsPage is a page of Client.Cars.
type CarsParams ¶
type CarsParams struct {
// Brand is a make as [Client.Brands] spells it; case, spaces and hyphens are ignored.
Brand string
// Model is the model family, one name in every market: Grandeur, 3 Series.
Model string
// Generation is for Korea only: the generation as in [Car.Generation].
Generation string
// Trim is an exact match.
Trim string
YearMin int
YearMax int
// PriceMin and PriceMax are in the market's currency (KRW, CNY or USD).
PriceMin float64
PriceMax float64
// PriceUSDMin and PriceUSDMax are in dollars at the ECB rate.
PriceUSDMin float64
PriceUSDMax float64
// MileageMax is in kilometres.
MileageMax int
// Fuel, Transmission and BodyType are canonical values from [Client.Dictionaries].
Fuel string
Transmission string
BodyType string
HasPhotos *bool
// IsActive is true by default on the server; false returns cars taken off sale in the last 14 days.
IsActive *bool
// Q is up to 6 words of the title: year, make, model, trim.
Q string
Sort Sort
// Limit is the cars per page, 1-100 (up to 20 on the demo); 20 by default.
Limit int
// Cursor is [CarsPage.NextCursor] of the previous page.
Cursor string
}
CarsParams are the optional filters of Client.Cars. A zero value is left out of the request; use Bool for the two boolean filters.
type Change ¶
type Change struct {
ID int64 `json:"id"`
// CarID is the car's id; its prefix tells the source.
CarID string `json:"car_id"`
Market Market `json:"market"`
Type ChangeType `json:"type"`
At time.Time `json:"at"`
// OldPrice and NewPrice are decimal strings, set on price events only.
OldPrice string `json:"old_price"`
NewPrice string `json:"new_price"`
// Pct is the size of the price change in percent, always positive; the direction is in Type.
Pct string `json:"pct"`
}
Change is one event of the change feed.
type ChangeType ¶
type ChangeType string
ChangeType is an event type of the change feed.
const ( ChangeAdded ChangeType = "added" ChangeDisappeared ChangeType = "disappeared" ChangePriceDrop ChangeType = "price_drop" ChangePriceUp ChangeType = "price_up" )
The event types of the change feed.
type ChangesPage ¶
type ChangesPage struct {
Items []Change `json:"items"`
// NextCursor goes into [ChangesParams.Cursor] for the next page; empty on the last one.
NextCursor string `json:"next_cursor"`
}
ChangesPage is a page of Client.Changes.
type ChangesParams ¶
type ChangesParams struct {
// Types are the event types to return; every type when empty.
Types []ChangeType
// MinPct is the smallest price change in percent (0.5 by default on the server).
MinPct float64
// Limit is the events per page, 1-100.
Limit int
// Cursor is [ChangesPage.NextCursor] of the previous page.
Cursor string
}
ChangesParams are the optional parameters of Client.Changes.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the apiauto.space API. Create it with NewClient; it is safe for concurrent use.
func NewClient ¶
NewClient returns a client. Without WithAPIKey the key comes from APIAUTO_KEY.
func (*Client) Car ¶
Car fetches one car by id, such as encar:43020893: GET /cars/{id}. Cars taken off sale stay available for 14 days with IsActive false.
func (*Client) Cars ¶
Cars returns one page of a market's catalog: GET /cars. Every car returned counts against the quota. params may be nil.
Example ¶
package main
import (
"context"
"fmt"
"log"
apiauto "github.com/apiauto-space/apiauto-go"
)
func main() {
client, err := apiauto.NewClient() // reads APIAUTO_KEY
if err != nil {
log.Fatal(err)
}
page, err := client.Cars(context.Background(), apiauto.MarketKR, &apiauto.CarsParams{
Brand: "Kia",
Model: "Sorento",
YearMin: 2021,
Limit: 20,
})
if err != nil {
log.Fatal(err)
}
for _, car := range page.Items {
fmt.Println(car.ID, car.Title, car.Price.Amount, car.Price.Currency)
}
}
Output:
func (*Client) Changes ¶
func (c *Client) Changes(ctx context.Context, market Market, since time.Time, params *ChangesParams) (*ChangesPage, error)
Changes returns one page of the change feed since a moment: GET /changes. Each event counts as one record; the feed comes with the Pro, Max and demo plans. params may be nil.
func (*Client) Dictionaries ¶
func (c *Client) Dictionaries(ctx context.Context) (Dictionaries, error)
Dictionaries returns the canonical values of fuel, transmission, body_type and more, with synonyms: field name, then canonical value, then synonyms. Free.
func (*Client) IterCars ¶
func (c *Client) IterCars(ctx context.Context, market Market, params *CarsParams, maxItems int) iter.Seq2[Car, error]
IterCars yields every car matching the filters, following next_cursor. With maxItems above 0 it stops after that many cars, and the last request asks only for what is still needed, so no quota is spent on extra records. The iteration ends at the first error, which it yields.
Example ¶
market=us holds both auctions and the API has no filter by auction: keep the lots you need by Source. The quota counts every lot returned.
package main
import (
"context"
"fmt"
"log"
apiauto "github.com/apiauto-space/apiauto-go"
)
func main() {
client, err := apiauto.NewClient()
if err != nil {
log.Fatal(err)
}
params := &apiauto.CarsParams{Brand: "Toyota", Limit: 100}
for lot, err := range client.IterCars(context.Background(), apiauto.MarketUS, params, 300) {
if err != nil {
log.Fatal(err)
}
if lot.Source == "copart" {
fmt.Println(lot.ID, lot.VIN, lot.RawString("damage_main"), lot.RawString("sale_date"))
}
}
}
Output:
func (*Client) IterChanges ¶
func (c *Client) IterChanges(ctx context.Context, market Market, since time.Time, params *ChangesParams, maxItems int) iter.Seq2[Change, error]
IterChanges yields every event since a moment, following next_cursor; maxItems works as in Client.IterCars.
Example ¶
package main
import (
"context"
"fmt"
"log"
"time"
apiauto "github.com/apiauto-space/apiauto-go"
)
func main() {
client, err := apiauto.NewClient()
if err != nil {
log.Fatal(err)
}
since := time.Now().Add(-time.Hour)
for event, err := range client.IterChanges(context.Background(), apiauto.MarketCN, since, nil, 0) {
if err != nil {
log.Fatal(err)
}
fmt.Println(event.Type, event.CarID, event.NewPrice)
}
}
Output:
func (*Client) LastResponse ¶
func (c *Client) LastResponse() *ResponseInfo
LastResponse returns the quota and rate headers of the last response, successful or not; nil before the first one.
func (*Client) Markets ¶
func (c *Client) Markets(ctx context.Context) ([]MarketStatus, error)
Markets returns every market's plan access, status, data age and active cars. Free.
type Dictionaries ¶
Dictionaries is field name, then canonical value, then synonyms.
type Error ¶
type Error struct {
// Message is a human-readable summary.
Message string
// StatusCode is the HTTP status, 0 when no response came.
StatusCode int
// Code is the error field of the body, such as quota_exhausted.
Code string
// Detail is the detail field: what exactly is wrong with the request. Like Code and ResetAt, it
// has no control characters and at most 500 characters.
Detail string
// Body is the decoded JSON body, or the raw text when it is not JSON.
Body any
// RetryAfter is how many seconds to wait before retrying (429 and 503), from 0 to 60; nil when
// not given. A larger or a negative value from the server is brought into that range.
RetryAfter *int
// ResetAt is when the quota renews, ISO 8601 (402).
ResetAt string
// contains filtered or unexported fields
}
Error is a failed request. API errors are JSON such as {"error": "quota_exhausted", "reset_at": "..."}; their fields are copied here. See https://apiauto.space/en/docs/errors/.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"log"
apiauto "github.com/apiauto-space/apiauto-go"
)
func main() {
client, err := apiauto.NewClient()
if err != nil {
log.Fatal(err)
}
_, err = client.Car(context.Background(), "encar:1")
var apiErr *apiauto.Error
switch {
case errors.Is(err, apiauto.ErrNotFound):
fmt.Println("no such car")
case errors.Is(err, apiauto.ErrQuotaExceeded) && errors.As(err, &apiErr):
fmt.Println("quota renews at", apiErr.ResetAt)
case errors.As(err, &apiErr):
fmt.Println(apiErr.StatusCode, apiErr.Code, apiErr.Detail)
}
}
Output:
type Location ¶
type Location struct {
// Country is an ISO 3166 code: KR, CN or US.
Country string `json:"country"`
// Region is a province or a US state.
Region string `json:"region"`
City string `json:"city"`
}
Location is where the car is.
type Market ¶
type Market string
Market is a market code.
const ( // MarketKR is Encar, South Korea. MarketKR Market = "kr" // MarketCN is Che168, China. MarketCN Market = "cn" // MarketUS is the Copart and IAAI auctions, USA. The API has no filter by auction: keep the // records you need by [Car.Source], and the events of the feed by the prefix of [Change.CarID]. MarketUS Market = "us" )
type MarketStatus ¶
type MarketStatus struct {
Market Market `json:"market"`
Source string `json:"source"`
AvailableInPlan bool `json:"available_in_plan"`
Status string `json:"status"`
DataUpdatedAt *time.Time `json:"data_updated_at"`
DataAgeSeconds *int `json:"data_age_seconds"`
StaleAfterSeconds int `json:"stale_after_seconds"`
Stale bool `json:"stale"`
ActiveCars int `json:"active_cars"`
}
MarketStatus is the plan access and data freshness of one market.
type Option ¶
Option configures NewClient.
func WithAPIKey ¶
WithAPIKey sets the key instead of the APIAUTO_KEY environment variable.
func WithBaseURL ¶
WithBaseURL sets the API root: https, or http for localhost; no user, query or fragment.
func WithHTTPClient ¶
WithHTTPClient sets your own HTTP client (a proxy, a transport, tracing). Its redirect policy is replaced on a copy: the client never follows a redirect, because the key is in the headers.
func WithMaxRetries ¶
WithMaxRetries sets how many times to retry after 429, 5xx or a network error, 2 by default. 429 and 503 wait Retry-After (at most 60 seconds), the rest back off exponentially. Other 4xx errors are never retried.
func WithTimeout ¶
WithTimeout sets how long one attempt may take, 30 seconds by default.
type Price ¶
type Price struct {
// Amount is a decimal string such as "23900000.00", kept as sent so no precision is lost;
// empty when the source gives no price.
Amount string `json:"amount"`
// Currency is KRW, CNY or USD.
Currency string `json:"currency"`
}
Price is a price in the market's currency.
type ReferenceItem ¶
ReferenceItem is a make or model as the catalog spells it, with the number of active cars.
type ResponseInfo ¶
type ResponseInfo struct {
Status int
// QuotaLimit is the period quota (X-Quota-Limit).
QuotaLimit *int
// QuotaRemaining is what is left of it (X-Quota-Remaining).
QuotaRemaining *int
// QuotaReset is when it renews, ISO 8601 (X-Quota-Reset).
QuotaReset string
// RateLimit is the requests per second the plan allows (X-RateLimit-Limit).
RateLimit *int
// RateRemaining is the requests left in the current second (X-RateLimit-Remaining).
RateRemaining *int
// RateReset is the seconds until the rate window resets (X-RateLimit-Reset).
RateReset *int
// DataAgeSeconds is the age of the market copy that answered (X-Data-Age-Seconds).
DataAgeSeconds *int
}
ResponseInfo is the quota and rate-limit headers of a response; a header the response did not carry is nil or empty.
type USLiveParams ¶
type USLiveParams struct {
Make string
Model string
YearMin int
YearMax int
// PriceMax is in US dollars.
PriceMax int
// Page is 1-100.
Page int
}
USLiveParams are the optional parameters of Client.USLive.
type Usage ¶
type Usage struct {
Plan string `json:"plan"`
// Status is demo, active or grace.
Status string `json:"status"`
ExpiresAt time.Time `json:"expires_at"`
// Period is YYYY-MM, or demo for the whole demo term.
Period string `json:"period"`
ResetAt time.Time `json:"reset_at"`
// QuotaUnit is records on paid plans, requests on the demo.
QuotaUnit string `json:"quota_unit"`
QuotaLimit int `json:"quota_limit"`
QuotaUsed int `json:"quota_used"`
RecordsLimit int `json:"records_limit"`
RecordsUsed int `json:"records_used"`
LiveUSLimit int `json:"live_us_limit"`
LiveUSUsed int `json:"live_us_used"`
ByMarket []UsageMarket `json:"by_market"`
}
Usage is the plan, its term and how much of the quota is spent.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
change-feed
command
What changed on Che168 in the last 24 hours: new cars, removed cars, price changes.
|
What changed on Che168 in the last 24 hours: new cars, removed cars, price changes. |
|
search
command
Search Encar (South Korea) by make, model and year.
|
Search Encar (South Korea) by make, model and year. |
|
us-lots
command
Copart and IAAI lots: VIN, damage, title and sale date.
|
Copart and IAAI lots: VIN, damage, title and sale date. |