Classification + Tariff Calculation API API reference

Get HTS codes and duties from one call.

Two POST endpoints, one product or 120 per batch by default, with every field, error code and curl example.

Classification + Tariff Calculation API reference
On this page
  1. Overview
  2. Integration steps
  3. Single product
    1. Request headers
    2. Response headers
    3. Request body
    4. Successful response
    5. Error response
    6. Error codes
    7. Try it with curl
  4. Batch
    1. Headers
    2. Request body
    3. Successful response
    4. Error response
    5. Error codes
    6. Try it with curl
  5. Rate limits and quotas
  6. Errors and retries
  7. Key security
  8. Contact

API version v1 · Base URL api.gingercontrol.com

What does the Classification + Tariff Calculation API return?

The Classification + Tariff Calculation API is GingerControl's REST API for U.S. HTS classification and duty lookup. You POST a plain product description and its country of origin; the API returns the HTS code, the general and special rates, and each Chapter 99 duty layer with its code and rate. Use the single-product endpoint for one item or the batch endpoint for 120 items per call by default (sized to your throughput on request). Keys are issued offline, and the results feed entry prep that a licensed customs broker reviews.

  • Base URL: https://api.gingercontrol.com
  • Endpoints: POST /openapi/v1/tariff for one product, POST /openapi/v1/tariff/batch for up to 120
  • Authentication: an API key in the X-Api-Key header, issued offline by GingerControl
  • Format: JSON request bodies and JSON responses

How do I get access and integrate?

Read the contract below, book a call for an API key sized to your traffic, then wire the results into your own systems.

Read the API contract

Read the endpoint sections below in full before you start. They cover request patterns, input and output structures, error semantics and rate limits, which cuts integration and debugging time.

Ask for an API key

Book a call with us. We ask about your calling pattern, IP allowlist, peak QPS and latency expectations, then deliver your key through an offline channel, such as email.

Integrate

Most teams finish the basic integration quickly, then spend more of their time wiring the returned data into their own systems.

Keep your production key secure. At this stage, the API key is the sole basis for billing.

Single-product endpoint

Send one product description and its country of origin; get back its HTS code and tariff data.

POST/openapi/v1/tariffhttps://api.gingercontrol.com

Request headers

HeaderRequiredMeaningPurpose
Content-TypeYesRequest body format, set by the callerMust be application/json
X-Api-KeyYesCaller credential, issued offline by GingerControlAuthentication, rate limiting and quota control
X-Request-IdNoRequest trace ID, best generated by your systemTroubleshooting, log tracing and investigation; the server generates one if you leave it out

Response headers

HeaderDescriptionPurpose
Content-TypeAlways application/jsonThe response body format
X-Request-IdEchoes your value, or a server-generated oneLog it for troubleshooting
Retry-AfterReturned only with 429 Too Many RequestsHow many seconds to wait before retrying

Request body

FieldTypeRequiredNotes
descriptionstringYesProduct description. Must not be empty; up to 10,000 characters
country_of_originstringYesISO 3166-1 alpha-2 country code (code list). EU (European Union) and UK (United Kingdom) are also accepted; GB is processed as UK
extraobjectNoAdditional input. Only steel_pour_country and aluminum_pour_country are read; other fields are ignored
extra.steel_pour_countrystringNoSteel pour country, same rules as country_of_origin
extra.aluminum_pour_countrystringNoAluminum pour country, same rules as country_of_origin
JSONRequest example
{
  "description": "Cotton knit short sleeve T-shirt",
  "country_of_origin": "DE",
  "extra": {
    "steel_pour_country": "IT",
    "aluminum_pour_country": "FR"
  }
}

Successful response

FieldTypeNotes
hts_codestringThe HTS code for the product. Normally 10 digits; a split-code product may return an 8-digit parent code such as 9101.11.40
tariffsobject or nullTariff data for the product. May be null for split-code products
tariffs.general_ratestring or nullGeneral rate, which can be read as MFN
tariffs.special_ratestring or nullSpecial rate, lightly normalized (a trailing parenthetical note is removed, for example)
tariffs.<module_key>arrayChapter 99 entries for that module, each {code, rate}
tariffs.<module_key>[].codestringChapter 99 code, for example 9903.85.08
tariffs.<module_key>[].ratestringRate as a percentage string, such as 25%
componentsarrayComponents of a split-code product; omitted for ordinary products
components[].hts_codestringComponent HTS code, always 10 digits
components[].tariffsobject or nullTariff data for the component
JSONResponse example
{
  "hts_code": "6109.10.0012",
  "tariffs": {
    "general_rate": "16.5%",
    "special_rate": "Free",
    "Section 301": [],
    "Section 232 - Metals": []
  }
}

