Developer Docs

API Documentation

Integrate TTClassify into your systems with our Upload APIs. Post purchase orders and products for automated tariff classification.

Overview

TTClassify has been built from the ground up to be RESTful and secure. Use your credentials to connect and obtain a token, then use that token to make calls to our system.

All endpoints are protected with OAuth 2.0 Client Credentials via Microsoft Entra ID. You must obtain an access token and send it as a Bearer token in the Authorization header on every request.

Base Endpoints

POST/api/v2/system/purchase-orders/upload
POST/api/v2/system/products/upload

Request Content-Type

application/json

Token Request

application/x-www-form-urlencoded

Token Retrieval

Authenticate using Microsoft Entra ID Client Credentials flow. Request an access token from the token endpoint below, then include it as a Bearer token in all subsequent API calls.

Token Endpoint
https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token

The tenant-id, client_id, client_secret, and scope values are provided during onboarding. to get your API credentials.

Form Fields

FieldTypeRequiredNotes
grant_typestringYesMust be client_credentials
client_idstringYesProvided per tenant
client_secretstringYesProvided per tenant
scopestringYesUse api://<api-client-id>/.default (value provided by TariffTel)

Sample Request

cURL
curl -X POST 'https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=<your-client-id>' \
  --data-urlencode 'client_secret=<your-client-secret>' \
  --data-urlencode 'scope=api://<api-client-id>/.default'

Sample Response

JSON
{
  "token_type": "Bearer",
  "expires_in": 3599,
  "ext_expires_in": 3599,
  "access_token": "eyJ0eXAiOiJKV1QiL..."
}

Use the access_token value in the Authorization header of all API requests: Authorization: Bearer eyJ0eXAiOiJKV1QiL...

Common Request Requirements

Headers

  • Authorization: Bearer <token>
  • Content-Type: application/json
  • Accept: application/json

Batching

Both upload endpoints accept a JSON array of items in a single request.

Rate Limiting

Both endpoints are rate limited to 30 requests per minute per client.

Purchase Orders Upload

TTClassify takes a purchase order and classifies each item listed. The specification below allows this to happen automatically using a structured, nested request body.

POST/api/v2/system/purchase-orders/upload

Rate limited to 30 requests per rolling 1-minute fixed window.

Request Body Example

JSON
[
  {
    "orderNumber": "ORD-12122",
    "supplierIdentifier": "THOC",
    "dueDateForClassification": "2025-11-05",
    "product": {
      "code": "PRD-ORD-12121",
      "description": "Grated Cheese",
      "productDetails": {
        "primarySize": "NA",
        "unitCost": 1.95,
        "currencyCode": "GBP",
        "gender": "NA",
        "productGroup": "Food & Drink - Cheese"
      },
      "movement": {
        "countryOfOriginCode": "CN",
        "destinationCountryCode": "GB"
      }
    },
    "orderDetails": {
      "incoTermCode": "",
      "incoTermDescription": "",
      "shipmentMethod": "",
      "orderLineTypeCode": "string",
      "orderLineTypeDescription": "string",
      "orderQuantity": 2
    }
  }
]

Request Body Example (minimal)

JSON
[
  {
    "orderNumber": "ORD-12122",
    "supplierIdentifier": "THOC",
    "dueDateForClassification": "2025-11-05",
    "product": {
      "code": "PRD-ORD-12121",
      "description": "Grated Cheese",
      "movement": {
        "destinationCountryCode": "GB"
      }
    }
  }
]

Field Reference

Order

FieldTypeNotes
orderNumberstringRequired. Unique order ref within the organization. Max 50 chars.
supplierIdentifierstringRequired. Supplier code known to TariffTel; the supplier must already exist. Max 50 chars.
dueDateForClassificationstring (yyyy-MM-dd)Required for SLA routing; UTC recommended.
productobjectRequired. The product being ordered (see below).
orderDetailsobjectOptional. Additional order line details (see below).

product

FieldTypeNotes
codestringRequired. Your product/SKU identifier. Max 100 chars.
descriptionstringRequired. Human-readable description; read by TCAS to suggest classifications. Max 500 chars.
productDetailsobjectOptional (see below).
movementobjectRequired for orders (see below).

