TarriAPI

API Reference · v1

Tarri Classification API

Send a product description or photo; get back the HTS code, the real stacked landed duty, the CBP rulings behind it, and a calibrated confidence score — the same engine that powers the Tarri app, over a single REST call.

Introduction

The Tarri API is a JSON-over-HTTPS endpoint. There is one method — classify a product — and it returns everything the app shows: the ranked HTS classification, a full landed-duty breakdown (Section 301 / 122 / 232 / MPF / HMF, gated by country of origin), and an honest confidence score that tells you when to trust the answer and when to review it.

Base URLhttps://app.tarri.ai
AuthAuthorization: Bearer <key>
Content typeapplication/json
Plan requiredTeam or Enterprise

Authentication

Every request must include your secret API key as a bearer token:

Authorization: Bearer tarri_sk_live_…
  • Get your key in the app under Settings → API access (available on the Team plan). Click Generate API key.
  • One key per workspace. Generating again rotates the key — the previous one stops working immediately.
  • Shown once. For your security only a hash is stored; copy the key when it's shown. If you lose it, regenerate.
  • Keep it secret. Use it server-side only — never ship it in a browser, mobile app, or public repo.
Treat keys like passwords. Anyone with your key can spend your workspace's classification quota. Store it in an environment variable (TARRI_API_KEY), not in source.

Quickstart

Classify your first product in one call. Set TARRI_API_KEY in your environment, then:

curl -X POST https://app.tarri.ai/api/v1/classify \
  -H "Authorization: Bearer $TARRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "1500W stainless steel convection toaster oven",
    "origin": "CN",
    "fob": 42,
    "units": 1
  }'
const res = await fetch("https://app.tarri.ai/api/v1/classify", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.TARRI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    description: "1500W stainless steel convection toaster oven",
    origin: "CN",
    fob: 42,
    units: 1,
  }),
});

if (!res.ok) throw new Error(`Tarri API error ${res.status}`);
const result = await res.json();
console.log(result.best.htsNumber, result.best.confidence);
import os, requests

res = requests.post(
    "https://app.tarri.ai/api/v1/classify",
    headers={"Authorization": f"Bearer {os.environ['TARRI_API_KEY']}"},
    json={
        "description": "1500W stainless steel convection toaster oven",
        "origin": "CN",
        "fob": 42,
        "units": 1,
    },
    timeout=60,
)
res.raise_for_status()
result = res.json()
print(result["best"]["htsNumber"], result["best"]["confidence"])

A classification typically takes a few seconds (it runs retrieval, an AI rerank, and a CBP-ruling lookup). Use a request timeout of at least 60 seconds.

Rate limits & quota

API calls draw from the same monthly classification quota as the app — there is no separate API meter.

PlanClassifications / monthAPI access
Free10
Pro1,000
Team5,000
EnterpriseUnlimited

When the quota is exhausted the endpoint returns 402 quota_exceeded with plan, used, and limit. Quota resets at the start of each calendar month (UTC). Only successful (2xx) classifications count.

POST /api/v1/classify

Classify a single product. Returns the ranked HTS shortlist, the landed-duty breakdown, and a confidence score.

POST https://app.tarri.ai/api/v1/classify

Request parameters

A JSON body. Supply at least one of description, name, or imageUrl; everything else is optional but improves accuracy and unlocks the dollar duty breakdown.

FieldTypeRequiredDescription
description string Conditional Plain-language description of the product — what it is, material, and use. e.g. "1500W stainless steel convection toaster oven". At least one of description, name, or imageUrl is required.
name string Conditional Short product name. Combined with description as the classification query.
imageUrl string Conditional A public image URL or a base64 data URL of the product photo. Tarri describes the image, then classifies. Max ~10 MB.
origin string Optional ISO 3166-1 alpha-2 country of origin, e.g. CN, IN, US. Drives origin-gated tariffs (Section 301 applies to CN/HK; Section 122 is zeroed for US). Defaults to no origin gating.
fob number Optional FOB / customs value in USD. Required to compute the dollar landed-duty breakdown and per-unit value bands. Omit for a code-only classification.
units number Optional Pieces per set. With fob, yields the per-unit value used to resolve "valued … each" HTS value bands.
netWeightKg number Optional Net weight in kilograms. Needed for specific/compound rates expressed per kg.
containsSteel boolean Optional Whether the article contains steel (triggers a Section 232 line on the steel portion). Defaults to false.
steelPercent number Optional Percent (0–100) of FOB attributable to steel content. Used with containsSteel.
oceanShipment boolean Optional Whether the shipment arrives by ocean (adds the Harbor Maintenance Fee). Defaults to true.
settings object Optional Override the duty-engine parameters: section122Rate, steel232Rate, mpfRate, mpfMin, mpfMax, hmfRate (all numbers). Defaults to the current published rates.

The response object

A 200 returns the full classification result:

FieldTypeDescription
bestCandidate | nullThe single best HTS classification, or null when Tarri abstains (no confident match).
candidatesCandidate[]Ranked alternatives (highest confidence first), including best. Present even when abstained, so you can review the closest options.
abstainedbooleanWhen true, no candidate cleared the confidence threshold — review candidates manually rather than auto-accepting.
abstainReasonstring | nullWhy Tarri abstained, when it did.
querystringThe normalized text query that was classified.
imageDescriptionstring | nullIf an image was supplied, the literal product description Tarri derived from it.
perUnitValuenumber | nullFOB ÷ units, when both were provided.
dutyDuty | nullThe landed-duty breakdown for best. null when no fob was supplied.
pipelineobjectDiagnostics: directCodeMatch, headings[], embeddingUsed, aiRerankUsed, rulingsUsed.

