Classification
HS/HTS codes.
POST /v1/classify Classify a product
Requires an API key. Operation ID classifyProduct.
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
| 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. ClassifyResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
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"}'
POST /v1/classify/batch Classify up to 25 products
Requires an API key. Operation ID classifyBatch.
Each item is a classify request, run four at a time and charged as one classification by its own mode; one failing (an unclassifiable description, insufficient credits) never stops the others and is refunded. With an Idempotency-Key, each item replays on its own.
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
| Name | Type | Required | Description |
|---|
items | ClassifyRequest[] | yes | |
Responses
200 One result per item, in order. ClassifyBatchResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/classify/batch' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"items":[{"description":"Men'\''s short-sleeve t-shirt, 100% cotton, knitted","destinationCountry":"US"},{"description":"Stainless steel water bottle, 750 ml","destinationCountry":"GB"}]}'
POST /v1/classify/regulator Find an agency product code
Requires an API key. Operation ID classifyRegulator.
The product code the destination’s import agency asks for, in that agency’s coding scheme: for the US, FDA’s import product code (e.g. 16AYN07) for foods, drugs, devices, cosmetics and other goods FDA regulates. The answer names the agency and scheme, lists the code’s parts in order, and says whether the agency’s own check accepted it. Costs 1 credit; goods the agency doesn’t regulate are refused (details.reason: "not_regulated", with details.agency) and refunded.
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
| Name | Type | Required | Description |
|---|
description | string | yes | What the product is, what it is made of, and how it is processed or packed (frozen, smoked, canned…). |
destinationCountry | string | yes | Where the goods are imported. Its import agency and coding scheme follow from it (for US, FDA’s import product codes). |
hsCode | string | no | The goods’ tariff code, when known: a hint for the product pick. |
Responses
200 The agency product code and the credits charged. RegulatorClassifyResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error422 No agency product codes for this destination yet (unsupported_country). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/classify/regulator' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"description":"<string>","destinationCountry":"<string>"}'
GET /v1/classify/modes Classification modes
Public, no API key needed. Operation ID classifyModes.
The classification modes you can use (admin configures them: today mini, pro and max), with what each costs and reads, in general or for one destination: whether it reads a product photo, and a photo sent inline, and which mode runs when a request names none. A product page URL works in every mode. No API key needed.
Parameters
| Name | Type | Required | Description |
|---|
destination | string | no | ISO-2 destination: what each mode offers there (photos sent inline need border.bot’s own classifier). |
Responses
200 The modes. ClassifyModesResponse400 Invalid input (invalid_input). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/classify/modes'
GET /v1/classify/blocklist Blocked codes
Requires an API key. Operation ID listBlockedCodes.
Codes your classifications never suggest: a blocked line, or every line under a blocked heading, is skipped and the next best answers. Free.
API key scope: settings.
Responses
200 Your blocked codes. BlockedCodesResponse401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/classify/blocklist' \
-H 'Authorization: Bearer bb_live_...'
POST /v1/classify/blocklist Block a code
Requires an API key. Operation ID addBlockedCode.
Never suggest this code (or any line under it) again, for one destination or all. Adding a code that is already blocked updates its reason. Free; up to 5,000 codes.
API key scope: settings.
Request body
| Name | Type | Required | Description |
|---|
code | string | yes | |
destination | "*" | string | no | |
reason | string | no | |
Responses
200 The blocked code. BlockedCodeResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X POST 'https://api.border.bot/v1/classify/blocklist' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"code":"6109.10","destination":"US","reason":"Our broker files these under 6109.90"}'
DELETE /v1/classify/blocklist Unblock a code
Requires an API key. Operation ID removeBlockedCode.
Lets classifications suggest the code again. Free.
API key scope: settings.
Parameters
| Name | Type | Required | Description |
|---|
code | string | yes | |
destination | "*" | string | no | |
Responses
200 Whether a blocked code was removed. BlockedCodeRemoveResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X DELETE 'https://api.border.bot/v1/classify/blocklist' \
-H 'Authorization: Bearer bb_live_...'
POST /v1/classify/blocklist/import Block many codes
Requires an API key. Operation ID importBlockedCodes.
Block a list of codes at once, or with replace: true make the list exactly these. All or nothing: an import that would pass 5,000 codes changes nothing. Free.
API key scope: settings.
Request body
| Name | Type | Required | Description |
|---|
codes | object[] | yes | |
replace | boolean | no | |
Responses
200 What the import added, updated and removed. BlockedCodesImportResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X POST 'https://api.border.bot/v1/classify/blocklist/import' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"codes":[{"code":"6109.10","destination":"US","reason":"Our broker files these under 6109.90"},{"code":"9503"}],"replace":false}'
POST /v1/classify/feedback Say whether a classification was right
Requires an API key. Operation ID sendClassifyFeedback.
Tell border.bot a code was right, or what it should have been. Corrections are reviewed and become test cases the classifier must pass. With the classification’s usageId, sending again replaces your earlier verdict until it is reviewed. Free; up to 1,000 a day.
API key scope: classify.
Request body
| Name | Type | Required | Description |
|---|
usageId | string | no | |
destinationCountry | string | yes | |
description | string | yes | |
predictedCode | string | yes | |
verdict | "right" | "wrong" | yes | |
correctCode | string | no | |
reason | string | no | |
mode | string | no | |
productUrl | string (uri) | no | |
Responses
200 The feedback was recorded. ClassifyFeedbackResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the classify scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X POST 'https://api.border.bot/v1/classify/feedback' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"usageId":"use_01J9ZK8Q7X4N2M","destinationCountry":"US","description":"Yoga mat, 6 mm, TPE","predictedCode":"3926909985","verdict":"wrong","correctCode":"9506910030","reason":"Exercise equipment, not an article of plastics"}'
Landed cost
Duties, taxes and fees.
POST /v1/calculate Calculate duties, taxes and landed cost
Requires an API key. Operation ID calculateLandedCost.
Duties, taxes, fees and the landed cost for one HS code / origin / destination (the destinations GET /countries marks landedCost). Costs 1 credit. Unsupported destinations return 422 unsupported_country without charging.
API key scope: calculate.
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
| Name | Type | Required | Description |
|---|
destinationCountry | string | yes | Destination: one GET /countries marks landedCost. ISO 3166-1 alpha-2, case-insensitive. |
currency | string | no | ISO 4217 currency of value, shippingCost and insuranceCost. |
shippingCost | number | no | Shipping cost (affects CIF-based duty/VAT bases). |
insuranceCost | number | no | Insurance cost. |
transportMode | "air" | "sea" | "road" | "rail" | no | Transport mode (fees such as HMF apply to sea freight). |
shippingTerms | "EXW" | "FCA" | "FAS" | "FOB" | "CFR" | "CIF" | "CPT" | "CIP" | "DAP" | "DPU" | "DDP" | no | Incoterm of the price (Incoterms 2020). Under C and D terms (CFR, CIF, CPT, CIP, DAP, DPU, DDP) the price already carries the goods to the destination, so shippingCost isn’t added to the customs value again. |
shipmentChannel | "courier" | "postal" | no | How the parcel travels: courier (express carriers, default) or postal — affects de minimis and channel-specific regimes. |
entryDate | string | no | Calculate with the rates in force on this date (YYYY-MM-DD). Default: today. |
tradeAgreement | string | no | Ignored. Preferential rates are applied automatically from the origin and destination; the agreement used is returned as tradeAgreement. |
region | string | no | State, province or territory, where import taxes differ inside the country. Canada needs one (ON, QC, BC…). |
purpose | "sale" | "gift" | "sample" | "return" | no | Why the goods are sent: some thresholds differ for gifts, samples and returns. Default: sale. |
businessBuyer | boolean | no | The buyer is a business: some taxes are reverse-charged or apply differently. |
sellerRegistrations | string[] | no | Tax schemes the seller is registered for (EU_IOSS, GB_VAT, AU_GST, NZ_GST, NO_VOEC…). Taxes the seller collects at checkout are returned with collectedBy: "seller". |
preference | string | no | best (default): the lowest preferential rate the origin qualifies for. none: the general rate only. Or a programme code (S for USMCA into the US). Every option is returned in options. |
claims | string[] | no | Exemption, relief and quota codes being claimed (US Chapter 99 exclusions such as 9903.88.69; an end-use authorisation such as TARIC document N990; a tariff quota order number such as 050331). Codes you could claim are returned in claims.available. |
enforceValidation | boolean | no | Refuse a shipment that matches one of the destination’s reject validation rules (400 invalid_input, details.reason: "validation_failed", refunded) instead of reporting it in validation. |
hsCode | string | yes | HS/HTS code (4–10 digits; dots and spaces are ignored). |
originCountry | string | yes | Country of origin — drives preferential rates and additional duties (e.g. Section 301). ISO 3166-1 alpha-2, case-insensitive. |
value | number | yes | Total customs value of the goods in the shipment (unit price × quantity), in currency, excluding shipping. |
quantity | integer | no | Number of units. |
weight | number | no | Shipment weight (needed for weight-based duties). Requires weightUnit. |
weightUnit | "kg" | "lb" | no | Unit of weight. |
volumeLiters | number | no | Volume of the goods in litres, for duties charged per litre (wine, spirits, fuel). |
alcoholPercent | number | no | Alcohol by volume (%), for duties charged per litre of pure alcohol. |
components | object[] | no | Metal content by value (line totals in currency), for duties charged on it: US Section 232 steel, aluminum and copper derivatives. Without it, those duties come back as regulatory (conditional). |
metalWeightPercent | number | no | Share of the product’s weight that is metal (0–100), for content-based exemptions. |
conditions | string[] | no | Rate conditions the goods meet, when the destination reserves a rate for them: a harmonised kind (pharmaceutical, end_use, certificate, company, quality, route) or the publisher’s own code (EU TARIC additional code 2500). Without it the default rate is charged (the unconditional one, or the highest when every rate has a condition); the rates you could claim are returned in options with their condition. The importer must hold what justifies a claimed condition. |
Responses
200 The landed-cost breakdown and the credits charged. CalculateResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error422 Destination not supported (unsupported_country). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/calculate' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"hsCode":"6109.10.00.12","originCountry":"CN","destinationCountry":"US","value":120,"currency":"USD","shippingCost":15}'
POST /v1/calculate/shipment Calculate a whole shipment
Requires an API key. Operation ID calculateShipment.
Duties, taxes, fees and the landed cost of a cart or order to one destination: up to 50 lines, freight and insurance shared out by value, one de minimis check, and per-entry fees charged once. Costs 1 credit per line. Priced on border.bot’s own data: a destination it doesn’t cover yet returns 422 unsupported_country without charging.
API key scope: calculate.
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
| Name | Type | Required | Description |
|---|
destinationCountry | string | yes | Destination: one GET /countries marks landedCost. ISO 3166-1 alpha-2, case-insensitive. |
currency | string | no | ISO 4217 currency of value, shippingCost and insuranceCost. |
shippingCost | number | no | Shipping cost (affects CIF-based duty/VAT bases). |
insuranceCost | number | no | Insurance cost. |
transportMode | "air" | "sea" | "road" | "rail" | no | Transport mode (fees such as HMF apply to sea freight). |
shippingTerms | "EXW" | "FCA" | "FAS" | "FOB" | "CFR" | "CIF" | "CPT" | "CIP" | "DAP" | "DPU" | "DDP" | no | Incoterm of the price (Incoterms 2020). Under C and D terms (CFR, CIF, CPT, CIP, DAP, DPU, DDP) the price already carries the goods to the destination, so shippingCost isn’t added to the customs value again. |
shipmentChannel | "courier" | "postal" | no | How the parcel travels: courier (express carriers, default) or postal — affects de minimis and channel-specific regimes. |
entryDate | string | no | Calculate with the rates in force on this date (YYYY-MM-DD). Default: today. |
tradeAgreement | string | no | Ignored. Preferential rates are applied automatically from the origin and destination; the agreement used is returned as tradeAgreement. |
region | string | no | State, province or territory, where import taxes differ inside the country. Canada needs one (ON, QC, BC…). |
purpose | "sale" | "gift" | "sample" | "return" | no | Why the goods are sent: some thresholds differ for gifts, samples and returns. Default: sale. |
businessBuyer | boolean | no | The buyer is a business: some taxes are reverse-charged or apply differently. |
sellerRegistrations | string[] | no | Tax schemes the seller is registered for (EU_IOSS, GB_VAT, AU_GST, NZ_GST, NO_VOEC…). Taxes the seller collects at checkout are returned with collectedBy: "seller". |
preference | string | no | best (default): the lowest preferential rate the origin qualifies for. none: the general rate only. Or a programme code (S for USMCA into the US). Every option is returned in options. |
claims | string[] | no | Exemption, relief and quota codes being claimed (US Chapter 99 exclusions such as 9903.88.69; an end-use authorisation such as TARIC document N990; a tariff quota order number such as 050331). Codes you could claim are returned in claims.available. |
enforceValidation | boolean | no | Refuse a shipment that matches one of the destination’s reject validation rules (400 invalid_input, details.reason: "validation_failed", refunded) instead of reporting it in validation. |
items | object[] | yes | The shipment’s lines (up to 50), each with its code, origin and line value. Freight and insurance are shared out by value. |
Responses
200 The shipment’s landed cost, line by line, and the credits charged. CalculateShipmentResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error422 Destination not covered by border.bot’s own data (unsupported_country). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/calculate/shipment' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"destinationCountry":"GB","currency":"USD","shippingCost":12,"items":[{"hsCode":"6109.10","originCountry":"CN","value":60,"quantity":3},{"hsCode":"6204.62","originCountry":"BD","value":45}]}'
POST /v1/calculate/stacking Duty stacking
Requires an API key. Operation ID dutyStacking.
Which duty lines apply to a code from an origin on a date, without a shipment: the base duty, then every additional measure (Section 301, 232, reciprocal…), which exemption codes remove which, the measures that depend on metal content or a claim, and the claim codes you could file. Up to 100 lines; free unless your pricing says otherwise. Answers from border.bot’s own data: a destination it doesn’t cover returns 422 unsupported_country.
API key scope: calculate.
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
| Name | Type | Required | Description |
|---|
items | object[] | yes | |
Responses
200 Each line’s stack. StackingResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error422 A destination border.bot’s own data doesn’t cover yet (unsupported_country). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/calculate/stacking' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"items":[{"hsCode":"8479.89.94","originCountry":"CN","destinationCountry":"US"}]}'
Compliance
Restricted and prohibited goods (free).
POST /v1/restrictions Check restricted goods
Requires an API key. Operation ID checkRestrictedGoods.
Check items against the destination’s import prohibitions and restrictions and, when originCountry is given, the origin’s export rules (sanctions, agency requirements such as FDA or APHIS), plus your own rules (/restrictions/rules). It uses no credits and is limited to 10 checks per minute per workspace (up to 100 items each). Destinations without coverage return 422 unsupported_country.
API key scope: compliance.
Request body
| Name | Type | Required | Description |
|---|
destinationCountry | string | yes | Ship-to country — its import rules are checked. ISO 3166-1 alpha-2, case-insensitive. |
originCountry | string | no | Ship-from country — its export rules are checked when given. ISO 3166-1 alpha-2, case-insensitive. |
items | RestrictionItem[] | yes | 1–100 items. |
reference | string | no | Your reference (an order or customer id), kept with the check as evidence. |
Responses
200 Matches per item (an empty restrictions list means nothing matched). RestrictionsResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the compliance scope (forbidden, details.reason: "missing_scope"). Error422 Restricted-goods checks are not available for this destination (unsupported_country). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). Error504 The engine timed out (upstream_timeout). Error
Example
curl -X POST 'https://api.border.bot/v1/restrictions' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"destinationCountry":"US","originCountry":"CN","items":[{"sku":"LAMP-01","title":"Lithium battery desk lamp","hsCode":"9405.21"}]}'
GET /v1/restrictions/checks Restricted-goods checks
Requires an API key. Operation ID listRestrictionChecks.
Your restricted-goods checks, newest first (up to 200): the evidence of what was checked, for which lane, by which engine, and what matched. Free.
API key scope: compliance.
Parameters
| Name | Type | Required | Description |
|---|
limit | integer | no | |
Responses
200 Your checks. RestrictionChecksResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the compliance scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/restrictions/checks' \
-H 'Authorization: Bearer bb_live_...'
GET /v1/restrictions/rules Your restricted-goods rules
Requires an API key. Operation ID listRestrictionRules.
Rules your restricted-goods checks apply on top of border.bot’s (a compliance team’s own list). Free.
API key scope: settings.
Responses
200 Your rules. RestrictionRulesResponse401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/restrictions/rules' \
-H 'Authorization: Bearer bb_live_...'
POST /v1/restrictions/rules Add a restricted-goods rule
Requires an API key. Operation ID addRestrictionRule.
A rule matched by HS code (and everything under it), by words in the item, or by origin, for one country or *. Free.
API key scope: settings.
Request body
| Name | Type | Required | Description |
|---|
country | "*" | string | no | |
direction | "import" | "export" | no | |
ruleType | "prohibition" | "restriction" | "observation" | yes | |
code | string | no | |
keywords | string[] | no | |
origins | string[] | no | |
title | string | yes | |
summary | string | no | |
Responses
200 The rule. RestrictionRuleResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X POST 'https://api.border.bot/v1/restrictions/rules' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"country":"US","ruleType":"prohibition","code":"9304","keywords":["airsoft"],"title":"No airsoft or air guns (company policy)"}'
DELETE /v1/restrictions/rules Remove a restricted-goods rule
Requires an API key. Operation ID removeRestrictionRule.
Removes one of your rules by its id. Free.
API key scope: settings.
Parameters
| Name | Type | Required | Description |
|---|
id | string (uuid) | yes | The rule’s id. |
Responses
200 Whether a rule was removed. RestrictionRuleRemoveResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the settings scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X DELETE 'https://api.border.bot/v1/restrictions/rules' \
-H 'Authorization: Bearer bb_live_...'
POST /v1/screen Screen a person or company
Requires an API key. Operation ID screenParty.
Check a name (and a company) against denied-party lists: the US Consolidated Screening List (the SDN list, the Entity List, the Denied Persons List and the other export-screening lists of Commerce, State and the Treasury). Names and aliases match exactly or fuzzily (from 0.7); each check is kept as your evidence. Free, up to 60 a minute per workspace. A match is a lead to review, not a verdict.
API key scope: compliance.
Request body
| Name | Type | Required | Description |
|---|
name | string | yes | The person’s or company’s name, as you have it. |
company | string | no | A company to screen too (a buyer and their employer, say). |
country | string | no | The party’s country (ISO-2): matches with an address, nationality or flag there are flagged countryMatch. |
type | "individual" | "entity" | no | Only parties of this type. Vessels, aircraft and parties of unknown type are always screened. |
reference | string | no | Your reference (an order or customer id), kept with the check as evidence. |
Responses
200 The matches, best first, and the lists screened. ScreenResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the compliance scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X POST 'https://api.border.bot/v1/screen' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"name":"<string>"}'
GET /v1/screen/checks Your screening checks
Requires an API key. Operation ID listScreeningChecks.
Every screening your workspace ran, newest first: the evidence that a name was screened, when, against which lists, and what matched. Free.
API key scope: compliance.
Parameters
| Name | Type | Required | Description |
|---|
limit | integer | no | |
Responses
200 Your checks. ScreeningChecksResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the compliance scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/screen/checks' \
-H 'Authorization: Bearer bb_live_...'
Account
Your workspace, credits and usage.
GET /v1/me Who am I
Requires an API key. Operation ID getWorkspace.
The workspace and API key behind this request, with the current credit balance.
Any API key may call this.
Responses
200 The calling workspace. MeResponse401 Missing, invalid, revoked or expired API key (unauthorized). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/me' \
-H 'Authorization: Bearer bb_live_...'
GET /v1/credits Credit balance
Requires an API key. Operation ID getCredits.
Current balance, lifetime totals and the 20 most recent ledger entries (purchases, usage, refunds).
API key scope: account.
Responses
200 Balance and recent ledger. CreditsResponse401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the account scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/credits' \
-H 'Authorization: Bearer bb_live_...'
GET /v1/usage Usage history
Requires an API key. Operation ID listUsage.
Every billable request made by the workspace (dashboard, API and MCP), newest first, with cursor pagination.
API key scope: account.
Parameters
| Name | Type | Required | Description |
|---|
limit | integer | no | Page size (1–100). |
cursor | string | no | Opaque cursor from a previous page. |
action | "classify" | "classify_max" | "classify_regulator" | "calculate" | "calculate_stacking" | "extract_product" | "infer_origin" | no | Only this action. |
Responses
200 A page of usage events. UsageResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the account scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/usage' \
-H 'Authorization: Bearer bb_live_...'
GET /v1/requests Request log
Requires an API key. Operation ID listApiRequests.
Every request your API keys made in the last 30 days, newest first: what it called, how it ended (status and error code), how long it took, and, for requests that change something, the body. Quote a request's requestId (its X-Request-Id) to support.
API key scope: account.
Parameters
| Name | Type | Required | Description |
|---|
limit | integer | no | Page size (1–100). |
cursor | string | no | The next of the previous page. |
failed | "true" | "false" | no | true: only requests that failed (status 400 and up). |
requestId | string | no | One request, by the X-Request-Id its response carried. |
Responses
200 A page of requests. ApiRequestsResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the account scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/requests' \
-H 'Authorization: Bearer bb_live_...'
Origin
POST /v1/origin Infer country of origin
Requires an API key. Operation ID inferOrigin.
Where a product is made, as a probability per country with the evidence behind each: the product page’s data, “Made in …” text, a label in the photo, an AI estimate from brand, materials and price, the ship-from country and the barcode’s GS1 prefix. An AI estimate alone is never reported as high confidence. needsReview (with reviewReason) says when a person should confirm, because origin drives duty rates: below reviewThreshold (the request’s, else the workspace’s setting, else the platform default of 0.8), or when strong evidence disagrees.
API key scope: origin.
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
| Name | Type | Required | Description |
|---|
productUrl | string | no | Product page URL: its structured data and “Made in …” text are read first. |
imageUrl | string | no | Product photo (JPEG, PNG, WebP or GIF, up to 6 MB), as an http(s) URL or a base64 data URL: a visible “Made in …” label is strong evidence. |
description | string | no | Product description. |
title | string | no | Product name. |
brand | string | no | Brand. |
sku | string | no | Your SKU (kept with the result). |
gtin | string | no | Barcode (GTIN, UPC, EAN). Its GS1 prefix shows where the brand registered: a weak hint, never proof. |
material | string | no | Main materials. |
categories | string[] | no | Category path, broadest first. |
price | number | no | Selling price, in currency. |
currency | string | no | ISO 4217 currency of price. |
shipFromCountry | string | no | Country the goods ship from (often, not always, where they are made). ISO 3166-1 alpha-2, case-insensitive. |
reviewThreshold | number | no | When needsReview is set (0–1). Inferring: an answer below this probability. Validating: a declaration whose probabilityOfMisrepresentation is at or above it. Without it, the workspace’s setting (dashboard → Settings → Origin review) applies, then the platform default (0.8 to infer, 0.3 to validate). |
Responses
200 The most likely origin, its probability, the alternates and the evidence. InferOriginResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/origin' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"title":"Organic cotton tee","brand":"Example","productUrl":"https://shop.example.com/products/organic-tee"}'
POST /v1/origin/validate Check a declared country of origin
Requires an API key. Operation ID validateOrigin.
How believable a declared origin is, given everything else known about the product: the share of the evidence pointing elsewhere (probabilityOfMisrepresentation), a verdict, the likely origin, and the reasons in words. With no evidence either way the verdict is unknown, never a guess. needsReview flags it when the misrepresentation probability reaches reviewThreshold (the request’s, else the workspace’s setting, else the platform default of 0.3), or when there is no evidence.
API key scope: origin.
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
| Name | Type | Required | Description |
|---|
productUrl | string | no | Product page URL: its structured data and “Made in …” text are read first. |
imageUrl | string | no | Product photo (JPEG, PNG, WebP or GIF, up to 6 MB), as an http(s) URL or a base64 data URL: a visible “Made in …” label is strong evidence. |
description | string | no | Product description. |
title | string | no | Product name. |
brand | string | no | Brand. |
sku | string | no | Your SKU (kept with the result). |
gtin | string | no | Barcode (GTIN, UPC, EAN). Its GS1 prefix shows where the brand registered: a weak hint, never proof. |
material | string | no | Main materials. |
categories | string[] | no | Category path, broadest first. |
price | number | no | Selling price, in currency. |
currency | string | no | ISO 4217 currency of price. |
shipFromCountry | string | no | Country the goods ship from (often, not always, where they are made). ISO 3166-1 alpha-2, case-insensitive. |
reviewThreshold | number | no | When needsReview is set (0–1). Inferring: an answer below this probability. Validating: a declaration whose probabilityOfMisrepresentation is at or above it. Without it, the workspace’s setting (dashboard → Settings → Origin review) applies, then the platform default (0.8 to infer, 0.3 to validate). |
declaredOrigin | string | yes | The country of origin declared (by a supplier, a listing, a customs entry). ISO 3166-1 alpha-2, case-insensitive. |
Responses
200 The verdict on the declared origin, with reasons. ValidateOriginResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/origin/validate' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"declaredOrigin":"US","title":"Wireless earbuds","description":"Bluetooth 5.3. Made in China."}'
POST /v1/origin/batch Infer or check the origin of up to 50 products
Requires an API key. Operation ID originBatch.
Each item is inferred, or validated when it has declaredOrigin. Items run four at a time, each charged as one origin call; one failing (bad photo, insufficient credits) never stops the others. With an Idempotency-Key, each item replays on its own.
API key scope: origin.
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
| Name | Type | Required | Description |
|---|
items | object[] | yes | |
reviewThreshold | number | no | The review threshold for every item that doesn’t set its own reviewThreshold (see POST /v1/origin). |
Responses
200 One result per item, in order. OriginBatchResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error402 Not enough credits (insufficient_credits). Nothing was charged. Error403 Workspace suspended, action disabled, or the key lacks the scope (org_suspended, action_disabled, forbidden). Error409 A request with this Idempotency-Key is still in progress (conflict). Error429 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. Error500 Unexpected error (internal). Error502 The engine failed (upstream_error). The credits are refunded automatically. Error504 The engine timed out (upstream_timeout). The credits are refunded automatically. Error
Example
curl -X POST 'https://api.border.bot/v1/origin/batch' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"items":[{"title":"Organic cotton tee","brand":"Example"},{"title":"Wireless earbuds","declaredOrigin":"US","gtin":"6901234567892"}]}'
Bulk
GET /v1/bulk/runs Your bulk runs
Requires an API key. Operation ID listBulkRuns.
Your workspace’s bulk runs, newest first.
API key scope: bulk.
Parameters
| Name | Type | Required | Description |
|---|
limit | integer | no | |
Responses
200 The runs. BulkRunsResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the bulk scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/bulk/runs' \
-H 'Authorization: Bearer bb_live_...'
POST /v1/bulk/runs Start a bulk run
Requires an API key. Operation ID startBulkRun.
Classify, calculate, find agency product codes or find and check countries of origin for up to 10,000 items in the background. Each item is charged when it runs (as its single request would be) and refunded if it fails; a run stops when your credits run out. Follow it with GET /bulk/status, read its answers with GET /bulk/results.
API key scope: bulk.
Request body
| Name | Type | Required | Description |
|---|
Responses
200 The run, queued. BulkRunResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the bulk scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X POST 'https://api.border.bot/v1/bulk/runs' \
-H 'Authorization: Bearer bb_live_...' \
-H 'Content-Type: application/json' \
-d '{"kind":"classify","reference":"catalogue-2026-10","items":[{"description":"Men'\''s cotton t-shirt","destinationCountry":"US"},{"description":"Stainless steel water bottle, 750 ml","destinationCountry":"GB"}]}'
DELETE /v1/bulk/runs Cancel a bulk run
Requires an API key. Operation ID cancelBulkRun.
Stops a run after the chunk it is working on. Items already done stay done (and charged).
API key scope: bulk.
Parameters
| Name | Type | Required | Description |
|---|
id | string | yes | |
Responses
200 The run, cancelled. BulkRunResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the bulk scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X DELETE 'https://api.border.bot/v1/bulk/runs' \
-H 'Authorization: Bearer bb_live_...'
GET /v1/bulk/status A bulk run’s progress
Requires an API key. Operation ID bulkRunStatus.
How far a run has got: items done, succeeded and failed, and the credits charged so far.
API key scope: bulk.
Parameters
| Name | Type | Required | Description |
|---|
id | string | yes | |
Responses
200 The run. BulkRunResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the bulk scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/bulk/status' \
-H 'Authorization: Bearer bb_live_...'
GET /v1/bulk/results A bulk run’s results
Requires an API key. Operation ID bulkResults.
The answers so far, in item order, up to 1,000 at a time (next is the following page’s offset). Each is exactly what its single endpoint returns, or the error it failed with.
API key scope: bulk.
Parameters
| Name | Type | Required | Description |
|---|
id | string | yes | |
offset | integer | null | no | |
limit | integer | no | |
Responses
200 A page of results. BulkResultsResponse400 Invalid input (invalid_input). Error401 Missing, invalid, revoked or expired API key (unauthorized). Error403 The API key doesn’t have the bulk scope (forbidden, details.reason: "missing_scope"). Error429 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. Error500 Unexpected error (internal). Error
Example
curl -X GET 'https://api.border.bot/v1/bulk/results' \
-H 'Authorization: Bearer bb_live_...'