product.productDetails (optional)

FieldTypeNotes
primarySizestringOptional (e.g., NA). Max 50 chars. Required only when product group is Garment/Footwear.
unitCostnumberOptional; if provided must be > 0. Up to 15 digits before the decimal point and 4 after. Used for duty estimation.
currencyCodestringISO currency (e.g., GBP). Max 10 chars.
genderstringOptional (e.g., NA). Max 250 chars.
productGroupstringE.g., Food & Drink - Cheese. Max 500 chars.

product.movement (required for orders)

FieldTypeNotes
countryOfOriginCodestringISO 3166-1 alpha-2 (e.g., CN). Max 2 chars.
destinationCountryCodestringRequired. ISO 3166-1 alpha-2 (e.g., GB). Max 2 chars. Must differ from countryOfOriginCode.

orderDetails (optional)

FieldTypeNotes
incoTermCodestringOptional; e.g., FOB, DDP. Max 50 chars.
incoTermDescriptionstringOptional free text. Max 50 chars.
shipmentMethodstringOptional; e.g., Sea, Air. Max 1000 chars.
orderLineTypeCodestringOptional code for line type. Max 50 chars.
orderLineTypeDescriptionstringOptional description for line type. Max 100 chars.
orderQuantitynumber (integer)Optional; if provided must be > 0.

Sample Request

cURL
curl -X POST 'https://<your-api-host>/api/v2/system/purchase-orders/upload' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '[
        {
          "orderNumber": "ORD-12122",
          "supplierIdentifier": "THOC",
          "dueDateForClassification": "2025-11-05",
          "product": {
            "code": "PRD-ORD-12121",
            "description": "Grated Cheese",
            "productDetails": {
              "primarySize": "NA",
              "unitCost": 1.95,
              "currencyCode": "GBP",
              "gender": "NA",
              "productGroup": "Food & Drink - Cheese"
            },
            "movement": {
              "countryOfOriginCode": "CN",
              "destinationCountryCode": "GB"
            }
          },
          "orderDetails": {
            "incoTermCode": "",
            "incoTermDescription": "",
            "shipmentMethod": "",
            "orderLineTypeCode": "string",
            "orderLineTypeDescription": "string",
            "orderQuantity": 2
          }
        }
      ]'

Typical Responses

200OK - Batch accepted and processed; body contains the per-order result (Status, Data, Errors, Warnings).
400Bad Request - Malformed JSON, no orders supplied, or payload-shape validation errors (e.g., missing orderNumber, missing product.movement.destinationCountryCode, field-length violations). Error list returned in the body.
401/403Unauthorized / Forbidden - Invalid/missing token or insufficient scope (requires the SystemToSystem policy).
415Unsupported Media Type - Content-Type not application/json.
422Unprocessable Entity - Business validation errors (e.g., unknown supplier, country code that doesn't resolve to an active import region).
429Too Many Requests - Rate limit for the SystemToSystem policy exceeded.
500Internal Server Error - Unexpected error.

Products Upload

TTClassify takes a product list and classifies each item listed. The specification below allows this to happen automatically using a structured, nested request body.

POST/api/v2/system/products/upload

Rate limited to 30 requests per minute.

Request Body Example

JSON
[
  {
    "code": "PRD3312",
    "description": "Grated Cheese",
    "productDetails": {
      "primarySize": "N/A",
      "unitCost": 1.95,
      "currencyCode": "GBP",
      "gender": "N/A",
      "productGroup": "Food & Drink - Cheese"
    },
    "movement": {
      "countryOfOriginCode": "CN",
      "destinationCountryCode": "GB"
    }
  }
]

Request Body Example (minimal)

JSON
[
  {
    "code": "PRD3312",
    "description": "Grated Cheese"
  }
]

Field Reference

Product (top-level)

FieldTypeNotes
codestringRequired. Unique product/SKU code within the organization. Max 100 chars.
descriptionstringRequired. Human-readable description; read by TCAS to suggest classifications. Max 500 chars.
productDetailsobjectOptional (see below).
movementobjectOptional (see below).

productDetails (optional)

FieldTypeNotes
primarySizestringOptional (e.g., N/A). Max 50 chars. Required only when product group is Garment/Footwear.
unitCostnumberOptional; if provided must be > 0. Up to 15 digits before the decimal point and 4 after. Used for duty estimation.
currencyCodestringISO currency (e.g., GBP). Max 10 chars.
genderstringOptional (e.g., N/A). Max 250 chars.
productGroupstringCategory to aid classification (e.g., Food & Drink - Cheese). Max 500 chars.

movement (optional)

FieldTypeNotes
countryOfOriginCodestringISO 3166-1 alpha-2 (e.g., CN). Max 2 chars.
destinationCountryCodestringISO 3166-1 alpha-2 (e.g., GB). Max 2 chars.

Both-or-none rule: for a standalone product upload, movement may be omitted entirely, but if it is supplied then both countryOfOriginCode and destinationCountryCode must be provided (one alone is rejected), and the two must resolve to different countries.

Sample Request

cURL
curl -X POST 'https://<your-api-host>/api/v2/system/products/upload' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '[
        {
          "code": "PRD3312",
          "description": "Grated Cheese",
          "productDetails": {
            "primarySize": "N/A",
            "unitCost": 1.9500,
            "currencyCode": "GBP",
            "gender": "N/A",
            "productGroup": "Food & Drink - Cheese"
          },
          "movement": {
            "countryOfOriginCode": "CN",
            "destinationCountryCode": "GB"
          }
        }
      ]'