Each module key in tariffs, here Section 301 and Section 232 - Metals, holds a list of {code, rate} entries. An empty list means no Chapter 99 entry from that module applies to this product.

Error response

FieldTypeNotes
detailobjectError object
detail.codestringError code, for programmatic handling
detail.messagestringError message, for logging and troubleshooting
JSONError example
{
  "detail": {
    "code": "invalid_request",
    "message": "Invalid request body."
  }
}

Error codes

HTTP statusCodeMeaning
401 Unauthorizedmissing_api_keyX-Api-Key was not provided in the request headers
401 Unauthorizedinvalid_api_keyAPI key validation failed
403 Forbiddenapi_key_revokedThe API key has been revoked
403 Forbiddenclient_disabledThe caller account has been disabled
422 Unprocessable Entityinvalid_requestThe request body is malformed, fields are missing, field types are wrong, or values do not satisfy the contract
429 Too Many Requestsrequest_rate_limitedRequest-level protection was triggered
429 Too Many Requestsitem_rate_limitedItem-level quota protection was triggered
500 Internal Server Errorclassification_failedHTS identification failed
500 Internal Server Errorcalculator_failedTariff calculation failed
500 Internal Server Errorinternal_errorAn unclassified internal error occurred

Try it with curl

Replace YOUR_API_KEY with the key we issued you offline, then run the command as is.

cURLSingle-product call
curl -X POST 'https://api.gingercontrol.com/openapi/v1/tariff' \
  -H 'Content-Type: application/json' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'X-Request-Id: manual-single-001' \
  -d '{
    "description": "Cotton knit short sleeve T-shirt",
    "country_of_origin": "DE",
    "extra": {
      "steel_pour_country": "IT",
      "aluminum_pour_country": "FR"
    }
  }'

Batch endpoint

Send up to 120 products in one call, each with your own item_id; results come back in the order you sent them, each with its own status.

POST/openapi/v1/tariff/batchhttps://api.gingercontrol.com

Headers

The batch endpoint takes the same request headers and returns the same response headers as the single-product endpoint: Content-Type, X-Api-Key and the optional X-Request-Id in; Content-Type, X-Request-Id and, with a 429, Retry-After out.

Request body

FieldTypeRequiredNotes
itemsarrayYesProducts to process; up to 120 items in the current version
items[].item_idstringYesYour own identifier, to map each result back to your record. Must be unique within the request
items[].descriptionstringYesProduct description. Must not be empty; up to 10,000 characters
items[].country_of_originstringYesISO 3166-1 alpha-2 country code. EU and UK are also accepted; GB is processed as UK
items[].extraobjectNoOnly steel_pour_country and aluminum_pour_country are read; other fields are ignored
items[].extra.steel_pour_countrystringNoSteel pour country, same rules as country_of_origin
items[].extra.aluminum_pour_countrystringNoAluminum pour country, same rules as country_of_origin
JSONBatch request
{
  "items": [
    {
      "item_id": "SKU-DE-001",
      "description": "Cotton knit short sleeve T-shirt",
      "country_of_origin": "DE",
      "extra": {
        "steel_pour_country": "IT"
      }
    },
    {
      "item_id": "SKU-FR-002",
      "description": "Cotton crew neck T-shirt",
      "country_of_origin": "FR",
      "extra": {}
    }
  ]
}

Successful response

FieldTypeNotes
itemsarrayResults, in the same order as the request
items[].item_idstringYour identifier, for reconciliation
items[].statusstringok or failed
items[].hts_codestringHTS code of a successful item. Normally 10 digits; a split-code product may return an 8-digit parent code. Only when status is ok
items[].tariffsobject or nullTariff data of a successful item; may be null for split-code products. Only when status is ok
items[].tariffs.general_ratestring or nullGeneral rate, which can be read as MFN
items[].tariffs.special_ratestring or nullSpecial rate, lightly normalized
items[].tariffs.<module_key>arrayModule entries, each {code, rate}
items[].tariffs.<module_key>[].codestringChapter 99 code
items[].tariffs.<module_key>[].ratestringRate
items[].componentsarrayComponents of a split-code product; omitted for ordinary products. Only when status is ok
items[].components[].hts_codestringComponent HTS code, always 10 digits
items[].components[].tariffsobject or nullTariff data for the component
items[].codestringFailure code of a failed item. Only when status is failed
summaryobjectSummary of the batch request
summary.totalintegerItems in this request
summary.succeededintegerItems that succeeded
summary.failedintegerItems that failed
JSONBatch response
{
  "items": [
    {
      "item_id": "SKU-DE-001",
      "status": "ok",
      "hts_code": "6109.10.0012",
      "tariffs": {
        "general_rate": "16.5%",
        "special_rate": "Free",
        "Section 301": [],
        "Section 232 - Metals": []
      }
    },
    {
      "item_id": "SKU-FR-002",
      "status": "ok",
      "hts_code": "6109.10.0004",
      "tariffs": {
        "general_rate": "16.5%",
        "special_rate": "Free",
        "Section 301": [],
        "Section 232 - Metals": []
      }
    }
  ],
  "summary": {
    "total": 2,
    "succeeded": 2,
    "failed": 0
  }
}