Example response (abridged):

{
  "best": {
    "htsNumber": "8516.60.40.70",
    "htsDigits": "8516604070",
    "item": "Electrothermic ovens … Cooking stoves, ranges and ovens",
    "confidence": 0.94,
    "generalDutyRate": 2.7,
    "section301Rate": 25,
    "generalDuty": "2.7%",
    "section301Duty": "25%",
    "rationale": "Consumer electric toaster oven — domestic electrothermic oven under 8516.60.",
    "retrievalScore": 0.81
  },
  "candidates": [ /* … ranked alternatives … */ ],
  "abstained": false,
  "abstainReason": null,
  "perUnitValue": 42,
  "duty": {
    "fob": 42,
    "totalKnownDuty": 16.04,
    "effectiveKnownRate": 38.2,
    "landedCostKnown": 58.04,
    "lines": [ /* Standard, Sec 301, Sec 122, MPF, HMF … */ ],
    "manualReview": false,
    "missingInputs": []
  },
  "pipeline": { "aiRerankUsed": true, "rulingsUsed": true, "headings": ["8516"] }
}

The candidate object

Each entry in best and candidates:

FieldTypeDescription
htsNumberstringFormatted 10-digit HTS code, e.g. "8516.60.40.70".
htsDigitsstringUnformatted digits, e.g. "8516604070".
itemstringThe full HTS line description (breadcrumb of headings → statistical suffix).
confidencenumberCalibrated confidence in [0, 1]. ≥ 0.80 high, 0.60–0.79 medium, < 0.60 low.
generalDutyRatenumber | nullStandard ad-valorem rate as a percent (e.g. 2.7), or null for specific/compound rates.
section301Ratenumber | nullSection 301 rate as a percent, when applicable to the origin.
generalDutystringRaw HTSUS general duty expression (e.g. "2.7%", "5.3¢/kg + 1.4%").
section301DutystringRaw Section 301 duty expression.
rationalestringShort explanation of why this line was chosen, including any CBP-ruling support.
retrievalScorenumberInternal hybrid-retrieval score (debugging aid).

The duty object

Present when you pass a positive fob. It computes the full landed-duty stack by country of origin.

FieldTypeDescription
fobnumberThe FOB value the breakdown was computed on.
linesDutyLine[]Each duty component: { kind, label, basis, rate, amount, reviewRequired, note?, missing? } (Standard, Section 301, Section 122, Section 232, MPF, HMF).
totalKnownDutynumberSum of all computable duty lines, in USD.
landedCostKnownnumberFOB + total known duty.
effectiveKnownRatenumberTotal known duty ÷ FOB, as a percent.
manualReviewbooleanTrue if any line needs manual review (e.g. an un-parsable specific rate).
missingInputsstring[]Inputs needed to finish the calc (e.g. ["net weight"] for a per-kg rate).

Confidence & abstention

Tarri is calibrated — it tells you how sure it is, and refuses to bluff. Read these two fields before you trust a result programmatically:

  • best.confidence — a score in [0, 1]. As a rule of thumb: ≥ 0.80 high (safe to auto-accept), 0.60–0.79 medium (spot-check the value band), < 0.60 low.
  • abstained — when true, no candidate was a confident fit. best is null; route the item to a human and review candidates (still returned) instead of auto-accepting.
Recommended gate: auto-accept when !abstained && best.confidence >= 0.8; otherwise queue for review. This keeps a documented reasonable-care trail.

Errors

Errors return a non-2xx status with a JSON body of the form { "error": "…" }.

StatuserrorMeaning
400Bad RequestThe body failed validation — missing all of description/name/imageUrl, or a field has the wrong type.
401missing_api_keyNo Authorization: Bearer header was sent.
401invalid_api_keyThe key does not match any workspace (revoked, mistyped, or regenerated).
402quota_exceededThe workspace hit its monthly classification limit. Response includes plan, used, limit.
403api_access_requires_teamThe key is valid but its workspace is no longer on a plan that includes API access (Team or Enterprise).
5xxserver_errorA transient server/database error. Safe to retry with backoff.

Full examples

The same request in three languages:

curl -X POST https://app.tarri.ai/api/v1/classify \
  -H "Authorization: Bearer $TARRI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "1500W stainless steel convection toaster oven",
    "origin": "CN",
    "fob": 42,
    "units": 1
  }'
const res = await fetch("https://app.tarri.ai/api/v1/classify", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.TARRI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    description: "1500W stainless steel convection toaster oven",
    origin: "CN",
    fob: 42,
    units: 1,
  }),
});

if (!res.ok) throw new Error(`Tarri API error ${res.status}`);
const result = await res.json();
console.log(result.best.htsNumber, result.best.confidence);
import os, requests

res = requests.post(
    "https://app.tarri.ai/api/v1/classify",
    headers={"Authorization": f"Bearer {os.environ['TARRI_API_KEY']}"},
    json={
        "description": "1500W stainless steel convection toaster oven",
        "origin": "CN",
        "fob": 42,
        "units": 1,
    },
    timeout=60,
)
res.raise_for_status()
result = res.json()
print(result["best"]["htsNumber"], result["best"]["confidence"])

Support

Questions, higher limits, or a bulk/Enterprise plan? Email hello@tarri.ai or manage your key any time in Settings → API access.

Classifications are decision support, not customs or legal advice. Review before filing.