Classification
Classification proposes commodity codes for your goods. You hold the goods you want classified as items, optionally attach images of them, and then run a classification for a jurisdiction. A run is asynchronous: you start it and poll for its outcome.
Classification is licensed separately from the rest of CAS, so it has its own scopes. You can use CAS and classification together or either one on its own. Contact the Customs4trade Customer Success team to have classification enabled for your tenant.
Items
An item is the thing you want classified. It carries your own identifier, a description of the goods, an optional country of origin, any images you attach, and the codes assigned to it.
Two identifiers are involved, and it is worth keeping them apart:
| Identifier | What it is |
|---|---|
externalReference |
Your own identifier for the item, unique within your tenant. You choose it, and you can search on it |
id |
The technical ID CAS assigns to the item. This is the ID every item endpoint takes |
Add an item
Use the Add a classification item endpoint:
POST https://api-<env>.customs4trade.com/v1/classifications/items
{
"externalReference": "SKU-12345",
"description": "100% cotton knitted t-shirt, men's, crew neck",
"origin": "IN",
"images": [
{ "url": "https://your-cdn.example.com/skus/12345/front.jpg" },
{ "contentType": "image/jpeg", "contentBase64": "/9j/4AAQSkZJRg...", "fileName": "front.jpg" }
],
"assignedClassifications": [
{ "jurisdiction": "EU", "code": "6109100010", "comment": "Confirmed by trade compliance" },
{ "jurisdiction": "UK", "code": "6109100000" }
]
}
The response is 201 Created with the stored item, including the technical id you use from then on.
The more precise your description, the better the proposed codes. assignedClassifications lets you load an item that is already classified: each entry seeds the confirmed code for one jurisdiction, at most one entry per jurisdiction. It is not a classification run, so it starts no engine work and consumes no credits.
Scope: create:classifications
externalReference must be unique within your tenant. Adding a second item with a reference that is already in use returns 409 Conflict.
Search items
Use the Search classification items endpoint:
GET https://api-<env>.customs4trade.com/v1/classifications/items/search?offset=0&limit=50&externalReference=SKU-123*
| Parameter | Description |
|---|---|
| offset | Number of items to skip. Defaults to 0 |
| limit | Number of items to return. Defaults to 50 and is capped at 200 |
| externalReference | Matches your own item identifier. * acts as a wildcard, so SKU-123* matches on the start, *123 on the end and *123* anywhere. Without a wildcard the value must match exactly |
| creationDateFrom, creationDateTo | Inclusive range on when the item was created |
| lastUpdatedFrom, lastUpdatedTo | Inclusive range on when the item was last changed |
| hasBetterSuggestion | Only returns items where a proposed code scores better than the assigned one |
No total count is returned, for performance. Page forward using links.next until it is empty:
{
"data": [
{
"id": "0f1c2d3e-4a5b-6c7d-8e9f-a0b1c2d3e4f5",
"externalReference": "SKU-12345",
"description": "100% cotton knitted t-shirt, men's, crew neck",
"origin": "IN"
}
],
"links": {
"self": "/v1/classifications/items/search?offset=0&limit=50",
"prev": null,
"next": "/v1/classifications/items/search?offset=50&limit=50"
}
}
Scope: read:classifications
Retrieve, update and delete an item
Use the Get classification item endpoint to read one item back with its images and assigned codes:
GET https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}
Scope: read:classifications
Use the Update classification item endpoint to change an item's mutable fields:
PATCH https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}
This applies JSON Merge Patch semantics, so send only what you want to change:
- a field you leave out of the body is left unchanged;
- a field with a value overrides it;
- a field set to
nullclears it.
{
"description": "100% cotton knitted t-shirt, men's, crew neck, updated spec",
"origin": null
}
The example changes the description and clears the origin. externalReference is untouched because it was left out. An empty body ({}) is valid and changes nothing. Images are managed through the image endpoints and assigned codes come from classification runs, so neither can be changed here.
Scope: create:classifications
externalReference can be renamed but not cleared. Sending it as null or blank returns 400 Bad Request, and renaming it to a reference another item already uses returns 409 Conflict.
Use the Delete classification item endpoint to delete an item together with its images and its classification runs:
DELETE https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}
The response is 204 No Content. Deleting an item that is already gone returns 404 Not Found.
Scope: delete:classifications
Images
Images improve the proposed codes considerably. CAS stores its own copy of every image and gives it an imageId. A url you supply is only used to fetch the bytes once and is not retained, so item responses carry image IDs rather than bytes or URLs.
There are three ways to attach an image:
| Method | When to use it |
|---|---|
url in the item body |
CAS fetches the bytes for you. fileName defaults to the last segment of the url |
contentBase64 in the item body |
Convenient for small images sent along with the item |
| Raw bytes to the image endpoint | Best for larger files, as base64 inflates the payload by roughly a third |
Add an image as raw bytes
Use the Add classification item image endpoint:
POST https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}/images
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="front.jpg"
<raw image bytes>
Content-Disposition is optional and only used to keep the original file name. The response is 201 Created:
{
"imageId": "img-5e6f7a8b",
"itemId": "0f1c2d3e-4a5b-6c7d-8e9f-a0b1c2d3e4f5",
"fileName": "front.jpg",
"contentType": "image/jpeg"
}
The image is added to the item straight away, so your next classification run picks it up without any further action.
Scope: create:classifications
Image constraints
These apply to inline base64 and to raw bytes alike.
| Constraint | Value |
|---|---|
| Accepted content types | image/jpeg, image/png, image/webp, image/gif, image/bmp and application/pdf |
| PDF handling | Every page is rendered to an image and classified. Its images are named name (page N) |
| Maximum images per item request | 50 |
| Recommended size per image | 10 MB or less. Larger images bring no gain in accuracy and slow processing down |
Content that is not accepted, or that cannot be decoded, returns 400 Bad Request. A request that is too large returns 413 Payload Too Large.
Retrieve and remove an image
Use the Get classification item image endpoint to read the raw bytes of a stored image, served under the content type it was stored as:
GET https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}/images/{imageId}
Scope: read:classifications
Use the Delete classification item image endpoint to remove a stored image:
DELETE https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}/images/{imageId}
The response is 204 No Content, or 404 Not Found if the image ID is not known for that item.
Scope: delete:classifications
Running a classification
Start a run
Use the Start a classification endpoint:
POST https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}/classifications
{
"jurisdiction": "EU",
"code": "6109100010"
}
jurisdiction is one of EU, UK, US or CH, and it must be enabled for your tenant. code is optional: supply it and the run verifies that code as well as proposing its own.
The response is 202 Accepted with the ID of the run:
{
"classificationId": "9ab2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"itemId": "0f1c2d3e-4a5b-6c7d-8e9f-a0b1c2d3e4f5",
"status": "Pending"
}
A run consumes one credit. When your credits are depleted the request returns 402 Payment Required and no run is started.
Scope: create:classifications
202 Accepted means the run was queued, not that it succeeded. Poll the run to get its outcome.
Poll for the result
Use the Get classification endpoint until status reaches Completed or Error:
GET https://api-<env>.customs4trade.com/v1/classifications/items/{itemId}/classifications/{classificationId}
| Status | Meaning |
|---|---|
Pending |
Queued, not started yet |
Processing |
Being classified |
Completed |
Finished, result is populated |
Error |
Failed, errorCode and message explain why |
A completed run returns every candidate code it considered, the highest scoring first:
{
"classificationId": "9ab2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"itemId": "0f1c2d3e-4a5b-6c7d-8e9f-a0b1c2d3e4f5",
"status": "Completed",
"jurisdiction": "EU",
"result": {
"notes": "General classification notes not tied to a specific option",
"options": [
{
"code": "6109100010",
"score": 9.2,
"explanation": "Knitted cotton t-shirts fall under heading 6109"
},
{
"code": "6105100010",
"score": 4.1,
"explanation": "Considered but the garment is a t-shirt, not a shirt"
}
],
"rulingReferences": [
{
"commodityCode": "6109100010",
"url": "https://example.org/rulings/12345",
"text": "T-shirts, singlets and other vests, knitted or crocheted, of cotton"
}
]
}
}
score is a confidence score from 0 to 10. A failed run reports the reason instead:
{
"classificationId": "9ab2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"status": "Error",
"errorCode": "classification_failed",
"message": "No classification found"
}
Scope: read:classifications
Duty rates and measures are deliberately not part of the result.
Scopes
Classification uses its own scopes, so it can be enabled independently of the rest of CAS:
| Scope | Grants |
|---|---|
read:classifications |
Searching and retrieving items, images and classification runs |
create:classifications |
Adding and updating items, adding images, and starting classification runs |
delete:classifications |
Deleting items and removing images |
See Authentication for how to obtain a token.
Error handling
The classification endpoints return validation failures as RFC 9457 application/problem+json. Where more than one thing is wrong, every problem is reported at once so you can fix a whole batch in one round trip:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"images": [
"images[1]: invalid base64 content."
],
"assignedClassifications": [
"jurisdiction 'XX' is not a known jurisdiction."
]
}
}
This differs from the RFC 7807 invalid-parameters shape most other CAS endpoints use. See Error handling.
For HTTP status codes and error response formats, see Error handling.