GET STARTED

Quickstart: classify your first product

Get a key, paste it in $CLEO_API_KEY, send one curl, and read back an HS code with the notes behind it. Then send your catalogue as a batch.

1. Get an API key

Sign up, then create a key in the dashboard. New accounts start on the Sandbox: 200 units, no card. The key is shown once; copy it then.

2. Paste it in $CLEO_API_KEY

terminal
export CLEO_API_KEY="ld_live_…"   # the key shown once in the dashboard

3. Classify one product

A men's knitted cotton T-shirt shipped to France, with the facts a broker would ask for. It costs 1 unit (1 + the number of kit components).

curl · POST /v2/customs/classifications
curl -X POST "https://api.legaldata.cleolabs.co/v2/customs/classifications" \
  -H "Authorization: Bearer $CLEO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"item_id":"SKU-TS-100","description":"T-shirt en coton, maille (tricoté), 100 % coton, pour hommes","country":"FR","lang":"fr","facts":{"process":"knitted","composition":[{"material":"cotton","percent":100}],"audience":"men"}}'
example response · 200 · captured 2026-09-29 · trimmed to 2 of 5 candidates, advisory_disclaimer shown below
{
  "data": {
    "item_id": "SKU-TS-100",
    "status": "ambiguous",
    "missing_attributes": [],
    "candidates": [
      {
        "code": "61091000",
        "system": "cn8",
        "country": "EU",
        "confidence": 0.54,
        "evidence": [
          {
            "kind": "chapter_note",
            "ref": "Chapter 61 Note 1",
            "url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32025R1926",
            "excerpt": "This chapter applies only to made-up knitted or crocheted articles.",
            "source_version": "32025R1926"
          },
          {
            "kind": "additional_note",
            "ref": "Chapter 61 Additional Note 2",
            "url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32025R1926",
            "excerpt": "For the purposes of heading 6109 , the terms 'singlets' and 'other vests' include garments, even if of a fancy design, worn next to the body, without collar, with or without sleeves, including those with shoulder straps.\n\nThese garments, which are intended to cover the upper part of the body, often possess many characteristics in common with those of T-shirts or with more traditional kinds of singlets and other vests of heading 6109 .",
            "source_version": "32025R1926"
          },
          {
            "kind": "chapter_note",
            "ref": "Chapter 61 Note 5",
            "url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32025R1926",
            "excerpt": "Heading 6109 does not cover garments with a drawstring, ribbed waistband or other means of tightening at the bottom of the garment.",
            "source_version": "32025R1926"
          }
        ],
        "set_aside_reason": null,
        "ambiguous": false
      },
      {
        "code": "610910",
        "system": "hs6",
        "country": null,
        "confidence": 0.26,
        "evidence": [
          {
            "kind": "chapter_note",
            "ref": "Chapter 61 Note 1",
            "url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32025R1926",
            "excerpt": "This chapter applies only to made-up knitted or crocheted articles.",
            "source_version": "32025R1926"
          },
          {
            "kind": "additional_note",
            "ref": "Chapter 61 Additional Note 2",
            "url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32025R1926",
            "excerpt": "For the purposes of heading 6109 , the terms 'singlets' and 'other vests' include garments, even if of a fancy design, worn next to the body, without collar, with or without sleeves, including those with shoulder straps.\n\nThese garments, which are intended to cover the upper part of the body, often possess many characteristics in common with those of T-shirts or with more traditional kinds of singlets and other vests of heading 6109 .",
            "source_version": "32025R1926"
          }
        ],
        "set_aside_reason": "It is the 6-digit parent, less specific than the CN8 line required for FR.",
        "ambiguous": false
      }
    ],
    "coverage": {
      "level": "national",
      "national_systems": [
        "cn8"
      ],
      "via_bloc": "EU",
      "hint": null
    },
    "provenance": {
      "reranker": "bedrock-sonnet-5.5",
      "acceptance_policy": "provisional_floor_0.7+signal_agreement_v1+thresholds_sonnet_v1+cited_evidence_required",
      "acceptance_checks": [
        "calibrated_confidence_below_floor"
      ],
      "evidence_source": "index",
      "evidence_rejected": 0,
      "tree_descent": {
        "hs6": [
          "610910"
        ],
        "children_count": 1,
        "chosen": "61091000",
        "path": [
          "T-shirts, singlets and other vests, knitted or crocheted",
          "Of cotton (6109.10)",
          "61091000 Of cotton"
        ]
      }
    }
  }
}

In this capture the answer is ambiguous: the French national line 61091000 (cn8) and the HS6 row 610910 lead, each with verbatim notes as evidence. Your own call can return a different status, order or confidence; provenance says which model answered and why the gate held. Send "system": "cn8" when you want the national line.

data.statusFor example classified, needs_information, needs_review or ambiguous; the reference lists every value. needs_information comes with missing_attributes and questions to ask your supplier.
data.candidates[]code, system (hs6, cn8, hts…), confidence, and set_aside_reason when a candidate was ranked below another.
data.candidates[].evidence[]Verbatim excerpts of the notes behind the candidate, each with ref, url and source_version.
data.coverageWhether the destination answers at the national line (level: national, national_systems: ["cn8"]) or at HS6 only.
data.provenanceThe acceptance policy used for classified and whether the notes index was available.
advisory_disclaimerA top-level string on every classification response. Show it verbatim next to any recommendation.

A bare description with no facts comes back needs_information with the questions to ask. The customs guide shows it, with kits, dossiers and code validity.

4. Your first catalogue batch

Send up to 2,000 items in one request. The answer is a job id (202); poll it, then download the items as CSV. Replaying the same Idempotency-Key within 24 h returns the same job and is not charged again. Units: the sum over items of min(1 + components, 11), so 2 units here.

curl · POST /v2/customs/classification-batches
curl -X POST "https://api.legaldata.cleolabs.co/v2/customs/classification-batches" \
  -H "Authorization: Bearer $CLEO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: my-first-batch" \
  -d '{"items":[{"item_id":"A1","description":"Cotton T-shirt, knitted, 100% cotton","country":"GB"},{"item_id":"A2","description":"Stainless steel water bottle 750 ml","country":"GB"}]}'
expected response · 202 · captured 2026-09-25
{
  "data": {
    "job_id": "06d435dc-6132-432e-b055-0e80d2790d59",
    "status": "queued",
    "total_items": 2,
    "replayed": false,
    "links": {
      "self": "/v2/customs/classification-batches/06d435dc-6132-432e-b055-0e80d2790d59"
    },
    "ignored_columns": []
  }
}
then
# Job status and the items, in submission order
curl -H "Authorization: Bearer $CLEO_API_KEY" \
  "https://api.legaldata.cleolabs.co/v2/customs/classification-batches/06d435dc-6132-432e-b055-0e80d2790d59"

# Every item as CSV, ready to paste back into your catalogue
curl -H "Authorization: Bearer $CLEO_API_KEY" -o items.csv \
  "https://api.legaldata.cleolabs.co/v2/customs/classification-batches/06d435dc-6132-432e-b055-0e80d2790d59/items.csv"

Webhooks classification_batch.completed and .failed tell you when the job ends: see Classify a whole catalog.

5. Legal data

The same key reads the legal corpus: search regulations, fetch a document, follow changes.

curl · GET /v2/search
curl -H "Authorization: Bearer $CLEO_API_KEY" \
  "https://api.legaldata.cleolabs.co/v2/search?q=GDPR&country=EU&limit=3"

6. Pick your integration surface

REST works everywhere; every operation is in the API reference. The TypeScript SDK and the MCP server sit on the same key. When the Sandbox runs out, pick a plan.