Error response

Batch-level errors use the same detail object as the single-product endpoint, with detail.code and detail.message.

JSONBatch error
{
  "detail": {
    "code": "item_rate_limited",
    "message": "Item quota exceeded."
  }
}

Error codes

A batch-level error fails the whole request:

HTTP statusCodeMeaning
401 Unauthorizedmissing_api_keyX-Api-Key was not provided in the request headers
401 Unauthorizedinvalid_api_keyAPI key validation failed
403 Forbiddenapi_key_revokedThe API key has been revoked
403 Forbiddenclient_disabledThe caller account has been disabled
422 Unprocessable Entityinvalid_requestThe top-level structure is invalid, item_id values are duplicated, fields are missing, field types are wrong, or values do not satisfy the contract
429 Too Many Requestsrequest_rate_limitedRequest-level protection was triggered
429 Too Many Requestsitem_rate_limitedItem-level quota protection was triggered
500 Internal Server Errorinternal_errorAn unclassified batch-level internal error occurred

An item-level failure comes back inside a successful response, as status: failed with a code on that item:

CodeMeaning
classification_failedHTS identification failed for that item
calculator_failedTariff calculation failed for that item
internal_errorAn unclassified internal error occurred for that item

Try it with curl

Replace YOUR_API_KEY with the key we issued you offline, then run the command as is.

cURLBatch call
curl -X POST 'https://api.gingercontrol.com/openapi/v1/tariff/batch' \
  -H 'Content-Type: application/json' \
  -H 'X-Api-Key: YOUR_API_KEY' \
  -H 'X-Request-Id: manual-batch-001' \
  -d '{
    "items": [
      {
        "item_id": "SKU-DE-001",
        "description": "Cotton knit short sleeve T-shirt",
        "country_of_origin": "DE",
        "extra": {
          "steel_pour_country": "IT"
        }
      },
      {
        "item_id": "SKU-FR-002",
        "description": "Cotton crew neck T-shirt",
        "country_of_origin": "FR",
        "extra": {}
      }
    ]
  }'

How do rate limits and quotas work?

Every call passes request-level protection, and every product processed draws from an item-level quota tied to your API key.

  • When requests come too fast, the API returns 429 Too Many Requests.
  • Quota is consumed per product: the single-product endpoint uses 1, the batch endpoint uses the number of entries in items.
  • Both endpoints draw from the same item quota under the same API key.
  • Each key is configured to that customer's traffic model, so quotas differ from customer to customer.

How should I handle errors and retries?

Check the request body on a 422, wait the number of seconds in Retry-After on a 429, and log the X-Request-Id of every call.

  • 422 Unprocessable Entity: the body structure, field types or field values usually do not match the contract. Check the request body first.
  • 429 Too Many Requests: request-level protection or the item quota was triggered. Retry using the Retry-After response header.
  • Request IDs: keep request logs in your own system and record the X-Request-Id of each call; it cuts troubleshooting time during integration and operations.
  • Timeouts and alerts: set reasonable timeouts, retries and failure alerts so network fluctuations and upstream jitter are handled cleanly.

How do I keep my API key safe?

Keep the key on your servers, out of anything users can see, and tell us right away if a key may have leaked.

  • Never embed your API key in frontend code, browser code or anything exposed to end users.
  • Store keys in a secure configuration system, such as a secret manager or tightly controlled environment variables.
  • If a key is lost, may have leaked, or needs rotating, contact us offline as soon as possible.

Who do I contact for API support?

Email chen@gingercontrol.com for help with integration, rollout or day-to-day operations.

See what the API is for.

The API overview shows who uses it, a worked response and how to ask for a key.

Want an AI system that fits your process?

See GingerOS.

GingerOS is the trade compliance system we build on your own process: every code keeps its reason, and every change in your company triggers its own compliance recheck (tariffs, classification, export controls and more).

Click Me!

See how GingerOS works