# Managed and Premium Tariff Code Classification — API Reference

Managed and Premium Tariff Code Classification provide expert-led and hybrid classification services to support accurate tariff code assignment for products. These solutions are designed for businesses that require high confidence in classification, especially for complex or compliance-sensitive scenarios.

Source: https://developer.avalara.com/products/managed-and-premium-tariff-code/api/
**Total endpoints:** 15 across 2 APIs

---

## Avalara Item Classification API

If you're subscribed to the Avalara Item Classification service, these APIs 
 allow you to submit products for Item Classification and retrieve the 
 corresponding HS Codes.

 **Notes**:
 - Avalara Item Classification is a billable service. If the service is not 
 enabled, the API will not provide a response.
 - In the development environment (Sandbox), the service will not 
 return HS Codes. Instead, requests will remain in a pending status by default.
 Use the `hs_code_test` parameter in your Sandbox/integration environment to pass 
 an HS Code directly. In this case the product will not be reviewed by any operator,
 and a response will be sent back in a matter of minutes.

 There are two subscription options for the Item Classification API:
 - **Item Classification With Storage**: Your item metadata is automatically 
 stored as an [Avalara
 Item](https://developer.avalara.com/api-reference/avatax/rest/v2/models/ItemModel/) 
 object. This enables easy referencing of your items and metadata throughout
 Avalara using your own SKU.
 - **Item Classification Without Storage**: You have the flexibility to store 
 your classified items in a manner that best suits your needs. If you opt 
 for this, you can optionally set up a webhook to receive your classifications.

 **Unify endpoint**:
 The `/api/v3/companies/{companyId}/classifications` resource (POST/GET) is the
 unify, explicit-intent contract. Callers declare what they want via
 `classificationType` (`managed`, `autoPro`, `autoEssentials`), `classifications`,
 and `classificationModifiers`, instead of relying on the implicit Auto HS
 branching of the older `/classifications/hs` endpoints. It uses Bearer (OAuth 2.0)
 authentication and the AvaTax error envelope. See
 `documentation/design/unified endpoint/` for background.

**Version:** 16.0.1-oas3
**Base URL:** https://api-sandbox.classification.avalara.net
**Endpoints:** 11

### Endpoints

#### Classifications

##### Submit a unified classification request

`POST /api/v3/companies/{companyId}/classifications`

Accepts a **JSON** body (`application/json`) in three shapes:
1. Single object with `item`.
2. Single object with `items[]` sharing classification config.
3. Top-level JSON array of independent requests.

**Document upload (`multipart/form-data`).** The same URL also accepts a document upload:
submit a PDF and/or image(s) and the service reads them into classification object(s) before
classifying (it does not require a structured `item`). Routing is by `Content-Type`
(`application/json` → JSON shapes above; `multipart/form-data` → document upload). Uploaded
files are validated (type, size, page count, readability, encryption, image quality) and one
classification result is returned per distinct product extracted from the document(s).
Extraction that yields no products returns `422` (`EXTRACTION_EMPTY`); if the extraction
service is unreachable the response is `503`, and an extraction error is `502`.

**Success responses** (`201 Created`, `207 Multi-Status`) are always `{ "items": [...] }`.

**HTTP-level errors** (`403`, `413`, `422`, `429`, `503`, `500`, and `401` from the gateway) return
the **AvaTax error envelope** (`AvaTaxError`) — `{ "error": { "code", "target", "details"[] } }` —
the same structural shape used for OAuth/gateway failures. Only **nested** rejections inside
a `207` item / per-COD result still use the small `{ "code", "message" }` object (`Error`).

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |

##### Retrieve a single classification by ID

`GET /api/v3/companies/{companyId}/classifications/{classificationId}`

Returns the unify view of a single classification by its composite
ID. The ID is the value previously returned in
`items[].classifications[].id` from `POST /classifications`
(e.g. `2-76713-1651708-CHEM-SAMPLE-008-HS_FULL-US`).

Internally the controller delegates to `UnifyGetService`, which
mirrors the legacy V1 GET pipeline: token check, SKU mapping
lookup, cross-customer guard, customer access checks, then
`ClassificationService.getClassification(...)`. The legacy
result is then reshaped into `ClassificationGetResponse` with
the unify field-name flips reversed automatically.

## Error envelope by HTTP status

| Status | Envelope        | Trigger                                                                                  |
|--------|-----------------|------------------------------------------------------------------------------------------|
| 200    | `ClassificationGetResponse` | Classification found.                                                        |
| 401    | `AvaTaxError`   | Gateway authentication failure (rejected before the controller ran).                     |
| 404    | `AvaTaxError`   | `SkuMappingNotFound` OR underlying classification null OR downstream `AvaTaxException`.  |
| 422    | `AvaTaxError`   | `ErrorCodeException` (auth / company-id / customer access). Same envelope as V1 GET.     |
| 500    | `AvaTaxError`   | Unhandled exception — same envelope as `POST .../classifications` (`UnhandledException`).  |

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `undefined` |  |  | No |  |
| `classificationId` | string | path | Yes | Composite classification ID returned by the POST endpoint
(e.g. `2-76713-1651708-CHEM-SAMPLE-008-HS_FULL-US`). Treat
it as opaque on the client - the server resolves it through
the SKU mapping store, not by parsing segments. |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |

##### Retrieve the audit trail for a single classification (AIML-26393)

`GET /api/v3/companies/{companyId}/classifications/{classificationId}/audit-trail`

Returns decision metadata, product data, "why this code" reasoning (justification /
compliance / premium rationale), the initial system recommendation and any
agent/coworker suggestions, and full version history for a single classification,
identified by the same composite ID as `GET /classifications/{classificationId}`.

Built entirely from `axb_dw_reporting.product`, `axb_dw_reporting.product_history`, and
`xb_customer.customer` - deliberately **no** Bitnami/Akeneo fallback.
`pim_versioning_version` rows are deleted whenever a product is purged from Bitnami,
which would silently lose history for exactly the items an audit trail most needs to
survive for; this endpoint never depends on that table. See
`documentation/design/audit_trails/Audit-Trail-AIML-26393.md` §12 for the full design.

Auth uses the same token/IAM-DS/SKU-mapping/customer-cross-check pipeline as
`GET /classifications/{classificationId}` (a `classificationId` is a SKU, so the same
token-vs-SKU-customer checks apply), but stops before that endpoint's
Bitnami/Akeneo/DW/parking-lot classification lookup - so a classification record purged
from Bitnami still returns 200 here instead of 404.

`versionHistory[]` only includes an old/new column pair (e.g. `hsCode`/`hsCodeOld`) when
it actually changed in that row, and drops whole rows with no tracked change at all
(except the earliest row, always kept as the baseline) - `product_history` logs a row on
every save regardless of whether anything moved, so most raw rows/pairs are noise.
`version` numbers can therefore skip values in the response; that is expected, not
missing data.

`decision.classifier` is omitted entirely until the item has actually been classified;
`decision.auditor` is omitted entirely until the item has actually been audited;
`premiumRationale` is omitted entirely unless `premium` is truthy. All three are absent
keys, not `null`-valued objects.

## Error envelope by HTTP status

| Status | Envelope        | Trigger                                                                                  |
|--------|-----------------|-------------------------------------------------------------------------------------------|
| 200    | `AuditTrailResponse` | Classification found (a `product` row exists in DW for this SKU).                    |
| 401    | `AvaTaxError`   | Gateway authentication failure (rejected before the controller ran).                     |
| 404    | `AvaTaxError`   | `SkuMappingNotFound`, OR no DW `product` row exists for this SKU.                        |
| 422    | `AvaTaxError`   | `ErrorCodeException` (auth / company-id / customer access). Same envelope as V1/V3 GET.  |
| 500    | `AvaTaxError`   | Unhandled exception - same envelope as `GET /classifications/{classificationId}`.        |

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `undefined` |  |  | No |  |
| `classificationId` | string | path | Yes | Composite classification ID returned by the POST endpoint
(e.g. `2-76713-1651708-CHEM-SAMPLE-008-HS_FULL-US`). Treat
it as opaque on the client - the server resolves it through
the SKU mapping store, not by parsing segments. |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |


#### CreateHSClassificationRequest

##### Create an HS classification request (auto-classifies synchronously for Auto HS / Auto HS Pro / Auto HS ICP customers; otherwise queues for manual classification).

`POST /api/v2/companies/{companyId}/classifications/hs`

Submit a product for HS classification for one or more countries of
destination. Each product (item) must be uniquely identified by itemCode
for a particular company. It is recommended to use your internal SKU
number as itemCode. Item classifications belong to the company you
designate.

## Dual-mode behavior

The same endpoint serves both auto-classification and manual
classification customers. The mode is chosen server-side based on
customer entitlement and the `async` query parameter:

- **Auto classification path** (synchronous prediction). Selected when:
  - The customer is entitled to **Auto HS**, **Auto HS Pro**, or **Auto HS ICP**, **and**
  - `async` is **not** `true` (i.e. omitted or `false`).

  The request is internally routed to the ML-based auto-classify flow
  (previously exposed as `/classifications/hscodes/auto`, now deprecated
  and removed from the public surface). The classification result is
  returned **synchronously** in the response body — there is no need to
  poll the GetHSClassification endpoint afterwards.

- **Manual classification path** (asynchronous parking-lot creation).
  Selected when:
  - The customer is **not** entitled to Auto HS / Auto HS Pro / Auto HS ICP, **or**
  - `async=true` is supplied (force manual flow even for entitled customers).

  The item is queued for manual classification. The response carries a
  `PENDING` status and the classification result can be retrieved later
  via `GET /classifications/hs/{hsClassificationId}`.

If the auto-classify path is selected but fails before invoking the
prediction service (e.g. authentication or payload conversion error),
the request automatically falls back to the manual classification path
so the call still succeeds.

## Request body

Both modes accept the **same** request body shape
(`HSClassificationModel` / `HSClassificationWithComplianceModel`).
Existing integrations do not need to change their payload to benefit
from auto-classification: simply enable Auto HS on the customer.

## Limits

- **Manual path:** maximum payload size of approximately 60KB. Exceeding
  this returns **413 Payload Too Large**. Split bulk-country requests if
  you hit the limit.
- **Auto path:** combined count (items × countries of destination) must
  not exceed the configured per-request limit (e.g. 10). Exceeding it
  returns **413 Payload Too Large**.

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `companyId` | integer | path | Yes | The ID of the company that owns this item. This is your integer company ID, not the string company code. |
| `customerId` | string | query | No | The unique identifier of the customer (for multi-tenant scenarios). When omitted, the customer is resolved from the authentication token. |
| `async` | boolean | query | No | Forces the **manual classification path** (parking-lot creation) even when the customer is entitled to Auto HS / Auto HS Pro / Auto HS ICP. When omitted or `false`, entitled customers are auto-classified synchronously. When `true`, the item is always queued for manual classification and the response status will be `PENDING`. |
| `Context` | string | header | No | Optional contextual metadata propagated to downstream services. |


#### GetHSClassification

##### Returns the HS classification code. The status field reflects the actual state of the classification request.

`GET /api/v2/companies/{companyId}/classifications/hs/{hsClassificationId}`

Returns the HS classification code. The status field reflects the actual state of the classification request.

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `companyId` | integer | path | Yes | The ID of the company that owns this item. This is your integer company ID, not the string company code. |
| `hsClassificationId` | integer | path | Yes | The hsClassificationId used to identify a request was returned in the response of the CreateHSClassificationRequest method. |


#### HSCodeValidationRequest

##### Validate HS Codes

`POST /api/v2/companies/{companyId}/classifications/hscodes/validate`

Validates a list of HS codes for a given customer and company.
There is a limit to the number of HS codes that can be validated in a single request.
Requests exceeding this limit will be rejected with a 413 Payload Too Large error.

The customer must be enabled for HS code validation service.

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `companyId` | string | path | Yes | The unique identifier of the company. |
| `customerId` | string | query | Yes | The unique identifier of the customer. |
| `HEADER_CONTEXT` | string | header | No | Optional header context. |


#### HSCodeVerificationRequest

##### Verify HS Code for Product

`POST /api/v2/companies/{companyId}/classifications/hscodes/verify`

Verifies whether a provided HS code is appropriate for a given product using ML-based classification.

**Behavior:**
- If the HS code is verified as appropriate, returns `verified: true`
- If the HS code is NOT verified, automatically submits the product for classification and returns the classification response

**Requirements:**
- The customer must be enabled for HS code validation service
- HS code must be 6-12 digits, optionally followed by up to 2 letters (e.g., 7323997000, 7323997000AB)
- All standard classification fields are required (companyId, itemCode, description, itemGroup, countryOfDestination)

**Performance:**
- Typical response time: 1-2 seconds
- Timeout: 5 seconds
- Circuit breaker protection for service resilience

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `companyId` | string | path | Yes | The unique identifier of the company. |
| `customerId` | string | query | No | The unique identifier of the customer (for multi-tenant scenarios). |
| `Context` | string | header | No | Optional header context. |


#### WebhookCallback

##### Webhook Callback Endpoint

`POST /webhook/callback`

This is the endpoint that clients need to implement to receive webhook callbacks when classifications are completed.
Only clients who have specifically opted to receive webhook callbacks need to implement this endpoint.

**Authentication:**
The webhook callback supports two authentication methods:
- Basic Authentication: Avalara will send an Authorization header with "Basic {token}"
- OAuth: Avalara will send an Authorization header with "Bearer {token}"

**Response Requirements:**
- The endpoint should return HTTP 200 or 201 on success
- The recommended response format is: {"name":"API_RESPONSE","value":"SUCCESSFUL"}
- For errors, return HTTP 400 with: {"name":"API_RESPONSE","value":"ERROR"}

**Retry Behavior:**
If the webhook call fails due to server connectivity issues, the system will automatically retry the call.
Clients should ensure their webhook endpoint is reliable and responds quickly to avoid retry delays.


#### NomenclatureChangeWebhook

##### Receive content-level nomenclature change webhook notifications.

`POST /nomenclature/webhook/content-changes`

This endpoint receives general nomenclature updates, for example, when a description is updated or an HS code is retired, that match configured HS-mask filters. The payload is identified by the top-level notificationType: NOMENCLATURE_CHANGES and uses a nested structure grouped by HS root and country. Return an HTTP 2xx response to acknowledge receipt. Avalara does not retry 4xx responses, but retries 5xx responses and timeouts.

##### Receive item-level tariff and invalid HS code change webhook notifications.

`POST /nomenclature/webhook/item-changes`

This endpoint receives item-level changes for a catalog that match configured category or item-code filters. The payload uses a flat envelope with item identity carried inline on each change. Each change is either a TARIFF_CHANGE for a reclassification where the hsCode is the new code, or an INVALID_HSCODE for a retired or expired code. Return an HTTP 2xx response to acknowledge receipt. Avalara does not retry 4xx responses, but retries 5xx responses and timeouts.

##### Receive duty rate change webhook notifications.

`POST /nomenclature/webhook/duty-changes`

This endpoint receives duty rate changes. The payload uses a flat envelope, where each change is based on the HS code and rate, and does not include catalog items. It carries duty fields with the rate expressed as nested previous and current objects. Return an HTTP 2xx response to acknowledge receipt. Avalara does not retry 4xx responses, but retries 5xx responses and timeouts.

### Models

#### DocumentUploadRequest

`multipart/form-data` body for the document-upload variant of this endpoint. Submit a PDF
and/or image(s); the service reads them into classification object(s) and classifies them.
At least one of `pdf`, `images`, `text`, `imageUrl`, or `url` must be supplied. The
classification-intent fields (`classificationType`, `classifications`, `countryOfDestination`)
are applied to every product extracted from the document(s). Files are validated for type,
size (≤ 25 MB/file, ≤ 50 MB total, ≤ 10 files), page count (≤ 50), readability, encryption,
and image quality before extraction.

| Property | Type | Description |
|---|---|---|
| `pdf` | string | A single PDF document to classify. |
| `images` | array | One or more image files (PNG / JPG / JPEG). Repeat the field to send several. |
| `text` | string | Raw product text/description (alternative or supplement to a file). |
| `title` | string | Optional product title hint. |
| `imageUrl` | string | URL of a product image to read, instead of or in addition to an uploaded file. |
| `url` | string | URL of a product page to read. |
| `countryOfExport` | string | ISO 3166-1 alpha-2 country-of-export hint. Applied to every extracted
product, and **required** when `classifications` includes `eccn` — the
per-item rule (`COE_REQUIRED_FOR_ECCN`) is evaluated against the
extracted items, so an ECCN upload without it fails validation after
extraction. |
| `isExport` | boolean | When true, classify using export (Schedule B) codes. |
| `classificationType` | string | Classification tier applied to every extracted product. |
| `classifications` | string | Comma-separated classification activities applied to every extracted product — e.g.
`hsFull` or `hsFull,compliance`. Allowed: `hsFull`, `hs6`, `export`, `eccn`,
`compliance`, `coo`, `premium`. Including `eccn` additionally requires
`countryOfDestination` and `countryOfExport`. |
| `countryOfDestination` | string | Comma-separated ISO 3166-1 alpha-2 destination country codes applied to every extracted
product — e.g. `US` or `US,CA`. Required when `classifications` includes
`hsFull`, `hs6` or `eccn`. |

#### WebhookCallbackRequest

The webhook callback payload that will be sent to the client's webhook endpoint when a classification is completed.
Only clients who have specifically opted to receive webhook callbacks need to implement this endpoint.
This is an optional feature for "Item Classification Without Storage" customers.

**Authentication:**
The webhook callback supports two authentication methods:
- Basic Authentication: Requires an API key configured in the Avalara system
- OAuth: Requires an OAuth token endpoint to be configured in the Avalara system

**Conditional Fields:**
- The `hsCode` field is included for HS classification activities such as `hs6`, `hsFull`, and export HS flows
- The `eccnCode` and `countryOfExport` fields are included for ECCN classification activities when available
- The `countryOfOrigin` field is included for COO classification activities when available
- The `possibleHsCodes` field is only included when the `broker` parameter is set to "true" and there are alternative HS codes available

**Retry Behavior:**
If the webhook call fails due to server connectivity issues, the system will automatically retry the call.

| Property | Type | Description |
|---|---|---|
| `hsClassificationId` | string | The id of the HS classification request that was completed. |
| `companyId` | string | The ID of the company that owns this item. |
| `itemCode` | string | The unique item code (typically your SKU) that was classified. |
| `countryOfDestination` | string | The country code for which the classification was performed. |
| `countryOfExport` | string | The export country code for ECCN classification results, when available. |
| `countryOfOrigin` | string | The country of origin for COO classification results, when available. |
| `status` | string | The status of the classification request. |
| `hsCode` | string | The HS classification result for HS classification activities. |
| `eccnCode` | string | The export-control classification result for ECCN classification activities. |
| `rationale` | string | Explanation of the classification decision when available. |
| `possibleHsCodes` | string | Alternative HS codes that could apply to this item, when available.
This is provided as a stringified JSON array. |
| `compliances` | array | Trade compliance data for the classified item, when compliance enrichment was
enabled. Each entry follows the same shape as the `compliance` payload returned
on the classification response. |

#### ContentChangeNotification

Defines the content-level general nomenclature notification. The changes use a nested structure, grouped by HS root and country, following the path `changes[] -> changedCountries[] -> changes[]`.

| Property | Type | Description |
|---|---|---|
| `notificationId` | string | Provides the unique identifier for this notification. |
| `timestamp` | string | Provides the date and time the notification was sent, in ISO-8601 UTC format. |
| `customerId` | integer | Identifies the Avalara customer account associated with this notification. |
| `notificationType` | string | This value is always `NOMENCLATURE_CHANGES` for this payload. |
| `summary` | object | Provides a high-level summary of the changes included in this notification. |
| `changes` | array | Lists the nomenclature changes grouped by HS root. |

#### ContentChangeSummary

Provides a high-level summary of the content-level nomenclature changes included in the notification.

| Property | Type | Description |
|---|---|---|
| `totalRoots` | integer | Provides the total number of HS roots affected by the changes. |
| `totalChanges` | integer | Provides the total number of individual changes included in the notification. |
| `affectedChapters` | array | Lists the affected HS chapters, represented by the first 2 digits of each root. |
| `affectedCountries` | array | Lists the countries affected by the changes, in ISO 3166-1 alpha-2 format. |

#### RootChange

Groups content-level changes by HS root.

| Property | Type | Description |
|---|---|---|
| `root` | string | Provides the HS root, for example the heading, that the grouped changes belong to. |
| `changedCountries` | array | Lists the countries that have changes for this HS root. |

#### ChangedCountry

Groups content-level changes by country for a given HS root.

| Property | Type | Description |
|---|---|---|
| `country` | string | Identifies the country associated with the changes, in ISO 3166-1 alpha-2 format. |
| `changes` | array | Lists the content-level changes for this country. |

#### ContentChange

Describes a single content-level nomenclature change.

| Property | Type | Description |
|---|---|---|
| `hsCode` | string | Provides the HS code affected by the change. |
| `changeType` | string | Describes the nature of the content-level change. The content-level payload uses `changeType` with `INVALID_CODE` for a retired code, whereas the item-level payload uses `eventType` with `INVALID_HSCODE`. |
| `oldDescription` | string | Provides the description of the HS code before the change. This field is `null` when not applicable. |
| `newDescription` | string | Provides the description of the HS code after the change. This field is `null` when not applicable. |
| `effectiveFrom` | integer | Provides the effective-from date as an integer in YYYYMMDD format. |
| `effectiveTo` | integer | Provides the effective-to date as an integer in YYYYMMDD format. This field is `null` when the code does not have an expiry date. |

#### ItemChangeNotification

Defines the item-level notification. The payload uses a flat `changes[]` envelope where item identity is carried inline on each change. The `eventType` value is either `TARIFF_CHANGE` or `INVALID_HSCODE`.

| Property | Type | Description |
|---|---|---|
| `notificationId` | string | Provides the unique identifier for this notification. |
| `timestamp` | string | Provides the date and time the notification was sent, in ISO-8601 UTC format. |
| `detectedAt` | string | Provides the date and time when the underlying changes were detected, in ISO-8601 UTC format. This field is omitted when the detection time is unknown. |
| `customerId` | integer | Identifies the Avalara customer account associated with this notification. |
| `totalChanges` | integer | Provides the total number of item-level changes included in this notification. |
| `changesByType` | object | Provides the count of changes grouped by `eventType`. |
| `changes` | array | Lists the item-level changes included in this notification. |

#### ItemChange

Describes a single item-level change. For a `TARIFF_CHANGE` event, `hsCode` is the new code.

| Property | Type | Description |
|---|---|---|
| `COD` | string | Provides the country of destination in ISO 3166-1 alpha-2 format. |
| `hsCode` | string | Provides the HS code affected by the change. |
| `eventType` | string | Identifies the type of item-level change. The value is either `TARIFF_CHANGE` for a reclassification or `INVALID_HSCODE` for a retired or expired code. |
| `companyId` | integer | Identifies the AvaTax company that owns the affected item. |
| `itemId` | integer | Identifies the Avalara item ID. |
| `itemCode` | string | Provides the catalog item code or SKU. |
| `itemDescription` | string | Provides the description of the affected catalog item. |
| `itemGroup` | string | Provides the category or item group of the affected item. |

#### DutyChangeNotification

Defines the duty rate notification. The payload uses a flat `changes[]` envelope where each change is based on the HS code and rate and does not include catalog items. The rate is expressed as nested `previous` and `current` objects.

| Property | Type | Description |
|---|---|---|
| `notificationId` | string | Provides the unique identifier for this notification. |
| `timestamp` | string | Provides the date and time the notification was sent, in ISO-8601 UTC format. |
| `detectedAt` | string | Provides the date and time when the underlying changes were detected, in ISO-8601 UTC format. This field is omitted when the detection time is unknown. |
| `customerId` | integer | Identifies the Avalara customer account associated with this notification. |
| `totalChanges` | integer | Provides the total number of duty rate changes included in this notification. |
| `changesByType` | object | Provides the count of changes grouped by `eventType`. |
| `changes` | array | Lists the duty rate changes included in this notification. |

#### DutyChange

Describes a single duty rate change.

| Property | Type | Description |
|---|---|---|
| `hsCode` | string | Provides the HS code the duty applies to. |
| `eventType` | string | Identifies the type of duty change. The value is `DUTY_RATE_CHANGE`, `DUTY_ADDED`, `DUTY_REMOVED`, or `FTA_UPDATE`. |
| `tariffSystemCode` | string | Provides the tariff system the duty change belongs to, for example, EU. |
| `effectiveCountries` | array | Lists the countries this duty change applies to, resolved from the tariff system. This field replaces the former single duty `countryCode`. |
| `rateLabel` | string | Provides a human-readable label for the duty rate. |
| `tradeGroupName` | string | Provides the trade group or FTA name for FTA changes. |
| `previous` | object | Provides the duty rate before the change. This field is omitted for a `DUTY_ADDED` event. |
| `current` | object | Provides the duty rate after the change. This field is omitted for a `DUTY_REMOVED` event. |
| `effectiveFrom` | string | Provides the date and time when the duty change takes effect, in ISO-8601 format. |
| `effectiveTo` | string | Provides the date and time when the duty rate stops being effective, in ISO-8601 format. This field is `null` when the duty rate does not have an expiry date. |

#### DutyRateDetail

Describes 1 side, either the previous or current value, of a duty rate.

| Property | Type | Description |
|---|---|---|
| `rateValue` | number | Provides the numeric duty rate value. |
| `currencyIsoCode` | string | Provides the currency for specific or compound duties. This field is omitted for ad-valorem percent rates. |
| `unitOfCalculationCode` | string | Provides the unit the rate is calculated on, for example, PERCENT or a per-unit measure. |

#### HSClassificationModel

| Property | Type | Description |
|---|---|---|
| `id` | string | The id of the HS classification request. This id is created by CreateHSClassificationRequest and returned in the response. |
| `item` | ItemModel |  |
| `items` | array | Alternative to `item` for submitting **multiple products** in a single request. Provide either `item` (one product) or `items` (many) - not both. Each product is classified independently and the response is a **JSON array** (HTTP 207) with one element per item, in request order; each element is a classification result or a per-item error object. |
| `activity` | string | The required classification activity for the request. Accepted values are: HS_FULL : full HS code classification (country specific). This is the default activity if no value is specified in the request. HS6: 6 digits HS code classification |
| `countryOfDestination` | object |  |
| `hsCode` | string | This is the HS classification result. It’ll be returned in the body for GetHSClassification method. |
| `status` | string | This value is returned by get HS Classification method. It’ll be ignored if it’s provided in the request. |
| `resolution` | string | This value will be returned by getHSClassification method only if an item cannot be classified (status=CANNOT_BE_CLASSIFIED). It contains a detailed description of the issue. It’ll be ignored if it’s provided in the request. |
| `confidence` | string | If the information provided in the request is not enough, an accurately classification might not be possible. In this case, the assumption made for classification will be returned in this field. |
| `confidenceDescription` | string | This is a detailed description of the assumptions made in order to classify an item when information provided in the request is not enough (e.g. similar product URL, product number or anything which can be used as identifier behind the classification logic). |
| `possibleHsCodes` | string | Alternative HS codes that could apply to this item, when available. This field is only included when the broker parameter is set to "true" and there are alternative HS codes available. Provided as a stringified JSON array. |
| `productUpdate` | boolean | Indicates whether the product information was updated during classification. |
| `validCODs` | array | List of valid country codes that were successfully processed |
| `invalidCODs` | array | List of invalid country codes that could not be processed |

#### ItemModel

| Property | Type | Description |
|---|---|---|
| `id` | integer | A unique id generated by Avalara Classification System |
| `companyId` | integer | The unique ID number of the company that owns this item. |
| `itemCode` | string | A unique code representing this item. It’s recommended to use the SKU number. |
| `parentCode` | string | Use the  parentCode to group product variants. Variants are a group of similar items that only differ from one another by product details like size, color, material, pattern, age group or size . |
| `description` | string | A short description of the product. It’s also referred as product title or name. |
| `summary` | string | A long description of the product. |
| `itemGroup` | string | Product category breadcrumbs. This is the full path to the category where item is included. Categories should be separated by ` > `. |
| `createdDate` | string | A long description of the product. |
| `classificationParameters` | array | Classification parameters supplied on the request, plus any read-only parameters the
service adds to a response.

**`justification` (read-only, response only).** On a GET of a classification, this list
may contain a `justification` entry holding the natural-language explanation of why the
HS code was chosen (**HS4 Reasoning**, **HS6 Reasoning** and **Full HS Reasoning**
sections). It appears once a justification has been produced and stored for the product.
It is never accepted on a request here — on this API a justification is requested only
for customers entitled to Auto HS Pro, and there is no request parameter to ask for one.
The unified (V3) API exposes the same value as a first-class `justification` field on
both its create and get responses. |
| `parameters` | array |  |

#### ItemParameterModel

| Property | Type | Description |
|---|---|---|
| `parameters` | array | The system has predefined list of parameter names that are known and
processed accordingly (see enum).

Predefined parameters for a compliance enrichment
request include: compliance_flagging, country_of_export,
country_of_manufacture, manufacturer_name, compliance_start_date,
compliance_end_date. When `compliance_flagging` is set to `true`,
`country_of_export` is **required** (otherwise no compliance call
is made for that country of destination) and
`country_of_manufacture` + `manufacturer_name` are strongly
recommended for accurate duty, FTA, CVD/ADD and restrictions
lookups.

For a brokerage response, where classification provides an HS6 with
optional full HS codes for your brokers to select from requires the
broker parameter set to true. 

For a premium response, where classifications are returned with full
explanations of rationale require the
premium parameter set to true. 

The system also supports 
custom parameters. For custom parameters, the name is not predefined
and should be provided in the request. |

#### HSClassificationWithComplianceModel

#### Compliance

| Property | Type | Description |
|---|---|---|
| `id` | string | The unique ID number for the quote |
| `countryOfExport` | string | Two characters ISO 3166 code identifying the country |
| `countryOfImport` | string | Two characters ISO 3166 code identifying the country |
| `countryOfManufacture` | string | Two characters ISO 3166 code identifying the country |
| `hscode` | string | Country specific hs code value. |
| `manufacturerName` | string | Name of the Manufacturer Company. |
| `productName` | string | Name of the product. |
| `duty` | Duty |  |
| `hasCVD` | boolean | is given request parameter has CVD(Countervailing Duty) rates. |
| `cvds` | array |  |
| `hasADD` | boolean | is given request parameter has ADD (Anti-Dumping Duty) rates. |
| `adds` | array |  |
| `hasRestrictions` | boolean | is given request parameter have restrictions for countryOfImport. |
| `hasPGA` | boolean | is given request parameter PGA restrictions for countryOfImport. |
| `restrictions` | array |  |

#### Duty

| Property | Type | Description |
|---|---|---|
| `calculationBasis` | string | Basis for duty calculation (e.g., PRICE, QUANTITY, CIF) |
| `mfn` | Rate |  |
| `hasPunitiveRate` | boolean | Indicates if punitive rates apply |
| `punitiveRates` | array |  |
| `hasFTARate` | boolean | Indicates if Free Trade Agreement rates apply |
| `ftaRates` | array |  |

#### CvdAdd

| Property | Type | Description |
|---|---|---|
| `productName` | string | the name of the product that need to ship |
| `manufacturerName` | string | Name of the Manufacturer Company. |
| `rate` | Rate |  |

#### Rate

| Property | Type | Description |
|---|---|---|
| `rate` | string | Rate value |
| `effectiveRate` | string | Effective rate value |
| `uom` | string | Unit of measurement (e.g., percentage, kilogram, count) |
| `currency` | string | Currency of the rate, if applicable |

#### PunitiveRate

| Property | Type | Description |
|---|---|---|
| `rate` | Rate |  |
| `stackable` | boolean | Indicates whether this punitive rate should be applied **on top of** all other duty components (MFN, FTA). |
| `taxOnTax` | boolean | Indicates whether the punitive rate is **compounded**, i.e. calculated as: rate + surchargeRate + (rate * surchargeRate) |
| `appliesUnderDeminimis` | boolean | Indicates if the rate applies under de minimis thresholds |

#### FreeTradeAgreementRate

| Property | Type | Description |
|---|---|---|
| `ftaName` | string | The FTA Name |
| `ftaRate` | Rate |  |

#### RestrictionsModel

| Property | Type | Description |
|---|---|---|
| `type` | string | The unique ID number for the quote |
| `regulation` | string | The regulation for the quote |
| `complianceMessage` | string | The compliance for the quote |
| `condition` | string | The condition for the quote |
| `hsCode` | string | The hsCode for the quote |
| `governmentAgency` | string | The government agency for the quote |
| `pgaCode` | string | The pgaCode for the quote |
| `exportCode` | string | The exportCode for the quote |
| `programCode` | string | The programCode for the quote |
| `agencyCode` | string | The agencyCode for the quote |
| `flagNote` | string | The flagNote for the quote |
| `ruleTitle` | string | The ruleTitle for the quote |
| `ruleSummary` | string | The ruleSummary for the quote |
| `searchKeyword` | string | The searchKeyword for the quote |
| `legislationTitle` | string | The legislationTitle for the quote |
| `legislationSummary` | string | The legislationSummary for the quote |
| `textLink` | string | The textLink for the quote |
| `webLink` | string | The webLink for the quote |
| `topic` | string | The topic for the quote |

#### ErrorInfo

| Property | Type | Description |
|---|---|---|
| `code` | string | Name of the error. Refer to Error Codes section for a list of accepted values. |
| `message` | string | Short one-line message to summarize what went wrong |
| `target` | string | What object or service caused the error? (Unknown, HttpRequest, HttpRequestHeaders, IncorrectData) |
| `details` | array | Array of detailed error messages |

#### ErrorDetail

| Property | Type | Description |
|---|---|---|
| `code` | string | Name of the error. Refer to Error Codes section for a list of accepted values. |
| `number` | integer | Unique ID number referring to this error or message. |
| `message` | string | Concise summary of the message, suitable for display in the caption of an alert box. |
| `description` | string | A more detailed description of the problem referenced by this error message, suitable for display in the contents area of an alert box. |
| `helpLink` | string | URL to help for this message |
| `refersTo` | string | Item the message refers to, if applicable. This is used to indicate a missing or incorrect value. |
| `severity` | string | Severity of the message`:`  Success, Warning, Error and Exception. |

#### HsValidCodeRequest

| Property | Type | Description |
|---|---|---|
| `hsCode` | string |  |
| `country` | string |  |
| `version` | string |  |
| `isExport` | boolean |  |

#### HsValidCodeResponse

| Property | Type | Description |
|---|---|---|
| `hsCode` | string |  |
| `country` | string |  |
| `isValid` | boolean |  |
| `validatedOn` | string |  |
| `validUntil` | string |  |
| `updatedOn` | string |  |
| `message` | string |  |

#### ErrorResponse

| Property | Type | Description |
|---|---|---|
| `errorCode` | string |  |
| `message` | string |  |

#### HsCodeVerificationRequest

| Property | Type | Description |
|---|---|---|
| `hsCode` | string | The HS code to verify. Must be 6-12 digits, optionally followed by up to 2 letters. |
| `classification` | HSClassificationModel |  |

#### HsCodeVerificationResponse

| Property | Type | Description |
|---|---|---|
| `verified` | boolean | Whether the HS code was verified as appropriate for the product |
| `providedHsCode` | string | Echo of the HS code that was submitted for verification |
| `classification` | object |  |
| `message` | string | Human-readable message about the verification result |

#### ClassificationRequest

| Property | Type | Description |
|---|---|---|
| `classificationType` | string |  |
| `classifications` | array | **`eccn`** (export-control classification) is a standalone activity, not a
variant of `export`. It has two extra requirements and one routing quirk:

- `countryOfDestination` is **required** (reused as the licence-determination
  jurisdiction; there is no separate ECCN jurisdiction field). Omitting it
  returns `422` / `COD_REQUIRED`.
- `item.countryOfExport` is **required per item** — it selects the control list
  (US → CCL under EAR, EU → dual-use list), so without it there is no scheme to
  resolve a code in. Omitting it returns `422` / `COE_REQUIRED_FOR_ECCN`.
- ECCN has no auto-classifier. An ECCN-primary request is **re-routed to the
  managed pipeline for every `classificationType`**, including `autoPro` and
  `autoEssentials` — it is not rejected. The response echoes the caller's
  original `classificationType`.

`hsCodeTest` is ignored for `eccn` (a test HS code is meaningless for an
export-control result and must not put an `hsCode` back on it). |
| `classificationModifiers` | ClassificationModifiers |  |
| `countryOfDestination` | array | Capped at 10 per request (configurable via
unify.max.countries.of.destination). Exceeding returns 413.

Required when `classifications` includes `hsFull`, `hs6` or `eccn`
(`422` / `COD_REQUIRED`). For `eccn` it is the licence-determination
jurisdiction, not the country the code is resolved against — that is
`item.countryOfExport`. |
| `providedHsCodes` | object |  |
| `hsCodeTest` | object | **Sandbox / integration use only.** Pre-supplied HS code per
country of destination. When present, the server treats the
listed code as the final classification for that COD and
bypasses all classification logic (no Indix / HSAC call).
Strictly capped to 20 characters per value.

Independent of `classificationType`, every COD listed in
`hsCodeTest` is processed via the managed pipeline. When the
caller asked for an auto type and supplied `hsCodeTest` for
every entry in `countryOfDestination`, the entire request is
re-routed to managed. Partial coverage splits the request:
covered CODs run through managed, the rest through the
requested auto path.

Covered CODs surface in the response as
`status: "classified"`, `hsCode: ""`,
`confidence: "test"`. Uncovered CODs follow the regular auto
response shape. The response's `classificationType` echoes
the original caller value (re-routing is internal). |
| `complianceData` | ComplianceData |  |
| `item` | Item |  |
| `items` | array | Bulk shape. Capped at 10 per request (configurable via
unify.max.items.per.request). Exceeding returns 413. |

#### ClassificationModifiers

| Property | Type | Description |
|---|---|---|
| `broker` | boolean |  |
| `mtccPlus` | boolean |  |
| `reclassify` | boolean |  |
| `verify` | boolean |  |
| `autoPerformance` | string |  |
| `priority` | integer |  |
| `includeJustification` | boolean | Return a natural-language justification for each classification as
`items[].classifications[].justification`.

`autoPro` always requests a justification regardless of this flag; every other
classification type requests one only when this is explicitly `true`. Accepted on
`managed` too - a managed request can still be auto-classified downstream by the
workflow engine, which routes to ICP.

Note a synchronous `managed` response is `pending`, so no justification can appear on
it; the create response carries one only where the classification was produced
synchronously by a tier whose classifier can return it.

The justification is LLM-generated, so enabling it adds latency to every prediction.
It is persisted once produced, so it is also returned on a later `GET` of the same
classification - including for classifications completed asynchronously. |

#### ComplianceData

Pure pass-through map. Anything the caller puts here is forwarded
verbatim to the Quoting compliance service. The only locally
validated fields are `startDate` / `endDate` (yyyy-MM-dd, end >=
start). System-managed keys (`id`, `hscode`, `countryOfImport`,
`countryOfExport`, `countryOfManufacture`, `section232Parameters`)
are silently stripped before forwarding. Section 232 must be sent
via `item.section232[]`.

| Property | Type | Description |
|---|---|---|
| `startDate` | string |  |
| `endDate` | string |  |

#### Item

| Property | Type | Description |
|---|---|---|
| `companyId` | integer |  |
| `itemCode` | string |  |
| `parentCode` | string |  |
| `title` | string |  |
| `description` | string |  |
| `category` | string |  |
| `url` | string |  |
| `imageUrl` | string |  |
| `material` | string |  |
| `countryOfOrigin` | string |  |
| `countryOfExport` | string | ISO 3166-1 alpha-2. Required when `classifications` includes `eccn` —
it selects the export-control list the code is resolved against
(`422` / `COE_REQUIRED_FOR_ECCN`, reported per item index). |
| `hsHint` | string |  |
| `taxCode` | string |  |
| `job` | string |  |
| `measurements` | Measurements |  |
| `identifiers` | Identifiers |  |
| `manufacturer` | Manufacturer |  |
| `section232` | array |  |
| `attributes` | object |  |

#### Measurements

| Property | Type | Description |
|---|---|---|
| `price` | string | " " |
| `weight` | string | " " (lb|oz|kg|g) |
| `length` | string | " " (in|cm|m|mm) |
| `width` | string |  |
| `height` | string |  |

#### Identifiers

| Property | Type | Description |
|---|---|---|
| `gtin` | string |  |
| `upc` | string |  |
| `ean` | string |  |
| `mpn` | string |  |
| `asin` | string |  |
| `jan` | string |  |
| `isbn` | string |  |
| `cas` | string |  |
| `ebayEpid` | string |  |
| `ebayItemId` | string |  |

#### Manufacturer

| Property | Type | Description |
|---|---|---|
| `manufacturerName` | string |  |
| `countryOfManufacture` | string |  |

#### Section232Entry

| Property | Type | Description |
|---|---|---|
| `material` | string |  |
| `percentage` | number |  |
| `countryOfOrigin` | string |  |

#### ClassificationResponse

| Property | Type | Description |
|---|---|---|
| `items` | array |  |

#### ItemResult

| Property | Type | Description |
|---|---|---|
| `itemCode` | string |  |
| `companyId` | integer |  |
| `status` | string |  |
| `classificationType` | string |  |
| `error` | Error |  |
| `classifications` | array |  |

#### ClassificationResult

| Property | Type | Description |
|---|---|---|
| `id` | string |  |
| `activity` | string |  |
| `countryOfDestination` | string | Present for `hsFull` / `hs6`. |
| `countryOfExport` | string | Present for `export` and for `eccn`. An ECCN result is **keyed by the
exporting country** — `countryOfExport` selects the control list, so it
identifies the result the way `countryOfDestination` does for `hsFull`,
and the composite `id` carries it as the trailing country segment
(e.g. `2-1000-1651708-WRENCH-10MM-ECCN-DE`). Null when the request
carried no item. |
| `countryOfOrigin` | string | Present for `coo` — the resolved ISO-2 country of origin. Null on a POST
response (the classifier has not run; `item.countryOfOrigin` is only an
input hint); populated on GET once classified. |
| `enrichments` | array |  |
| `classificationModifiers` | ClassificationModifiers |  |
| `status` | string |  |
| `hsCode` | string | Never present for `eccn`. An export-control result must not carry an HS
code — the underlying record can hold one (mixed request, reclassify,
pre-existing value) and surfacing it here would present tariff data as
the ECCN answer. |
| `eccnCode` | string | Export-control code (e.g. `5A001.a`) for `eccn` results, in place of
`hsCode`.

**Null on a POST response.** ECCN is managed-only with no auto-classifier,
so no code exists at submission time — an ECCN POST result carries no code
in either field, the same as `coo`. Fetch the classification by `id` once
it has been worked; `GET .../classifications/{id}` fills this in from the
item's `eccn_code` parameter. |
| `grade` | string | Raw auto prediction grade returned for autoPro and autoEssentials HS results. |
| `confidence` | string | Present when the HS code came from the request's hsCodeTest override. |
| `verifyResult` | string |  |
| `providedHsCode` | string |  |
| `message` | string |  |
| `justification` | string | Natural-language justification for this per-COD classification, containing
**HS4 Reasoning**, **HS6 Reasoning** and **Full HS Reasoning** sections.

Present on the create (POST) response when the classification was produced
synchronously by a classifier that returns a justification — in practice the ICP-backed
auto tiers. `autoPro` is routed to the HS-hints API, which cannot produce one, so its
justification is generated later by the workflow engine instead.

Once produced the value is persisted, so it is also returned by
`GET /classifications/{classificationId}`. |
| `compliance` | array | Per-COD compliance payload returned by the Quoting service. Populated only
when `compliance` is in `classifications` *and* an HS code is available
for the COD (auto path: Indix prediction; managed path:
caller-supplied `providedHsCodes`). Mutually exclusive with
`complianceError`. Item shape mirrors
`com.avalara.xb.classification.views.Compliance` (Quoting response). |
| `complianceError` | object | Set when the unify compliance gate skipped the Quoting call because the item
is missing a field Quoting requires (today: `countryOfExport`). Wire
shape is identical to Quoting's own `ValueRequiredError` payload
(`{ details: [{ code, number, message, description, helpLink, severity }] }`).
Mutually exclusive with `compliance`; the classification itself is
unaffected. |
| `error` | Error |  |

#### ClassificationGetResponse

Unify GET response - one classification record identified by the
composite `id`, with the originating item embedded as `item`.
Field projection is driven by the legacy classification record
loaded from the underlying store (HSCT / ItemMaster / DW /
parking lot, in priority order), not by parsing the ID. Fields
beyond `id` / `status` are populated when the legacy record
carries them. The embedded `item` reverses the legacy
field-name flips (`description` -> `title`, `summary` ->
`description`, `itemGroup` -> `category`); the
`_uncategorized` sentinel is silently dropped.

| Property | Type | Description |
|---|---|---|
| `id` | string | Composite ID; same value the POST response surfaces under `items[].classifications[].id`. |
| `activity` | string | Reverse-mapped from the legacy uppercase activity (`HS_FULL` -> `hsFull`, `ECCN` -> `eccn`, etc.). |
| `classificationType` | string |  |
| `countryOfDestination` | string | Present for `hsFull` / `hs6`. |
| `countryOfExport` | string | Present for `export` and `eccn`, read from the record's
`country_of_export` parameter (not from `countryOfDestination`). |
| `countryOfOrigin` | string | Present for `coo` — the resolved country of origin, read from the
record's `coo` parameter. |
| `enrichments` | array |  |
| `classificationModifiers` | ClassificationModifiers |  |
| `status` | string | Reverse-mapped from the legacy status using the same heuristics as the POST builder. |
| `hsCode` | string | Final HS code; present once `status == "classified"` for `hsFull` / `hs6`.
Never present for `eccn` — see `eccnCode`. |
| `eccnCode` | string | Export-control code (e.g. `5A001.a`) returned for `eccn` instead of
`hsCode`, read from the item's `eccn_code` parameter
(`product.eccn_code`). Absent until the classification has been worked. |
| `confidence` | string | Suppressed when the resolved customer's `confidenceFlag` is off. |
| `confidenceDescription` | string | Suppressed alongside `confidence` for customers without the confidence flag. |
| `rationale` | string | Suppressed unless `status == "classified"` (V1 parity). |
| `item` | Item |  |

#### AuditTrailResponse

AIML-26393. Built entirely from DW's `product`/`product_history`/`customer` tables -
deliberately no Bitnami/Akeneo fallback. See
`documentation/design/audit_trails/Audit-Trail-AIML-26393.md` for the full field-by-field
mapping and design history.

| Property | Type | Description |
|---|---|---|
| `classificationId` | string | Same composite ID as `GET /classifications/{classificationId}`. |
| `itemCode` | string |  |
| `companyId` | string |  |
| `status` | string | `COMPLETED` when `product.hs_state == SENT`, else `PENDING`. Deliberately different
vocabulary from `GET /classifications/{classificationId}`'s `status`
(`pending`/`classified`/`rejected`/`cannotBeClassified`) for the same underlying item -
an intentional, currently-unresolved divergence, not an oversight. |
| `audited` | integer | Raw passthrough of product.audited (0/1). |
| `premium` | string | Raw passthrough of product.premium ("true"/"false" string). |
| `reclassify` | string |  |
| `mtccPlus` | string |  |
| `isExport` | string |  |
| `itemMasterSaveStatus` | string |  |
| `broker` | string |  |
| `coe` | string | Country of Entry. |
| `cooRequired` | string |  |
| `decision` | object |  |
| `productData` | object |  |
| `decisionWorkflow` | object |  |
| `justification` | string | ICP natural-language explanation. |
| `compliance` | string | Raw product.compliance JSON string. |
| `premiumRationale` | object | Omitted entirely (not present as a key) unless product.premium is truthy. |
| `versionHistory` | array | Each old/new pair (`hsCode`/`audited`/`hsState`/`companyId`/`autoAssignmentState`) is
only present when it actually changed in that row; whole rows with no tracked change
are dropped entirely except the first (baseline) row, which is always kept. |

#### Error

Small `{ "code", "message" }` object used **only** inside successful HTTP responses
for **nested** failures — e.g. `207` payloads under `items[].error` or
`items[].classifications[].error` when a single item or COD is rejected.

**Not** used for HTTP-level `403` / `413` / `422` / `503` / `500` on `POST .../classifications`;
those responses use `AvaTaxError`. Gateway `401` also uses `AvaTaxError`.

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `message` | string |  |

#### ComplianceError

Compliance-enrichment error envelope returned on
`ClassificationResult.complianceError` when the unify compliance gate
short-circuited the Quoting call. Wire shape is identical to
Quoting's own `{ details: [...] }` validation-error payload, so
unify callers can reuse the error handling they already wrote for
direct AvaTax / Quoting calls. Mutually exclusive with
`ClassificationResult.compliance`.

| Property | Type | Description |
|---|---|---|
| `code` | string | Optional top-level summary code. Omitted by the unify gate today. |
| `message` | string | Optional top-level summary message. Omitted by the unify gate today. |
| `target` | string | Optional context tag. Omitted by the unify gate today. |
| `details` | array |  |

#### ComplianceErrorDetail

Single entry inside `ComplianceError.details[]`. 1:1 mirror of the
AvaTax `ErrorDetail` shape (minus the legacy `faultCode` slot). The
unify gate emits `{ code: "ValueRequiredError", number: 5 }` for
required-field violations - byte-for-byte identical to what Quoting
returns when called directly.

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `number` | integer |  |
| `message` | string |  |
| `description` | string |  |
| `helpLink` | string |  |
| `severity` | string |  |

#### AvaTaxError

AvaTax-style error envelope `{ "error": { "code", "target", "details"[] } }`.

Returned for:
- Gateway / OAuth failures (**401**) before the controller runs.
- **POST** `/{companyId}/classifications` HTTP-level failures: **403**, **413**, **422**,
  **503**, **500** (and downstream `AvaTaxException` bodies when mapped by the controller).
- **GET** `.../classifications/{id}` **404**, **422**, **500** (mirrors legacy V1).

Nested per-item / per-COD errors inside a **207** `ClassificationResponse` still use the flat
`Error` schema on the embedded `error` fields.

Use `error.code` plus `error.details[].number` to branch programmatically. Common numbers
include `31` / `34` / `37` (auth), `5` (`ValueRequiredError`), `50` (`UnhandledException`),
`70` (`ModelStateInvalid`), `3011` (`MtccPlusNotProvisioned`), `3015` (`EntitlementRequired`),
`3016` (`PayloadTooLarge`), `3008` (`RetryRequest`), and values from `ClassificationError`.

| Property | Type | Description |
|---|---|---|
| `error` | object |  |

#### AvaTaxErrorDetail

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `number` | integer |  |
| `message` | string |  |
| `description` | string |  |
| `helpLink` | string |  |
| `severity` | string |  |


---

## Avalara Item Classification API — Unified Classification Endpoint

The unified ("unify") Classification endpoint: `POST` and `GET` on
`/api/v3/companies/{companyId}/classifications`.

One endpoint covers the classification workflows available to your account. You state what
you want and how you want it produced:

* **`classificationType`** — how the answer is produced: `managed` (an Avalara customs
  specialist works the item), `autoPro` / `autoEssentials` (machine classification), or one
  of the read-only checks `verify` / `validate` / `compliance`.
* **`classifications`** — what to produce: `hsFull`, `hs6`, `export`, `eccn`, `coo`, plus the
  `compliance` and `premium` enrichments.
* **`classificationModifiers`** — options that adjust the run, such as `autoPerformance`,
  `priority` and `includeJustification`.

Submit one item or many in a single call, for one destination country or several.

## Authentication

Send an Avalara-issued OAuth 2.0 bearer token with each request. Access to some workflows
depends on the capabilities enabled for your account.

## Response lifecycle

Auto-classification and read-only checks usually return final results synchronously.
Managed activities return `status: "pending"` with an `id`. Use that `id` to retrieve the
final result later or to correlate the webhook callback.

A successful request returns **201**. If some items, countries, or activities are rejected
while others succeed, the response is **207**. If the request cannot be processed at all, the
endpoint returns the relevant 4xx or 5xx error response.

## Asynchronous managed lifecycle

`managed` — and `export` / `eccn`, which are always handled by a managed review — hand
the item to an Avalara customs specialist. The `POST` returns immediately with **201** and
`status: "pending"` plus a classification `id`; the final code arrives later through either of
two channels:

* **Webhook push (recommended).** When the specialist completes the review, Avalara `POST`s
  the result to the callback URL you registered — see `POST /webhook/callback` below, which
  you implement. One callback per completed classification, correlated by
  `hsClassificationId`.
* **GET polling (fallback).** Re-fetch
  `GET /api/v3/companies/{companyId}/classifications/{classificationId}` until `status`
  moves off `pending`.

Store the `id` from every `classifications[]` entry: with several activities there is one id
per activity, and each is fetched separately.

## Multiple activities in one request

`classifications` may name several standalone activities — e.g. `["hsFull", "eccn"]`. Each
requested activity produces its own `classifications[]` entry (and its own `id`) per item,
listed in the order requested. If one activity is rejected but another can still be processed,
the response is **207**. If every requested activity is rejected, the response is **422**.

Request limits apply to the total number of item/activity combinations. A request with many
items and many activities may return **413** if it exceeds the allowed size.

## ECCN

`eccn` is an export-control classification, not a tariff one. It needs
`countryOfDestination` (the licence jurisdiction, `COD_REQUIRED`) **and** `countryOfExport`
on every item (the control list to classify against, `COE_REQUIRED_FOR_ECCN`). It is always
handled by the managed path, so a `POST` returns `pending` and carries **no code at all** —
neither `hsCode` (an export-control code is not tariff data) nor `eccnCode` (nothing has
classified the item yet). Fetch the classification by id once it has been worked: the `GET`
returns the code in `eccnCode`. `hsCodeTest` is ignored for `eccn`.

**Read-only ops.** `verify`, `validate`, and `compliance` are read-only checks over
caller-supplied HS codes (`providedHsCodes`): nothing is generated and no classification
record is created.
They can be requested via `classificationType` or `classifications`, combined with each other
(results merged per COD), but not with an HS activity
(`hsFull`/`hs6`/`export`/`eccn`/`coo`/`premium`) — verify/validate combined with an HS
activity is rejected with 422 (`READONLY_EXCLUSIVE_WITH_HS`).
`compliance` additionally requires compliance access for your account (403 otherwise).

**Version:** 3.0.0-draft
**Base URL:** https://hscodeclassification.api.sbx.avalara.com/api/v3
**Endpoints:** 4

### Endpoints

#### Classifications

##### Submit a unified classification request

`POST /companies/{companyId}/classifications`

Accepts a **JSON** body (`application/json`) in three shapes:
1. Single object with `item`.
2. Single object with `items[]` sharing classification config.
3. Top-level JSON array of independent requests.

**Multiple activities.** `classifications` may name several standalone activities
(`hsFull`, `hs6`, `export`, `eccn`, `coo`). Each requested activity produces its own
`items[].classifications[]` entry with its own `id`, listed in the order requested.
The enrichment options (`compliance`, `premium`) apply to the applicable classification
results. If one activity is rejected but another can still be processed, the response is
**207**. If every requested activity is rejected, the response is **422**. Request limits
apply to the total number of item/activity combinations; exceeding the allowed size
returns **413**.

**Document upload (`multipart/form-data`).** The same URL also accepts a document upload:
submit a PDF and/or image(s) and the service reads them into classification object(s) before
classifying (it does not require a structured `item`). Routing is by `Content-Type`
(`application/json` → JSON shapes above; `multipart/form-data` → document upload). Uploaded
files are validated (type, size, page count, readability, encryption, image quality) and one
classification result is returned per distinct product extracted from the document(s).
Extraction that yields no products returns `422` (`EXTRACTION_EMPTY`); if the extraction
service is unreachable the response is `503`, and an extraction error is `502`.

**Success responses** (`201 Created`, `207 Multi-Status`) are always `{ "items": [...] }`.

**HTTP-level errors** (`401`, `403`, `413`, `422`, `429`, `500`, and `503`) return the
standard Avalara error envelope (`AvaTaxError`) —
`{ "error": { "code", "target", "details"[] } }`. Nested rejections inside a `207`
response use the smaller `{ "code", "message" }` object (`Error`).

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |

##### Retrieve a single classification by ID

`GET /companies/{companyId}/classifications/{classificationId}`

Returns a single classification by its composite ID — the value returned in
`items[].classifications[].id` when the classification was created with
`POST /api/v3/companies/{companyId}/classifications`
(e.g. `2-76713-1651708-CHEM-SAMPLE-008-HS_FULL-US`).

This is how an asynchronous classification is collected: a `managed`, `export` or `eccn`
create returns `pending`, and re-fetching the id here returns the finished result once a
specialist has worked it. Treat the id as opaque and store it as returned.

One id addresses **one activity**. A create that requested several activities returns
one id per activity per item, each fetched separately here — there is no combined view.

For an `eccn` classification the code comes back in `eccnCode` (never `hsCode`), and the
country is reported in `countryOfExport`.

## Error envelope by HTTP status

| Status | Envelope        | Trigger                                                                       |
|--------|-----------------|-------------------------------------------------------------------------------|
| 200    | `ClassificationGetResponse` | Classification found.                                             |
| 401    | `AvaTaxError`   | Authentication failed.                                                        |
| 403    | `AvaTaxError`   | The caller is not permitted to read this classification.                      |
| 404    | `AvaTaxError`   | No classification exists for this ID, or it is not visible to this customer.  |
| 422    | `AvaTaxError`   | The request could not be processed — company ID or customer access problem.   |
| 500    | `AvaTaxError`   | Unhandled server error (`UnhandledException`).                                |

**Parameters:**

| Name | Type | In | Required | Description |
|---|---|---|---|---|
| `undefined` |  |  | No |  |
| `classificationId` | string | path | Yes | Composite classification ID returned by the POST endpoint
(e.g. `2-76713-1651708-CHEM-SAMPLE-008-HS_FULL-US`). Treat
it as opaque on the client - the server resolves it through
the SKU mapping store, not by parsing segments. |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |
| `undefined` |  |  | No |  |


#### Classification Webhook

##### Receive a completed classification (implemented by the caller)

`POST /webhook/callback`

Avalara `POST`s here when a classification you submitted finishes. Implement this only if
you have opted in to webhook delivery; otherwise poll
`GET /api/v3/companies/{companyId}/classifications/{classificationId}` instead.

This is the push half of the asynchronous lifecycle: a `managed`, `export` or `eccn`
create returns **201** with `status: "pending"`, and this callback arrives once a customs
specialist has worked the item. One callback is delivered per completed classification, so
an item submitted for several activities produces several callbacks — match them up with
`hsClassificationId`, which is the same value the create response returned as
`items[].classifications[].id`.

**Authentication.** Avalara authenticates itself to your endpoint with the credentials you
register — `Authorization: Basic {token}` or `Authorization: Bearer {token}`. Your own API
bearer token plays no part here, which is why this operation declares no security scheme.

**Responding.** Return `200` or `201` within a few seconds; the recommended body is
`{"name":"API_RESPONSE","value":"SUCCESSFUL"}`. Return `400` with
`{"name":"API_RESPONSE","value":"ERROR"}` to reject a payload you cannot process.

**Retries and idempotency.** Connectivity failures, timeouts and `5xx` responses are
retried, so the same callback can arrive more than once. De-duplicate on
`hsClassificationId` and make your handler idempotent. A `400` is not retried.


#### Notification Webhook

##### Receive a nomenclature change notification (implemented by the caller)

`POST /nomenclature/webhook/notifications`

Avalara `POST`s here when reference data your catalog depends on changes, so you can react
without re-submitting items. Three payload shapes are delivered, distinguished by their
top-level fields — accept all three on this one endpoint, or register a separate URL per
kind if you prefer to route them apart:

* **Content changes** (`ContentChangeNotification`) — a description was reworded or an HS
  code was retired. Identified by `notificationType: "NOMENCLATURE_CHANGES"`. Changes are
  nested by HS root and then by country: `changes[] -> changedCountries[] -> changes[]`.
* **Item changes** (`ItemChangeNotification`) — a change that hits specific items in your
  catalog. Flat `changes[]`, each entry carrying its own item identity and an `eventType`
  of `TARIFF_CHANGE` (reclassified, `hsCode` is the new code) or `INVALID_HSCODE`
  (retired or expired).
* **Duty changes** (`DutyChangeNotification`) — a duty rate moved. Flat `changes[]` keyed
  on HS code and rate, with no catalog items; rates appear as nested `previous` and
  `current` objects.

Content notifications are filtered by the HS masks configured for your account; item
notifications by your configured category or item-code filters.

**Responding.** Return any `2xx` to acknowledge. A `4xx` is treated as a permanent
rejection and is not retried; `5xx` responses and timeouts are retried, so de-duplicate on
`notificationId`.

### Models

#### DocumentUploadRequest

`multipart/form-data` body for the document-upload variant of this endpoint. Submit a PDF
and/or image(s); the service reads them into classification object(s) and classifies them.
At least one of `pdf`, `images`, `text`, `imageUrl`, or `url` must be supplied. The
classification-intent fields (`classificationType`, `classifications`, `countryOfDestination`)
are applied to every product extracted from the document(s). Files are validated for type,
size (≤ 25 MB/file, ≤ 50 MB total, ≤ 10 files), page count (≤ 50), readability, encryption,
and image quality before extraction.

| Property | Type | Description |
|---|---|---|
| `pdf` | string | A single PDF document to classify. |
| `images` | array | One or more image files (PNG / JPG / JPEG). Repeat the field to send several. |
| `text` | string | Raw product text/description (alternative or supplement to a file). |
| `title` | string | Optional product title hint. |
| `imageUrl` | string | URL of a product image to read, instead of or in addition to an uploaded file. |
| `url` | string | URL of a product page to read. |
| `countryOfExport` | string | ISO 3166-1 alpha-2 country-of-export hint. |
| `isExport` | boolean | When true, classify using export (Schedule B) codes. |
| `classificationType` | string | Classification tier applied to every extracted product. |
| `classifications` | string | Comma-separated classification activities applied to every extracted product — e.g.
`hsFull` or `hsFull,compliance`. Allowed: `hsFull`, `hs6`, `export`, `eccn`,
`compliance`, `coo`, `premium`. Naming several standalone activities returns one
result per requested activity, the same way as the JSON body. |
| `countryOfDestination` | string | Comma-separated ISO 3166-1 alpha-2 destination country codes applied to every extracted
product — e.g. `US` or `US,CA`. Required when `classifications` includes `hsFull`/`hs6`. |

#### ClassificationRequest

| Property | Type | Description |
|---|---|---|
| `classificationType` | string | `verify` / `validate` / `compliance` are read-only ops: the caller supplies HS
codes via `providedHsCodes` and the endpoint checks them without generating a code
or persisting anything. They may also be requested via `classifications`. |
| `classifications` | array | What to classify. Two kinds of token:

* **Standalone activities** — `hsFull`, `hs6`, `export`, `eccn`, `coo`. Each produces
  its own result (its own `id`) per item. Several may be requested together, and the
  results are returned in the order listed here. `hsFull` and `hs6` are mutually
  exclusive (`HSFULL_HS6_EXCLUSIVE`).
* **Enrichments** — `compliance`, `premium`. Never standalone activities; they ride
  along with every activity in the request.

Per-activity requirements: `hsFull`/`hs6`/`eccn` require `countryOfDestination`
(`COD_REQUIRED`); `eccn` additionally requires `item.countryOfExport` on every item
(`COE_REQUIRED_FOR_ECCN`). A failing activity is rejected on its own under **207**;
the request only 422s when every activity fails.

Read-only ops (`verify`, `validate`, `compliance`) may be combined with each other
but not with HS activities (`hsFull`/`hs6`/`export`/`eccn`/`coo`/`premium`) —
combining verify/validate with an HS activity returns 422
(`READONLY_EXCLUSIVE_WITH_HS`). |
| `classificationModifiers` | ClassificationModifiers |  |
| `countryOfDestination` | array | Capped at 10 per request. Exceeding the limit returns 413. |
| `providedHsCodes` | object | Map of countryOfDestination -> HS code. Required for the read-only ops
(`verify`/`validate`/`compliance`): the caller supplies the code to check for each
COD; nothing is generated. Also used to run compliance enrichment on a managed HS
classification. |
| `hsCodeTest` | object | **Sandbox / integration use only.** Pre-supplied HS code per
country of destination. When present, the server treats the
listed code as the final classification for that COD and
bypasses all classification logic (no classifier call).
Strictly capped to 20 characters per value.

Independent of `classificationType`, every COD listed in
`hsCodeTest` uses the supplied test HS code as the final result.
When the caller asked for an auto type and supplied `hsCodeTest`
for every entry in `countryOfDestination`, all CODs use the test
values. Partial coverage means covered CODs use the test values
and uncovered CODs follow the requested auto path.

Covered CODs surface in the response as
`status: "classified"`, `hsCode: ""`,
`confidence: "test"`. Uncovered CODs follow the regular auto
response shape. The response's `classificationType` echoes
the original caller value.

**Ignored for `eccn`** — a test *HS* code is meaningless for an export-control
result and would otherwise put an HS code on it. |
| `complianceData` | ComplianceData |  |
| `item` | Item |  |
| `items` | array | Bulk shape. Capped at 10 items per request. Exceeding the limit returns 413.

When several standalone activities are requested, the limit applies to the total
number of item/activity combinations. |

#### ClassificationModifiers

| Property | Type | Description |
|---|---|---|
| `broker` | boolean |  |
| `mtccPlus` | boolean |  |
| `reclassify` | boolean |  |
| `verify` | boolean | Runs verification as an **enrichment on a managed classification**: the item is
classified, and the result is compared against the code you supplied. This is not
the standalone read-only check.

Prefer `classifications: ["verify"]` (or `classificationType: "verify"`), which
checks a supplied HS code without classifying or persisting anything. |
| `autoSubmit` | boolean | Verify only. When `true`, a COD whose `providedHsCode` fails verification is
auto-submitted for classification (this sub-path persists). Default `false`. |
| `autoPerformance` | string |  |
| `priority` | integer |  |
| `includeJustification` | boolean | Return a natural-language justification for each classification as
`items[].classifications[].justification`.

`autoPro` always requests a justification regardless of this flag; every other
classification type requests one only when this is explicitly `true`. Accepted on
`managed` too - a managed request can still be classified automatically further down
the review workflow.

Note a synchronous `managed` response is `pending`, so no justification can appear on
it; the create response carries one only where the classification was produced
synchronously by a tier whose classifier can return it.

The justification is LLM-generated, so enabling it adds latency to every prediction.
Once produced, it is also returned on a later `GET` of the same
classification - including for classifications completed asynchronously. |

#### ComplianceData

Pure pass-through map. Anything the caller puts here is forwarded
verbatim to the trade compliance service. The only locally
validated fields are `startDate` / `endDate` (yyyy-MM-dd, end >=
start). System-managed keys (`id`, `hscode`, `countryOfImport`,
`countryOfExport`, `countryOfManufacture`, `section232Parameters`)
are silently stripped before forwarding. Section 232 must be sent
via `item.section232[]`.

| Property | Type | Description |
|---|---|---|
| `startDate` | string |  |
| `endDate` | string |  |

#### Item

| Property | Type | Description |
|---|---|---|
| `companyId` | integer |  |
| `itemCode` | string |  |
| `parentCode` | string |  |
| `title` | string |  |
| `description` | string |  |
| `category` | string |  |
| `url` | string |  |
| `imageUrl` | string |  |
| `material` | string |  |
| `countryOfOrigin` | string |  |
| `countryOfExport` | string |  |
| `hsHint` | string |  |
| `taxCode` | string |  |
| `job` | string |  |
| `measurements` | Measurements |  |
| `identifiers` | Identifiers |  |
| `manufacturer` | Manufacturer |  |
| `section232` | array |  |
| `attributes` | object |  |

#### Measurements

| Property | Type | Description |
|---|---|---|
| `price` | string | " " |
| `weight` | string | " " (lb|oz|kg|g) |
| `length` | string | " " (in|cm|m|mm) |
| `width` | string |  |
| `height` | string |  |

#### Identifiers

| Property | Type | Description |
|---|---|---|
| `gtin` | string |  |
| `upc` | string |  |
| `ean` | string |  |
| `mpn` | string |  |
| `asin` | string |  |
| `jan` | string |  |
| `isbn` | string |  |
| `cas` | string |  |
| `ebayEpid` | string |  |
| `ebayItemId` | string |  |

#### Manufacturer

| Property | Type | Description |
|---|---|---|
| `manufacturerName` | string |  |
| `countryOfManufacture` | string |  |

#### Section232Entry

| Property | Type | Description |
|---|---|---|
| `material` | string |  |
| `percentage` | number |  |
| `countryOfOrigin` | string |  |

#### ClassificationResponse

| Property | Type | Description |
|---|---|---|
| `items` | array |  |

#### ItemResult

| Property | Type | Description |
|---|---|---|
| `itemCode` | string |  |
| `companyId` | integer |  |
| `status` | string |  |
| `classificationType` | string |  |
| `error` | Error |  |
| `classifications` | array |  |

#### ClassificationResult

| Property | Type | Description |
|---|---|---|
| `id` | string |  |
| `activity` | string |  |
| `countryOfDestination` | string | Present for `hsFull` / `hs6`, and for `eccn` / `export` as the jurisdiction the
request was made against. |
| `countryOfExport` | string | Echoed from `item.countryOfExport` for `export` and `eccn`. |
| `countryOfOrigin` | string | Present for `coo` — the resolved ISO-2 country of origin. |
| `enrichments` | array |  |
| `classificationModifiers` | ClassificationModifiers |  |
| `status` | string |  |
| `hsCode` | string | Never set for `eccn` — an export-control code is not tariff data, so the HS code the
underlying record may carry is deliberately withheld from an `eccn` result. |
| `eccnCode` | string | Export-control classification number (e.g. `5A001.a`).

**Always absent on this create response.** `eccn` is managed-only, so at submission
time nothing has classified the item yet and the result carries no code in either
field — the same as `coo`. Read it from
`GET /api/v3/companies/{companyId}/classifications/{classificationId}` once the
classification has been worked. |
| `grade` | string | Raw auto prediction grade returned for autoPro and autoEssentials HS results. |
| `confidence` | string | Present when the HS code came from the request's hsCodeTest override. |
| `verifyResult` | string | Present for the `verify` op — per-COD accepted/rejected of the `providedHsCode`. |
| `providedHsCode` | string | The caller-supplied HS code that was checked (verify / validate ops). |
| `valid` | boolean | Present for the `validate` op — whether the `providedHsCode` is valid reference data. |
| `validatedOn` | string | Validate op — date the code was validated on. |
| `validUntil` | string | Validate op — date the code is valid until. |
| `message` | string |  |
| `justification` | string | Natural-language justification for this per-COD classification, containing
**HS4 Reasoning**, **HS6 Reasoning** and **Full HS Reasoning** sections.

Present on the create (POST) response when the classification was produced
synchronously by a classifier that can produce one. `autoPro` resolves through a
path that cannot, so its justification is generated later, once the classification
has been completed.

Once produced, the value is also returned by
`GET /api/v3/companies/{companyId}/classifications/{classificationId}`. |
| `compliance` | array | Per-COD trade compliance payload. Populated only when `compliance` is in
`classifications` *and* an HS code is available for the COD (auto path: the
classifier's prediction; managed path: caller-supplied `providedHsCodes`).
Mutually exclusive with `complianceError`. Each entry's top-level
fields are `id`, `companyId`, `countryOfExport`, `countryOfImport`,
`countryOfManufacture`, `hscode` (lower-case `c`), `manufacturerName`, `productName`,
`duty` (`calculationBasis`, `mfn`, `punitiveRates[]`, `ftaRates[]`), `cvds[]`,
`adds[]`, `restrictions[]`, `effectiveDate` and `effectiveRate`, plus the
`hasCVD` / `hasADD` / `hasRestrictions` / `hasPGA` / `hasPunitiveRate` / `hasFTARate`
booleans. Passed through verbatim and typed here as a free-form object, so it is not
validated by this spec. |
| `complianceError` | object | Set when the compliance lookup was skipped because the item is missing a field the
trade compliance service requires (today: `countryOfExport`). Wire
shape is identical to that service's own `ValueRequiredError` payload
(`{ details: [{ code, number, message, description, helpLink, severity }] }`).
Mutually exclusive with `compliance`; the classification itself is
unaffected. |
| `error` | Error |  |

#### ClassificationGetResponse

One classification record identified by its composite `id`, with the product it was
created for embedded as `item`. Only `id` and `status` are guaranteed; every other field
appears when the classification record carries it, so a `pending` record returns little
more than its status.

| Property | Type | Description |
|---|---|---|
| `id` | string | Composite ID; the same value returned on create under `items[].classifications[].id`. |
| `activity` | string | The activity this classification was created for. |
| `classificationType` | string |  |
| `countryOfDestination` | string | Present for `hsFull` / `hs6`. |
| `countryOfExport` | string | Present for `export` and `eccn` — the country the goods are exported from. |
| `countryOfOrigin` | string | Present for `coo` - the resolved ISO-2 country of origin. |
| `enrichments` | array |  |
| `classificationModifiers` | ClassificationModifiers |  |
| `status` | string | `pending` until the classification has been worked; then `classified`, `rejected` or
`cannotBeClassified`. |
| `hsCode` | string | Final HS code; present once `status == "classified"` for `hsFull` / `hs6`.
Never set for `eccn` — see `eccnCode`. |
| `eccnCode` | string | Export-control classification number for an `eccn` classification. Returned instead
of `hsCode`, never alongside it: an export-control code is not tariff data. |
| `confidence` | string | Omitted for customers whose account does not include confidence reporting. |
| `confidenceDescription` | string | Omitted alongside `confidence` for customers without confidence reporting. |
| `rationale` | string | The classifier's reasoning for the assigned code. Present only once `status` is `classified`. |
| `justification` | string | Natural-language justification for the classification. Present whenever one was
produced, whether at create time or later when the classification was completed.

Distinct from `rationale`, which carries the premium/agent classification rationale. |
| `item` | Item |  |

#### Error

Per-item / per-COD failure object used **only** inside successful HTTP responses for
**nested** failures — e.g. `207` payloads under `items[].error` or
`items[].classifications[].error` when a single item or COD is rejected.

Two shapes are emitted (both backwards compatible; unused fields are omitted):
  * **Simple** — `{ code, message }` for validation / auth short-circuits
    (e.g. `NO_RESULT`, `INVALID_COD`), and for a per-activity rejection in a
    multi-activity request — the activity's own rule code (e.g. `COE_REQUIRED_FOR_ECCN`)
    when it failed validation, or `ACTIVITY_FAILED` when it failed while running. If the
    activity broke more than one rule, the first is the headline and the rest follow in
    `details[]`.
  * **Structured** — `{ code, target, details[] }`, the AvaTax `ValueRequiredError`
    shape. Emitted for a `verify` item missing the required `title` or `category`;
    only the offending item is rejected, so sibling items still succeed under a `207`.

**Not** used for HTTP-level `403` / `413` / `422` / `503` / `500` on `POST .../classifications`;
those responses use `AvaTaxError`, as does `401`.

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `message` | string | Present on the simple shape; omitted when details[] is present. |
| `target` | string | Present on the structured shape. |
| `details` | array | Present on the structured shape. |

#### ErrorDetail

Single entry inside the structured `Error.details[]`, following the AvaTax
`ErrorDetail` shape so the same error handling works for AvaTax responses generally.

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `number` | integer |  |
| `message` | string |  |
| `description` | string |  |
| `helpLink` | string |  |
| `severity` | string |  |

#### ComplianceError

Compliance-enrichment error envelope returned on
`ClassificationResult.complianceError` when the compliance lookup
was skipped. Wire shape is identical to the trade compliance
service's own `{ details: [...] }` validation-error payload, so
the same error handling works for compliance data obtained
directly from AvaTax. Mutually exclusive with
`ClassificationResult.compliance`.

| Property | Type | Description |
|---|---|---|
| `code` | string | Optional top-level summary code. Not emitted by the gate today. |
| `message` | string | Optional top-level summary message. Not emitted by the gate today. |
| `target` | string | Optional context tag. Not emitted by the gate today. |
| `details` | array |  |

#### ComplianceErrorDetail

Single entry inside `ComplianceError.details[]`. 1:1 mirror of the
AvaTax `ErrorDetail` shape. The gate emits
`{ code: "ValueRequiredError", number: 5 }` for required-field violations -
byte-for-byte identical to what the trade compliance service returns directly.

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `number` | integer |  |
| `message` | string |  |
| `description` | string |  |
| `helpLink` | string |  |
| `severity` | string |  |

#### AvaTaxError

AvaTax-style error envelope `{ "error": { "code", "target", "details"[] } }`.

Returned for:
- Authentication failures (**401**).
- **POST** `/{companyId}/classifications` HTTP-level failures: **403**, **413**, **422**,
  **502**, **503**, **500**.
- **GET** `.../classifications/{id}`: **403**, **404**, **422**, **500**.

Nested per-item / per-COD errors inside a **207** `ClassificationResponse` still use the flat
`Error` schema on the embedded `error` fields.

Use `error.code` plus `error.details[].number` to branch programmatically. Common numbers
include `31` / `34` / `37` (auth), `5` (`ValueRequiredError`), `50` (`UnhandledException`),
`70` (`ModelStateInvalid`), `3011` (`MtccPlusNotProvisioned`), `3015` (`EntitlementRequired`),
`3016` (`PayloadTooLarge`), `3008` (`RetryRequest`), and values from `ClassificationError`.

| Property | Type | Description |
|---|---|---|
| `error` | object |  |

#### AvaTaxErrorDetail

| Property | Type | Description |
|---|---|---|
| `code` | string |  |
| `number` | integer |  |
| `message` | string |  |
| `description` | string |  |
| `helpLink` | string |  |
| `severity` | string |  |

#### WebhookAck

The acknowledgement body your webhook endpoint returns to Avalara.

| Property | Type | Description |
|---|---|---|
| `name` | string |  |
| `value` | string |  |

#### ClassificationWebhookRequest

The payload Avalara delivers to your webhook endpoint when a classification completes.
Null fields are omitted rather than sent as `null`.

| Property | Type | Description |
|---|---|---|
| `hsClassificationId` | string | The completed classification — the same composite value returned on create under
`items[].classifications[].id`. Use it to correlate the callback with your request and
to de-duplicate retries. |
| `companyId` | string | The company that owns the item. |
| `itemCode` | string | Your item code / SKU. |
| `countryOfDestination` | string | The country the classification was performed for. Carries the country of export for
`export` and `eccn` classifications. |
| `countryOfOrigin` | string | The item's country of origin, when known. |
| `status` | string | Terminal outcome. Uppercase here, unlike the lower-camel `status` on the API responses. |
| `hsCode` | string | The assigned code. Absent when `status` is `CANNOT_BE_CLASSIFIED`. |
| `rationale` | string | The specialist's explanation of the classification decision, when available. |
| `possibleHsCodes` | string | Alternative codes that could apply, delivered as a **stringified JSON array** rather
than a JSON array — parse it before use. Sent only when the `broker` modifier was set
and alternatives exist. |
| `compliances` | array | Trade compliance data, when compliance enrichment was requested. Same free-form entry
shape as `ClassificationResult.compliance`. |

#### ContentChangeNotification

A content-level nomenclature change: a description was reworded, or an HS code was retired.
Changes are nested by HS root and then by country
(`changes[] -> changedCountries[] -> changes[]`).

| Property | Type | Description |
|---|---|---|
| `notificationId` | string | Unique id for this notification. De-duplicate retries on it. |
| `timestamp` | string | When the notification was sent (ISO-8601 UTC). |
| `customerId` | integer | The Avalara customer account this notification belongs to. |
| `notificationType` | string | Always `NOMENCLATURE_CHANGES`. This is what distinguishes this payload from the other two. |
| `summary` | object | High-level counts for the changes in this notification. |
| `changes` | array | The changes, grouped by HS root. |

#### ContentChangeSummary

High-level counts for a content-change notification.

| Property | Type | Description |
|---|---|---|
| `totalRoots` | integer | Number of HS roots affected. |
| `totalChanges` | integer | Number of individual changes included. |
| `affectedChapters` | array | The affected HS chapters — the first two digits of each root. |
| `affectedCountries` | array | The affected countries, ISO 3166-1 alpha-2. |

#### RootChange

Content-level changes grouped under one HS root.

| Property | Type | Description |
|---|---|---|
| `root` | string | The HS root (e.g. the heading) the grouped changes belong to. |
| `changedCountries` | array | The countries with changes under this root. |

#### ChangedCountry

Content-level changes for one country under one HS root.

| Property | Type | Description |
|---|---|---|
| `country` | string | The country the changes apply to, ISO 3166-1 alpha-2. |
| `changes` | array | The changes for this country. |

#### ContentChange

A single content-level nomenclature change.

| Property | Type | Description |
|---|---|---|
| `hsCode` | string | The HS code affected. |
| `changeType` | string | What changed. Note a retired code is `INVALID_CODE` here, whereas the item-level
payload reports the same situation as `eventType: "INVALID_HSCODE"`. |
| `oldDescription` | string | The description before the change; `null` when not applicable. |
| `newDescription` | string | The description after the change; `null` when not applicable. |
| `effectiveFrom` | integer | Effective-from date as an integer in `YYYYMMDD` form — not a date string. |
| `effectiveTo` | integer | Effective-to date as `YYYYMMDD`; `null` when the code has no expiry. |

#### ItemChangeNotification

Item-level changes affecting specific products in your catalog. A flat `changes[]`
envelope; item identity is carried inline on each entry.

| Property | Type | Description |
|---|---|---|
| `notificationId` | string | Unique id for this notification. De-duplicate retries on it. |
| `timestamp` | string | When the notification was sent (ISO-8601 UTC). |
| `detectedAt` | string | When the underlying changes were detected (ISO-8601 UTC). Omitted when unknown. |
| `customerId` | integer | The Avalara customer account this notification belongs to. |
| `totalChanges` | integer | Number of item-level changes included. |
| `changesByType` | object | Counts grouped by `eventType`. |
| `changes` | array | The item-level changes. |

#### ItemChange

A single item-level change. For `TARIFF_CHANGE`, `hsCode` is the **new** code.

| Property | Type | Description |
|---|---|---|
| `COD` | string | Country of destination, ISO 3166-1 alpha-2. Note the uppercase field name. |
| `hsCode` | string | The HS code affected. |
| `eventType` | string | `TARIFF_CHANGE` for a reclassification, `INVALID_HSCODE` for a retired or expired code. |
| `companyId` | integer | The company that owns the affected item. |
| `itemId` | integer | The Avalara item id. |
| `itemCode` | string | Your catalog item code / SKU. |
| `itemDescription` | string | The affected item's description. |
| `itemGroup` | string | The affected item's category / item group. |

#### DutyChangeNotification

Duty rate changes. A flat `changes[]` envelope keyed on HS code and rate; no catalog items
are involved.

| Property | Type | Description |
|---|---|---|
| `notificationId` | string | Unique id for this notification. De-duplicate retries on it. |
| `timestamp` | string | When the notification was sent (ISO-8601 UTC). |
| `detectedAt` | string | When the underlying changes were detected (ISO-8601 UTC). Omitted when unknown. |
| `customerId` | integer | The Avalara customer account this notification belongs to. |
| `totalChanges` | integer | Number of duty rate changes included. |
| `changesByType` | object | Counts grouped by `eventType`. |
| `changes` | array | The duty rate changes. |

#### DutyChange

A single duty rate change.

| Property | Type | Description |
|---|---|---|
| `hsCode` | string | The HS code the duty applies to. |
| `eventType` | string | The kind of duty change. |
| `tariffSystemCode` | string | The tariff system the change belongs to (e.g. `EU`). |
| `effectiveCountries` | array | The countries this change applies to, resolved from the tariff system. |
| `rateLabel` | string | Human-readable label for the duty rate. |
| `tradeGroupName` | string | Trade group / FTA name, for `FTA_UPDATE` changes. |
| `previous` | object | The rate before the change. Omitted for `DUTY_ADDED`. |
| `current` | object | The rate after the change. Omitted for `DUTY_REMOVED`. |
| `effectiveFrom` | string | When the change takes effect (ISO-8601). |
| `effectiveTo` | string | When the rate stops being effective (ISO-8601); `null` when it has no expiry. |

#### DutyRateDetail

One side — previous or current — of a duty rate.

| Property | Type | Description |
|---|---|---|
| `rateValue` | number | The numeric duty rate value. |
| `currencyIsoCode` | string | Currency for specific or compound duties. Omitted for ad-valorem percent rates. |
| `unitOfCalculationCode` | string | The unit the rate is calculated on, e.g. `PERCENT` or a per-unit measure. |