Typical Responses

200OK - Batch accepted and processed; body contains the per-product result (Status, Data, Errors, Warnings).
400Bad Request - Malformed JSON, no products supplied, or payload-shape validation errors (e.g., missing code/description, field-length violations, movement with only one country code).
401/403Unauthorized / Forbidden - Invalid/missing token or insufficient scope (requires the SystemToSystem policy).
415Unsupported Media Type - Content-Type not application/json.
422Unprocessable Entity - Business validation errors (e.g., a country code that doesn't resolve to an active import region).
503Service Unavailable - Rate limit exceeded (default rejection status for the SystemToSystem limiter).
500Internal Server Error - Unexpected error.

Get Product Classifications

Returns the current classification for a single product, identified by its code.

GET/api/v1/system/classifications/product?code=<code>

Rate limited to 30 requests per minute (shared SystemToSystem policy).

Query Parameters

ParameterTypeNotes
codestringRequired. The product/SKU code. Whitespace is trimmed; an empty/missing value returns 400.

Sample Request

cURL
curl -X GET 'https://<your-api-host>/api/v1/system/classifications/product?code=PRD3312' \
  -H 'Authorization: Bearer <access_token>'

Response Body Example (200 OK)

JSON
{
  "product": {
    "code": "PRD3312",
    "description": "Grated Cheese",
    "status": "Active",
    "primarySize": "N/A",
    "primaryTariffCodeUnitCost": 1.95,
    "secondaryTariffCodeUnitCost": null,
    "unitCostCodeCurrencyDescription": "GBP",
    "genderDescription": "N/A",
    "unitCount": 1
  },
  "classification": {
    "status": "Approved",
    "rejectionReason": null,
    "import": {
      "importRegionCode": "GB",
      "destinationCountryCode": "GB",
      "countryOfOriginCode": "CN"
    },
    "attributes": {
      "Type of Cheese": "Grated"
    },
    "customsDescription": "Grated cheese, prepacked",
    "isPrimaryClassification": true,
    "tariffCode": "0406200000",
    "vatRatePercent": 20,
    "dutyRate": "10.00 %",
    "preferentialDutyRate": null,
    "selectedDutyRate": "non-preferential",
    "dutyRateJustification": "Goods do not qualify as originating products...",
    "meursingCode": null,
    "validityStartDate": "2025-01-01T00:00:00Z",
    "dueDate": "2025-03-18T00:00:00Z",
    "createdAt": "2026-05-26T16:05:46Z",
    "createdBy": "Tiffany Rhodes",
    "lastUpdatedAt": "2026-05-26T16:06:12Z",
    "lastUpdatedBy": "Ben O'Neill",
    "submittedAt": "2026-05-26T16:05:46Z",
    "submittedBy": "Tiffany Rhodes",
    "audit": [
      {
        "action": "Status changed from Submitted to Approved",
        "info": null,
        "at": "2026-05-26T16:06:12Z",
        "by": "Ben O'Neill"
      }
    ]
  }
}

Field Reference

product

FieldTypeNotes
codestringProduct/SKU code.
descriptionstringHuman-readable description.
statusstringProduct status (e.g., Active).
primarySizestring | nullOptional.
primaryTariffCodeUnitCostnumber | nullPrimary unit cost used for duty estimation.
secondaryTariffCodeUnitCostnumber | nullSecondary unit cost, if applicable.
unitCostCodeCurrencyDescriptionstring | nullISO currency (e.g., GBP).
genderDescriptionstring | nullOptional.
unitCountinteger | nullNumber of units.

classification

FieldTypeNotes
statusstringClassification status (e.g., Pending, Submitted, Approved, Rejected).
rejectionReasonobject | nullPresent when rejected; { reason, notes }.
importobjectMovement details (see below).
attributesobject (map)Key/value classification attributes.
customsDescriptionstring | nullCustoms description of the goods.
isPrimaryClassificationbooleanWhether this is the primary classification.
tariffCodestring | nullAssigned commodity/tariff code.
vatRatePercentnumber | nullVAT rate as a percentage.
dutyRatestring | nullStandard (non-preferential) duty rate.
preferentialDutyRatestring | nullPreferential duty rate, if any.
selectedDutyRatestring | nullThe duty rate type selected.
dutyRateJustificationstring | nullJustification for the selected duty rate.
meursingCodestring | nullMeursing code, if applicable.
validityStartDatedate-time | nullClassification validity start.
dueDatedate-time | nullClassification due date.
createdAt / createdBydate-time / stringCreation audit.
lastUpdatedAt / lastUpdatedBydate-time / stringLast-update audit.
submittedAt / submittedBydate-time | null / string | nullSubmission audit.
auditarrayAudit events; each { action, info, at, by }.

classification.import

FieldTypeNotes
importRegionCodestringImport region code.
destinationCountryCodestring | nullISO 3166-1 alpha-2 destination.
countryOfOriginCodestringISO 3166-1 alpha-2 origin.

Typical Responses

StatusMeaning
200 OKClassification found; body as above.
400 Bad Requestcode missing or blank. Body: { "error": "Product code is required." }
401 / 403Invalid/missing token or insufficient scope.
404 Not FoundNo product/classification matches the supplied code. Body: { "error": "<description>" }
503 Service UnavailableRate limit exceeded.
500 Internal Server ErrorUnexpected error. Body: { "error": "<description>" }

Get Order Classifications

Returns the classification state of every line on an order, including a summary and per-item audit trail.

GET/api/v1/system/order-classifications?orderNumber=<orderNumber>

Rate limited to 30 requests per minute (shared SystemToSystem policy).

Query Parameters

ParameterTypeNotes
orderNumberstringRequired. The order number. An empty/missing value returns 400.

Sample Request

cURL
curl -X GET 'https://<your-api-host>/api/v1/system/order-classifications?orderNumber=1643761' \
  -H 'Authorization: Bearer <access_token>'

Response Body Example (200 OK)

JSON
{
  "orderNumber": "1643761",
  "classificationSummary": {
    "totalItems": 1,
    "classifiedItems": 1,
    "pendingItems": 0
  },
  "orderLines": [
    {
      "destination": {
        "code": "GB",
        "description": "United Kingdom",
        "importRegion": {
          "code": "GB",
          "description": "United Kingdom"
        }
      },
      "supplier": {
        "identifier": "CT002",
        "supplierName": "ChefTech Equipment Co"
      },
      "classificationStatus": "Approved",
      "product": {
        "code": "945315842",
        "description": "Broad beans 650g",
        "type": "Single",
        "items": [
          {
            "origin": {
              "code": "IN",
              "description": "India"
            },
            "code": "945315842",
            "description": "Broad beans 650g",
            "classification": {
              "primary": false,
              "dueDate": "2025-03-18T00:00:00Z",
              "tariffCode": "0708900010",
              "status": "Approved",
              "auditTrail": [
                {
                  "oldStatus": "Pending",
                  "newStatus": "Submitted",
                  "timestamp": "2026-05-26T16:05:46.6067173Z",
                  "changedBy": {
                    "firstName": "Tiffany",
                    "lastName": "Rhodes",
                    "email": "[email protected]",
                    "organizationName": "ChefTech Equipment Co"
                  },
                  "notes": "System: Status changed from Pending to Submitted",
                  "submission": {
                    "productGroup": "Food & Drink - Vegetables - Not Prepared - Fresh or Chilled",
                    "attributes": [
                      {
                        "name": "Type of Vegetable",
                        "value": "Beans - Broad (Vicia Faba major L.)"
                      }
                    ],
                    "tariffCode": "0708900010"
                  },
                  "rejection": null
                }
              ],
              "customsDescription": "Beans - Broad (Vicia Faba major L.)",
              "customsCompliance": {
                "dutyRate": "10.00 %",
                "vatRate": null,
                "preferentialDutyRate": null,
                "selectedDutyRateType": "non-preferential",
                "preferentialJustification": "I declare that the goods... non-preferential tariff rates under customs regulations.",
                "meursingCode": null
              }
            }
          }
        ]
      }
    }
  ]
}

Field Reference

Top level

FieldTypeNotes
orderNumberstringThe order number echoed back.
classificationSummaryobjectCounts (see below).
orderLinesarrayOne entry per order line (see below).

classificationSummary

FieldTypeNotes
totalItemsintegerTotal items on the order.
classifiedItemsintegerItems that have been classified.
pendingItemsintegerItems still pending classification.

orderLines[]

FieldTypeNotes
destinationobject | null{ code, description, importRegion: { code, description } }.
supplierobject{ identifier, supplierName }.
classificationStatusstringLine-level status.
productobjectThe ordered product (see below).

orderLines[].product

FieldTypeNotes
codestringProduct code.
descriptionstringProduct description.
typestring | nullProduct type (e.g., Single).
itemsarrayPer-origin product items (see below).

orderLines[].product.items[]

FieldTypeNotes
originobject{ code, description } — country of origin.
codestringItem code.
descriptionstringItem description.
classificationobject | nullClassification detail (see below).

items[].classification

FieldTypeNotes
primarybooleanWhether this is the primary classification.
dueDatedate-time | nullClassification due date.
tariffCodestring | nullAssigned tariff code.
statusstringClassification status.
auditTrailarrayStatus-change history (see below).
customsDescriptionstringCustoms description.
customsComplianceobject | null{ dutyRate, vatRate, preferentialDutyRate, selectedDutyRateType, preferentialJustification, meursingCode }.

classification.auditTrail[]

FieldTypeNotes
oldStatus / newStatusstring | nullStatus transition (null for non-status events).
timestampdate-timeWhen the event occurred.
changedByobject | null{ firstName, lastName, email, organizationName }.
notesstring | nullFree-text note.
submissionobject | nullPresent on submission events: { productGroup, attributes: [{ name, value }], tariffCode }.
rejectionobject | nullPresent on rejection events: { reason, notes }.

Typical Responses

StatusMeaning
200 OKOrder found; body as above.
400 Bad RequestorderNumber missing or blank. Body: { "error": "orderNumber is required." }
401 / 403Invalid/missing token or insufficient scope.
404 Not FoundNo order matches the supplied orderNumber. Body: { "error": "<description>" }
503 Service UnavailableRate limit exceeded.
500 Internal Server ErrorUnexpected error. Body: { "error": "<description>" }

Ready to integrate?

Speak to our development team about your project and we'll get you set up with credentials and onboarding support.