# Classify a product (POST /v1/classify)

> POST /v1/classify: Classify a product. border.bot API reference (Classification).

Source: https://border.bot/docs/api/classify-product

> Documentation index: https://border.bot/llms.txt. Every page is Markdown at its URL + `.md`.

`POST https://api.border.bot/v1/classify`

Requires an API key (`Authorization: Bearer bb_live_…`).

Find the HS/HTS code for a product shipped to `destinationCountry`. Give a description, a product page URL, or both. Costs the mode’s credits (`GET /v1/classify/modes` lists the modes and their prices); a weak answer may escalate to another mode at no extra charge. Failed requests are refunded automatically.

API key scope: `classify`.

## Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | string | no | Make retries safe: a repeat with the same key returns the original result without charging again (keys are scoped to your workspace). |

## Request body (JSON)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `description` | string | no | What the product is: type, material/composition, intended use and user. Required unless `productUrl` or `imageUrl` is given. |
| `productUrl` | string | no | Product page URL. border.bot reads the listing (title, brand, materials, price and any stated country of origin) and classifies from it, with your description when you give one. |
| `imageUrl` | string | no | A product photo, classified with vision (in the modes that read photos: see `GET /v1/classify/modes`): a public URL, or the picture itself as a `data:image/jpeg;base64,…` URL (up to 6 MB). When given, the photo is classified instead of the listing URL. |
| `destinationCountry` | string | yes | Where the parcel is going: selects the tariff (the national one where border.bot has it, else the 6-digit HS). ISO 3166-1 alpha-2, case-insensitive. |
| `originCountry` | string | no | Where the product was made (context only). ISO 3166-1 alpha-2, case-insensitive. |
| `mode` | string | no | The classification mode: one of the modes `GET /v1/classify/modes` lists (each with its price and what it reads). Omitted: the default mode. An unknown or unavailable mode is refused (`invalid_input`, with `details.availableModes`). |
| `title` | string | no | Product title (optional extra context). |
| `brand` | string | no | Brand (optional extra context). |
| `sku` | string | no | Your SKU (stored with the usage record). |
| `price` | number | no | Unit price (optional context). |
| `currency` | string | no | ISO 4217 currency of `price`. |
| `hsCodeHint` | string | no | An HS code you believe is close (2–10 digits) — used as a hint, not trusted blindly. |

## Responses

- `200`: The classification and the credits charged. (`ClassifyResponse`)
- `400`: Invalid input (`invalid_input`). (`Error`)
- `401`: Missing, invalid, revoked or expired API key (`unauthorized`). (`Error`)
- `402`: Not enough credits (`insufficient_credits`). Nothing was charged. (`Error`)
- `403`: Workspace suspended, action disabled, or the key lacks the scope (`org_suspended`, `action_disabled`, `forbidden`). (`Error`)
- `409`: A request with this Idempotency-Key is still in progress (`conflict`). (`Error`)
- `429`: Rate limited (`rate_limited`). Retry after `Retry-After` seconds; `RateLimit` says which limit was hit (`r=0`, `t` seconds until its window ends) and `RateLimit-Policy` its quota. (`Error`)
- `500`: Unexpected error (`internal`). (`Error`)
- `502`: The engine failed (`upstream_error`). The credits are refunded automatically. (`Error`)
- `504`: The engine timed out (`upstream_timeout`). The credits are refunded automatically. (`Error`)

## Example

```bash
curl -X POST 'https://api.border.bot/v1/classify' \
  -H 'Authorization: Bearer bb_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"description":"Men'\''s short-sleeve t-shirt, 100% cotton, knitted","destinationCountry":"US","originCountry":"PT"}'
```
