API Documentation

Complete guide to integrating with the Duties & Tariffs API v1 Changelog

Contents

Getting Started

The API provides programmatic access to comprehensive HTS (Harmonized Tariff Schedule) data. Get complete tariff information including base duty rates and additional measures. This RESTful API uses standard HTTP response codes and returns JSON-encoded responses.

Base URL
https://tariffsapi.com/api/v1

Prerequisites: Active premium subscription and a valid API key.

Authentication

All API requests require authentication using an API key.

Bearer Token (Recommended)

curl "https://tariffsapi.com/api/v1/tariffs/resolve?hts=8541.10.00.80&origin=CN" \
  -H "Authorization: Bearer YOUR_API_KEY"

Alternative: X-API-Key Header

curl "https://tariffsapi.com/api/v1/tariffs/resolve?hts=8541.10.00.80&origin=CN" \
  -H "X-API-Key: YOUR_API_KEY"

Security: Never expose your API keys in client-side code, public repositories, or version control systems.

Rate Limits

API requests are rate-limited per API key.

Plan Limits
Per Minute
300 requests to /resolve
Per Month
Starter: 1,000 calls
Developer: 10,000 calls

Rate Limit Headers

Every response includes these headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1640995200

Tariff Resolution API

Resolve complete tariff information including base duty rates and additional measures (Section 301, 232, 338) for any HTS code and country of origin.

GET /api/v1/tariffs/resolve

Primary consumer-facing endpoint. Given an HTS code and country of origin, returns the complete tariff picture: base duty (general or special via trade agreements) plus every additional measure in force on that date (Section 301, 232, 338). Measures that have ended, such as the IEEPA tariffs and the Section 122 surcharge, are returned only when as_of falls inside the period they were in force. Uses HTS inheritance when needed. Returns a transparent breakdown—never silently sums rates. Use resolve_chapter_99=true to resolve reference-only measures (e.g. “See 9903.91.05”) to numeric rates by looking up the Chapter 99 HTS line in our database; rates are never invented.

Query Parameters

Parameter Required Description
hts Required HTS number (any length; inheritance applies)
origin Required ISO-2 country code (e.g., CN, MX, CA)
as_of Optional Date (YYYY-MM-DD) to price for. Omit to price for today (US Eastern). Pass a date only for historical/point-in-time rates — a hardcoded past date always returns that day’s rates, not current ones, and won’t reflect new tariffs or sunsets.
include_unknown_effective_dates Optional Include measures with unknown effective dates (default: true). Set false to exclude.
resolve_chapter_99 Optional Boolean (default: false). Resolves Chapter 99 references to numeric rates.

Example Request

const axios = require('axios');

// For origins where additional duties apply (e.g. CN), use resolve_chapter_99: true to get numeric rates
const response = await axios.get('https://tariffsapi.com/api/v1/tariffs/resolve', {
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
  params: {
    hts: '8541.10.00.80',
    origin: 'CN',
    as_of: '2026-10-15',
    include_unknown_effective_dates: true,
    resolve_chapter_99: true
  }
});

console.log(response.data);

Example Response

{
  "resolution": {
    "requested_hts": "8541.10.00.80",
    "resolved_hts": "8541.10.00",
    "origin": "CN",
    "as_of": "2026-10-15",
    "include_unknown_effective_dates": true,
    "resolve_chapter_99": true
  },
  "base_tariff": {
    "type": "general",
    "rate_type": "ad_valorem",
    "percentage_component": 0.0,
    "per_unit_component": null,
    "agreement": null
  },
  "additional_measures": [
    {
      "program": "section_301",
      "rate_type": "reference",
      "details": "See 9903.91.05",
      "chapter_99_code": "9903.91.05",
      "effective_date_unknown": true,
      "uncertain": true,
      "resolved_rate": {
        "rate_type": "ad_valorem",
        "percentage_component": 0.25,
        "source": "chapter_99",
        "chapter_99_hts": "9903.91.05"
      },
      "chapter_99_resolution": {
        "status": "resolved",
        "resolved_hts": "9903.91.05",
        "product_id": 12345
      }
    }
  ],
  "summary": {
    "applicable_ad_valorem_rate": 0.0,
    "resolved_additional_ad_valorem_rate": 25.0,
    "total_resolved_ad_valorem_rate": 25.0,
    "notes": [
      "Base duty is 0%",
      "Section 301: See 9903.91.05 for details"
    ]
  },
  "notice": "This information is provided for general educational and research purposes only. Tariffs API is not a licensed customs broker. Duty rates, HTS classifications, and tariff information should not be relied upon as legal or compliance advice, and should not be used as the basis for filing entry documentation with U.S. Customs and Border Protection without review and approval by a licensed customs broker. Actual classification and applicable duty rates are determined by CBP at its sole discretion.",
  "adcvd_status": "not_modeled",
  "adcvd_notice": "Antidumping and countervailing duty (AD/CVD) orders are not included. If your goods may fall under one, check with a licensed customs broker."
}

When resolve_chapter_99=true, reference measures may include resolved_rate (numeric rate from the Chapter 99 line) and chapter_99_resolution (status: resolved, not_found, no_numeric_rate, or multiple_rates). The summary may also include resolved_additional_ad_valorem_rate and total_resolved_ad_valorem_rate.

Missing hts or origin returns 422. Unknown HTS returns 404. Results are advisory only.

POST /api/v1/tariffs/resolve_batch Developer

Resolve multiple tariffs in a single request. Process up to 200 items with automatic deduplication and caching.

Request Body Parameters

Parameter Required Description
requests Required Array of request objects (max 200)
requests[].hts Required HTS number for this item
requests[].origin Required ISO-2 country code for this item
requests[].as_of Optional Date override for this specific item (YYYY-MM-DD)
as_of Optional Default date for all items (default: today)
include_unknown_effective_dates Optional Include measures with unknown dates (default: true)
resolve_chapter_99 Optional Resolve Chapter 99 references to numeric rates (default: false)

Example Request

const axios = require('axios');

const response = await axios.post(
  'https://tariffsapi.com/api/v1/tariffs/resolve_batch',
  {
    requests: [
      { hts: '8541.10.0080', origin: 'CN' },
      { hts: '7208.10.15', origin: 'CA' },
      { hts: '8703.23.00', origin: 'MX', as_of: '2025-01-15' }
    ],
    as_of: '2026-10-15',
    include_unknown_effective_dates: true,
    resolve_chapter_99: false
  },
  {
    headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
  }
);

console.log(`Processed ${response.data.meta.count} items in ${response.data.meta.ms_total}ms`);
console.log(response.data.results);

Example Response

{
  "meta": {
    "count": 3,
    "unique": 3,
    "ms_total": 45.23
  },
  "results": [
    {
      "index": 0,
      "ok": true,
      "data": {
        "resolution": {
          "requested_hts": "8541.10.0080",
          "resolved_hts": "8541.10.00.80",
          "origin": "CN",
          "as_of": "2026-10-15"
        },
        "base_tariff": { ... },
        "additional_measures": [ ... ],
        "summary": {
          "applicable_ad_valorem_rate": 25.0,
          "notes": ["Base: 0.0% (general) + Additional: 25.0% (section_301)"]
        }
      }
    },
    {
      "index": 1,
      "ok": true,
      "data": { ... }
    },
    {
      "index": 2,
      "ok": false,
      "error": {
        "code": "not_found",
        "message": "HTS code not found: 8703.23.00"
      }
    }
  ]
}

Limits: Maximum 200 items per batch. Each batch counts as one API request for rate limiting.

Common Use Cases

Bulk Import Analysis
Analyze entire product catalogs
Multiple Origins
Compare duties across countries
Historical Analysis
Track rate changes over time
Quote Generation
Calculate duties for line items

Performance Benchmarks

Batch Size Typical Time vs Individual Requests
10 items 50-150ms 10-20x faster
50 items 200-400ms 20-30x faster
100 items 400-800ms 20-40x faster
200 items 800-1500ms 25-50x faster

Each result includes an index field matching the original request array position and an ok boolean indicating success. Failed items include an error object with code and message fields.

GET /api/v1/tariffs/by_hts/:hts_number

Get base duties and additional measures for a single HTS code. Returns detailed duty information including all rate types.

Query Parameters

Parameter Type Description
origin string ISO country code for filtering additional duties
as_of date Date for filtering additional duties (default: today)
include_unknown_effective_dates boolean Include duties with unknown dates (default: true)

Example Request

const axios = require('axios');

const htsNumber = '8448.51.10.00';
const response = await axios.get(
  `https://tariffsapi.com/api/v1/tariffs/by_hts/${htsNumber}`,
  {
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY'
    },
    params: {
      origin: 'CN',
      include_unknown_effective_dates: true
    }
  }
);

console.log(response.data);

Note: The response includes detailed information about base duties, additional measures, and provides full transparency about data sources and inference confidence levels.

Newsletters API

Access curated trade newsletters and keyword-specific content.

GET /api/v1/newsletters

List all newsletters with pagination.

Example Response

{
  "newsletters": [
    {
      "id": 1,
      "title": "New Tariffs on Steel Products",
      "summary": "The U.S. announces new tariff rates...",
      "content": "Full content here...",
      "keywords": ["steel", "tariffs", "trade"],
      "published_at": "2025-01-15T10:00:00Z",
      "url": "https://tariffsapi.com/news/1"
    }
  ],
  "pagination": {
    "current_page": 1,
    "total_pages": 10,
    "total_count": 100,
    "per_page": 10
  }
}
GET /api/v1/newsletters/recent

Get the most recent newsletters (last 30 days).

Example Request

const axios = require('axios');

const response = await axios.get(
  'https://tariffsapi.com/api/v1/newsletters/recent',
  {
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY'
    }
  }
);

console.log(response.data);
GET /api/v1/newsletters/keywords

Get all available keywords for filtering newsletters.

Example Request

const axios = require('axios');

const response = await axios.get(
  'https://tariffsapi.com/api/v1/newsletters/keywords',
  {
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY'
    }
  }
);

console.log(response.data);

MCP Server Integration

Developer New

Connect your AI tools directly to live tariff data using the Model Context Protocol (MCP). The MCP server exposes the same tariff resolution capabilities as the REST API, but as tool calls that AI assistants like Claude, Cursor, and other MCP-compatible clients can invoke natively.

Paid plan required. MCP server access is included on the Developer plan ($199/month) and on Enterprise. See plans to get started.

Connection Details

Transport Endpoint Method
Streamable HTTP https://tariffsapi.com/api/v1/mcp POST Recommended
SSE (Legacy) https://tariffsapi.com/api/v1/mcp/sse GET

Streamable HTTP is recommended for all new integrations. It uses stateless POST requests with no persistent connections or keepalive overhead. SSE is kept for backward compatibility but maintains a persistent connection that consumes resources when idle.

Authentication Bearer token (your API key)
Rate Limit 500 requests/minute (Pro)

Available Tools

The MCP server exposes five tools that AI assistants can call:

resolve_tariff

Resolve complete tariff information for an HTS code and origin country. Returns base tariff rates (general/special) and additional measures (Section 301, 232, 338).

Parameters: hts (required), origin (required), as_of, include_unknown_effective_dates
batch_resolve_tariffs

Resolve tariffs for multiple HTS codes and origin countries in a single call (up to 200 items).

Parameters: requests (required, array), as_of, include_unknown_effective_dates
search_products

Keyword search over the published HTS schedule: matches your term against official code names and descriptions and returns the codes it hit. Schedule navigation, not classification — the code you file is yours and your broker's to determine.

Parameters: query (required), limit, has_tariffs
get_by_hts

Get detailed product and tariff information for a specific HTS code, including resolution metadata and base duties.

Parameters: hts_number (required), origin, as_of
list_agreements

List all trade agreements (USMCA, FTAs, GSP, etc.) with their codes, names, and descriptions.

Parameters: code (optional filter)

Setup Guide

Configure the MCP server in your AI client. Replace YOUR_API_KEY with a key from your API Keys dashboard.

Add to your claude_desktop_config.json file:

{
  "mcpServers": {
    "tariffs-api": {
      "url": "https://tariffsapi.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}
Config file location:
  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Example Usage

Once connected, your AI assistant can answer tariff questions naturally. Here are some example prompts:

Prompt
"What's the tariff rate for importing solar panels (HTS 8541.10.00) from China?"
Prompt
"Compare duty rates for steel coils from Canada vs Mexico vs China."
Prompt
"Search for HTS codes related to lithium-ion batteries."
Prompt
"Which trade agreements apply to imports from Australia?"

Verify Your Connection

Test that the MCP server is reachable with a simple cURL request:

curl -X POST "https://tariffsapi.com/api/v1/mcp" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{}}}'

A successful request returns a JSON-RPC response with the server's capabilities. If you receive a 403 response, verify that your API key belongs to a Pro subscription account.

Troubleshooting

Error Cause Solution
401 Missing or invalid API key Check your API key in the Authorization header
403 Endpoint not included on your plan Upgrade
429 Rate limit exceeded Wait for the Retry-After period, then retry
Connection refused Wrong endpoint URL Ensure you're using https://tariffsapi.com/api/v1/mcp (Streamable HTTP)
High request count Using legacy SSE transport Switch to Streamable HTTP — SSE keepalive pings inflate request counts

Error Handling

The API uses standard HTTP status codes and returns detailed error messages in JSON format.

Code Description
200 Success - Request completed successfully
400 Bad Request - Invalid parameters
401 Unauthorized - Invalid or missing API key
404 Not Found - Resource doesn't exist
429 Too Many Requests - Rate limit exceeded
500 Server Error - Something went wrong on our end

Error Response Format

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key provided",
    "details": "The API key is either missing, invalid, or has been revoked"
  }
}

Code Examples

Integration examples with error handling and response validation.

Integration Best Practices

Use match_type and confidence.score to determine how to display results:

  • • EXACT: Show with full confidence
  • • PREFIX_MATCH: Note statistical suffix applied
  • • PARENT_INHERITANCE: Show warning about inherited duties
  • • NOT_FOUND: Show error, check normalization.attempted_candidates

Always log request_id and check warnings[].path to identify field issues.

Integration Best Practices

Use match_type and confidence.score to determine how to display results:

  • • EXACT: Show with full confidence
  • • PREFIX_MATCH: Note statistical suffix applied
  • • PARENT_INHERITANCE: Show warning about inherited duties
  • • NOT_FOUND: Show error, check normalization.attempted_candidates

Always log request_id and check warnings[].path.

JavaScript (Node.js) - Basic Usage

const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://tariffsapi.com/api/v1';

async function resolveTariff() {
  try {
    const response = await axios.get(`${BASE_URL}/tariffs/resolve`, {
      headers: {
        'Authorization': `Bearer ${API_KEY}`
      },
      params: {
        hts: '8541.10.00.80',
        origin: 'CN',
        resolve_chapter_99: true
      }
    });
    console.log(response.data);
  } catch (error) {
    console.error('Error:', error.response.data);
  }
}

resolveTariff();

JavaScript - Production Integration

async function resolveTariffWithHandling(hts, origin) {
  try {
    const response = await axios.get(`${BASE_URL}/tariffs/resolve`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` },
      params: { hts, origin, resolve_chapter_99: true }
    });
    
    const data = response.data;
    
    // Log request ID for debugging
    console.log(`[${data.request_id}] Resolving ${hts} from ${origin}`);
    
    // Handle match types
    const { match_type, match_level, resolved_hts_digits } = data.resolution;
    
    if (match_type === 'EXACT') {
      console.log(`✓ Exact match (${match_level} digits): ${resolved_hts_digits}`);
    } else if (match_type === 'PREFIX_MATCH') {
      console.log(`⚠ Statistical suffix applied: ${resolved_hts_digits}`);
    } else if (match_type === 'PARENT_INHERITANCE') {
      console.warn(`⚠ Inherited from parent (requested ${data.resolution.requested_level}, matched ${match_level})`);
    } else if (match_type === 'NOT_FOUND') {
      console.error(`✗ HTS not found. Attempted: ${data.normalization.attempted_candidates.map(c => c.candidate).join(', ')}`);
      return null;
    }
    
    // Check confidence and warnings
    if (data.confidence.score < 1.0) {
      console.warn(`Confidence: ${data.confidence.score} (${data.confidence.method})`);
      data.warnings.forEach(w => {
        console.warn(`  [${w.severity}] ${w.code} @ ${w.path}: ${w.message}`);
      });
    }
    
    // Verify resolved_hts_digits length matches match_level
    if (resolved_hts_digits && resolved_hts_digits.length !== match_level) {
      console.error('Schema violation: digit length mismatch');
    }
    
    return {
      rate: data.summary.applicable_ad_valorem_rate,
      measures: data.additional_measures,
      confidence: data.confidence.score,
      requestId: data.request_id
    };
    
  } catch (error) {
    console.error('API Error:', error.response?.data || error.message);
    throw error;
  }
}

Python - Production Integration

import requests
from typing import Optional, Dict, Any

API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://tariffsapi.com/api/v1'

def resolve_tariff_with_handling(hts: str, origin: str) -> Optional[Dict[str, Any]]:
    """Resolve tariff with comprehensive error handling"""
    try:
        response = requests.get(
            f'{BASE_URL}/tariffs/resolve',
            headers={'Authorization': f'Bearer {API_KEY}'},
            params={'hts': hts, 'origin': origin, 'resolve_chapter_99': True}
        )
        
        if response.status_code != 200:
            print(f"Error {response.status_code}: {response.json()}")
            return None
            
        data = response.json()
        
        # Log request ID for tracing
        print(f"[{data['request_id']}] Resolved {hts} from {origin}")
        
        # Handle match types
        match_type = data['resolution']['match_type']
        match_level = data['resolution']['match_level']
        resolved_digits = data['resolution']['resolved_hts_digits']
        
        if match_type == 'EXACT':
            print(f"✓ Exact match ({match_level} digits): {resolved_digits}")
        elif match_type == 'PREFIX_MATCH':
            print(f"⚠ Statistical suffix applied: {resolved_digits}")
        elif match_type == 'PARENT_INHERITANCE':
            requested = data['resolution']['requested_level']
            print(f"⚠ Inherited from parent (requested {requested}, matched {match_level})")
        elif match_type == 'NOT_FOUND':
            candidates = data['normalization']['attempted_candidates']
            print(f"✗ Not found. Tried: {[c['candidate'] for c in candidates]}")
            return None
        
        # Verify length guarantee
        if resolved_digits and len(resolved_digits) != match_level:
            print(f"⚠ Schema violation: digit length mismatch")
        
        # Process warnings with path context
        for warning in data['warnings']:
            print(f"[{warning['severity']}] {warning['code']} @ {warning['path']}: {warning['message']}")
        
        # Check confidence
        confidence = data['confidence']
        if confidence['score'] < 1.0:
            print(f"Confidence: {confidence['score']} ({confidence['method']})")
            for factor in confidence['factors']:
                print(f"  {factor['factor']}: {factor['score']} - {factor['reason']}")
        
        return {
            'rate': data['summary']['applicable_ad_valorem_rate'],
            'confidence': confidence['score'],
            'request_id': data['request_id']
        }
        
    except requests.RequestException as e:
        print(f"Request failed: {e}")
        return None

# Example usage
result = resolve_tariff_with_handling('8541.10.00.80', 'CN')
if result:
    print(f"Final rate: {result['rate']}% (confidence: {result['confidence']})")

Ruby

require 'net/http'
require 'json'

API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://tariffsapi.com/api/v1'

params = {
  hts: '8541.10.00.80',
  origin: 'CN',
  resolve_chapter_99: true
}
query = URI.encode_www_form(params)
uri = URI("#{BASE_URL}/tariffs/resolve?#{query}")
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer #{API_KEY}"

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

if response.code == '200'
  data = JSON.parse(response.body)
  puts data
else
  puts "Error: #{response.code}"
  puts response.body
end

PHP

<?php

$apiKey = 'YOUR_API_KEY';
$baseUrl = 'https://tariffsapi.com/api/v1';

$params = http_build_query([
    'hts' => '8541.10.00.80',
    'origin' => 'CN',
    'resolve_chapter_99' => true
]);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '/tariffs/resolve?' . $params);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $apiKey
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpCode === 200) {
    $data = json_decode($response, true);
    print_r($data);
} else {
    echo "Error: $httpCode\n";
    echo $response;
}

?>

Support

Need help with the API?

Documentation

This page contains comprehensive documentation for all API endpoints.

API Keys

Manage your API keys and monitor usage.

Manage Keys

Email Support

support@tariffsapi.com

Latest News

Stay updated with the latest duty and tariff changes.

View News

Response Fields Reference

Request Tracking

request_id string

Unique identifier for tracing (format: req_[16 hex]).

as_of date

Effective date for tariff resolution (ISO 8601).

Resolution Metadata

resolution.requested_hts string

Original HTS code as provided in the request (preserves formatting).

resolution.resolved_hts string

Matched HTS code in dot format (e.g., "8541.10.00"). Primary display field.

resolution.resolved_hts_digits string

Matched HTS in digits-only format (e.g., "854110"). Use for database lookups and integrations.

Length always equals match_level. null if not found.

resolution.match_type enum

How the HTS match occurred (strict enum for machine parsing):

Value Meaning Confidence
"EXACT" Perfect match at requested specificity 1.0
"PREFIX_MATCH" Match after applying US .00 statistical suffix (8→10 digits) 0.95
"PARENT_INHERITANCE" Parent HTS fallback (duty inheritance from less specific code) 0.8
"NOT_FOUND" No match at any level (4/6/8/10 digits attempted) 0.0
resolution.match_level integer

Specificity level of matched HTS (4, 6, 8, or 10 digits). null if not found.

resolution.requested_level integer

Specificity level of requested HTS. Compare with match_level to detect inheritance.

resolution.origin string

ISO-2 country code (e.g., "CN", "MX").

resolution.as_of date

Effective date for tariff resolution (ISO 8601 format).

Base Tariff

base_tariff.type enum

Tariff type: "general" (MFN), "special" (FTA), or null (not found).

base_tariff.rate_type enum

Rate structure: "ad_valorem", "specific", or "compound".

base_tariff.percentage_component float

Ad valorem rate as decimal (e.g., 0.025 = 2.5%). null for purely specific duties.

base_tariff.per_unit_component float

Specific duty amount per unit (e.g., 0.52 USD/kg). null for ad valorem only.

base_tariff.unit_type string

Unit for specific duties (e.g., "kg", "liter", "m2").

base_tariff.agreement object

Trade agreement info if special rate applies: { code, name }. Examples: "S" (USMCA), "AU" (Australia FTA).

AD/CVD scope

adcvd_status enum

"screened" or "not_modeled". In both, antidumping and countervailing duties are not part of any rate, total or measure in this response. On a lane an AD/CVD order covers, the duty owed is higher than the total shown. "screened" means every AD/CVD order we track had been checked against its scope and the feed was current. It does not say whether an order covers the line, and it is not a clearance. "not_modeled" means the feed was not current or some order had not been checked yet, so no screen is claimed. Present on every resolve and on the top level of every batch response. New values may be added later.

adcvd_notice string

The same statement in words, suitable to show next to a rate: "Antidumping and countervailing duty (AD/CVD) orders are not included. If your goods may fall under one, check with a licensed customs broker."

Additional Measures

additional_measures[] array

Additional duty measures applicable to this HTS and origin on the resolved date. Ended programs such as ieepa appear only for a historical as_of.

.program enum

Program: "section_301", "section_232", "section_122", "section_338", "ieepa", "unknown_additional_duty", "adcvd". "adcvd" is reserved and not emitted today: AD/CVD is not modelled (see adcvd_status).

.rate_type enum

Rate structure: "ad_valorem", "specific", "compound", "reference", or "restriction".

.chapter_99_code string

Chapter 99 code for this measure (e.g., "9903.88.01"). Use with resolve_chapter_99=true to get numeric rate.

.effective_date_unknown boolean

true if effective date is uncertain (impacts confidence score).

Measures Application

measures_application enum

How multiple measures interact:

  • "STACKING" - Measures apply cumulatively (e.g., Section 301 + 232)
  • "ALTERNATIVE" - Measures are mutually exclusive options
  • "UNKNOWN" - Semantics unclear (triggers warning)

Conditional rates

Some rates cannot be settled from an HTS code, an origin and a date alone: they turn on a fact only the entry knows. Where that happens we serve one branch and describe the other here, rather than picking silently. rate_conditional is always present and is null on almost every lane.

rate_conditional.reason enum
  • "ad_valorem_equivalent" - A Section 301 floor or cap sits on a column 1 rate that is not ad valorem. U.S. note 52(k) makes the test a percentage of your declared customs value, so two entries of the same code at different values fall on opposite sides of the floor.
  • "tariff_rate_quota" - In-quota versus over-quota. No lane emits this today.
  • "scope_attribute" - The rate turns on a physical or end-use fact about the article that the HTS code, the country of origin and the date of entry do not determine. Section 232 on unmanned aircraft systems is the current example: U.S. note 43 charges the same six 8806 subheadings +100% with thermal imaging and +25% without, and charges the enumerated 8807 parts provisions only where the article is for use in or with a UAS. Where every article on a subheading is in scope and only the branch is unknown, served is null and no single total is stated; where the ordinary population is out of scope, served names the no-change lane and the total stands. Section 232 on pharmaceuticals is the second example, from September 29, 2026, with depends_on: "patent_status": on every provision listed in U.S. note 40(c) the duty turns on whether the article is a patented pharmaceutical (headings 9903.04.60, 100% including column 1; 9903.04.62, 15% including column 1 for Japan, the EU, South Korea, Switzerland and Liechtenstein; 9903.04.63, +0% for the United Kingdom) or a generic one (9903.04.67, no additional duty). alternatives carries a patented and a generic branch, each with the Section 301 duties it owes or is relieved of, because some reliefs apply only to patented articles. served is null and no single total is stated, except where both branches owe the same duties on a plain percentage column 1 rate: then served is "patented", the total is stated, and the block stays attached so you can see which heading each case enters under.
  • "scope_varies_by_statistical_suffix" - The rate turns on which ten-digit statistical reporting number the article is entered under, and the request named a shorter code whose children carry different rates. The current example is a Section 338 list that keeps some statistical lines of an eight-digit subheading and drops the others. served is null, no single total is stated, and alternatives names each ten-digit line with its rate. Request the ten-digit code for a total. The same value also appears in summary.total_declined_reason, with the warning SCOPE_VARIES_BY_STATISTICAL_SUFFIX and no rate_conditional block, on a shorter request whose ten-digit children disagree: on a six-digit or shorter code whenever its children carry different additional duties, and on an eight-digit code when one of its children owes an additional duty the eight-digit line does not.
  • "conflicting_authority" - The governing instruments and CBP guidance disagree on whether a duty applies to this article. The current example is the Section 232 Russia aluminum duty (9903.85.68, +200%) on an auto part that also carries the Section 232 auto-parts duty: Executive Order 14289 and the HTSUS notes say the two stack, CBP CSMS #65054270 says auto parts are exempt from the aluminum duties. served is null, no single total is stated, and alternatives carries both readings with their sources (russia_200_stacks and russia_200_relieved). The same value appears in summary.total_declined_reason. What to do: we cannot tell you which reading applies to your entry; check both, and ask your customs broker or CBP before you file.
  • "import_prohibited" - The goods are excluded from importation by Presidential proclamation from an effective date, so no duty is payable on an entry the law does not permit. The total is null rather than a number. The trigger is the date of IMPORTATION while as_of is the date of ENTRY, so goods imported before the effective date and entered after it remain dutiable; alternatives names the prohibited branch (carrying "prohibited": true and no rate) alongside the branches that still owe duty. Where the proclamation's annex limits the ban to part of a subheading (the Section 338 Canada alcohol lines marked "Packaged"), a third branch carries the rate for product outside that scope. See the top-level import_prohibition block for the proclamation, its Federal Register citation and the surviving duty. served is null.
  • "content_origin" - The rate turns on where the metal content was MADE, not on the country of origin declared on the entry. Section 232 on the United Kingdom metals lanes is the current example: U.S. note 16(d) gives headings 9903.82.04 (+25%) and 9903.82.05 (+15%) only to articles of the UK in which at least 95 percent of the steel was melted and poured in the UK, or at least 95 percent of the aluminum was smelted or most recently cast there. Country of melt and pour is reported to CBP as entry data and is not an input to this API, so served names the UK lane, the total stands, and alternatives states the heading and rate that apply if the metal was made elsewhere. Note 16(d) states no basis of measurement for the 95 percent, so we state none.
  • "preference_claim" - The rate turns on whether the entry claims a trade-agreement preference, which is a fact of the entry and not of the HTS code or origin. Section 232 on auto parts of Canada and Mexico is the current example: under U.S. note 33(h)(i) a part entered with a USMCA claim takes heading 9903.94.06 with no Section 232 addition, and without the claim heading 9903.94.05 adds 25% on top of column 1 General. A claimed knock-down kit or parts compilation stays on 9903.94.05. served is the branch an ordinary entry owes, and the base rate we serve is that branch's base. A USMCA claim is optional, so on a line with a USMCA special rate we serve "usmca_claimed" unless not claiming owes less (an auto part that is also a steel or aluminum derivative, where the claim brings the metals duty back), and "usmca_not_claimed" on the General base there and on every line without a USMCA rate. The total stands. Because the branch also changes the base rate, each alternative carries its own base_rate, total, and the other duties it owes (measures) or is relieved of (suppressed_measures): a part that is also a steel or aluminum derivative owes the metals duty only on the claimed branch.
.depends_on / .resolvable_from_request string / boolean

What the answer turns on, and whether you could resolve it by sending more on the request. depends_on values include declared_customs_value, quota_fill_at_entry, thermal_imaging, end_use, metal_melt_and_pour_origin, statistical_reporting_number, stacking_reading, date_of_importation, date_of_importation_and_packaging and patent_status. resolvable_from_request is currently always false: there is no request parameter that settles it yet.

.required_inputs array, ad_valorem_equivalent only

Every value an importer would have to supply to turn the bound into an exact rate, as { name, unit }. For a column 1 rate of $1.24/t that is the customs value and the entered quantity in tonnes, because the ad valorem equivalent is the duty payable divided by the customs value, and the duty payable is the per-unit rate times the quantity. A small number of lines add a basis only you hold, such as the jewel count on a watch movement or the lead content of an alloy; those can never be settled from HTS code and origin alone. This field names the inputs; it does not accept them, so resolvable_from_request stays false.

.served string

Which alternative the headline rate reflects. The headline rate itself is unchanged by this block.

.alternatives[] array

Each carries case, condition, rate, rate_expression, authority, and the program identity (chapter_99_code, program, program_detail) so you can link straight to the heading. program_detail may be null.

.alternatives[].measures / .suppressed_measures / .restriction_description array / array / string, patent_status only

On a patent_status lane each branch also lists the other duties it owes (measures, same shape as additional_measures) and the ones it is relieved of (suppressed_measures, with the exempting heading). A duty that turns on the branch is left out of the top-level additional_measures and listed in the top-level suppressed_measures with a reason pointing here. A branch total is column 1, plus the branch rate, plus its measures. The patented branch also carries restriction_description, the heading's own text, which names the further reliefs an importer may claim (9903.04.66, 9903.04.69, 9903.04.70).

.alternatives[].rate_is_bound enum

"upper", "lower" or "exact". Read this before totalling. On an ad_valorem_equivalent lane the served rate is an "upper" bound: where column 1 is a specific rate we net out nothing and charge the full floor, which over-states the duty. Reading only applicable_ad_valorem_rate gives you that ceiling with no indication it is one.

.quota object

Present on tariff_rate_quota only: period, period_start, period_end, quantity, quantity_unit. fill_status is always "unknown" and carries a fill_status_reason — live quota fill is decided at entry by CBP and is not a feed we ingest. We will not estimate it.

confidence.factors[] note

A conditional rate scores 0.8 through a factor named rate_conditional, listed beside base_rate rather than folded into the total, so you can see what cost you the reduction. The TARIFF_RATE_QUOTA_DIRECTED case is the exception and does not reduce confidence: the rate in force is correct.

Warnings

warnings[] array

Array of warnings about data quality, match type, or resolution issues.

.code enum

Machine-readable warning code:

  • "HTS_INHERITED" - Used parent HTS (not exact match)
  • "HTS_PREFIX_MATCH" - Applied .00 suffix per US convention
  • "MEASURE_SEMANTICS_UNKNOWN" - Unclear how measures combine
  • "EFFECTIVE_DATE_UNKNOWN" - Some measures have unknown dates
  • "AD_VALOREM_EQUIVALENT_REQUIRED" - Rate depends on your entry's declared customs value; served rate is an upper bound
  • "QUOTA_DEPENDENT" - Rate depends on quota fill at time of entry
  • "SCOPE_ATTRIBUTE_REQUIRED" - Rate depends on a physical or end-use fact about the article that the HTS code, origin and date do not determine
  • "CONTENT_ORIGIN_REQUIRED" - Rate depends on where the metal content was melted and poured, or smelted or most recently cast, which the declared country of origin does not establish
  • "SCOPE_VARIES_BY_STATISTICAL_SUFFIX" - The requested code is shorter than ten digits and its statistical reporting numbers carry different rates; request the ten-digit code for a single total
  • "PREFERENCE_CLAIM_REQUIRED" - Rate depends on whether the entry claims a trade-agreement preference (the USMCA on Canadian and Mexican auto parts); rate_conditional.alternatives states each branch with its base rate and total
  • "CONFLICTING_AUTHORITY" - The governing instruments and CBP guidance disagree on whether a duty applies; no single total is stated and rate_conditional.alternatives carries both readings
  • "IMPORT_PROHIBITED" - Importation of this good from this origin is excluded by proclamation on and after the effective date. Severity error, not warning: this is not a hedge on a number we are still serving. No total is stated.
  • "TARIFF_RATE_QUOTA_DIRECTED" - A quota is directed but not yet established; today's rate is correct (info only, no confidence reduction)
  • "KNOWN_DATA_DEFECT" - We have an open defect on record for this HTS/origin; the returned duty may be wrong. Removed once the defect is fixed
.severity enum

"info", "warning", or "error".

.path string

JSON path to the related response field (e.g., "resolution.match_type").

.details object

Context-specific details (e.g., requested_level, matched_level).

Confidence Scoring

confidence.score float

Overall confidence (0.0 to 1.0). Calculated by multiplying factor scores.

score = hts_match × measure_semantics × data_completeness × base_rate × rate_conditional × known_regression
confidence.method string

Method used to calculate confidence: "multiplicative" (factors are multiplied together).

confidence.factors[] array

Breakdown of confidence factors:

  • hts_match: 1.0 (exact), 0.95 (with suffix), 0.8 (inherited), 0.0 (not found)
  • measure_semantics: 1.0 (clear), 0.7 (unknown)
  • data_completeness: 1.0 (complete), 0.95 (some effective dates unknown), 0.9 (a measure is flagged uncertain: low-confidence parse or bare-heading match)
  • base_rate: 1.0 (resolved), 0.5 (base duty not machine-readable, or defers to another heading)
  • rate_conditional: 1.0, or 0.8 when the served rate is one branch of a fact only known at entry (quota fill, ad valorem equivalent, a physical or end-use attribute of the article, where the metal content was made, patent status, whether the entry claims a trade-agreement preference). A patent_status lane where both branches owe the same total scores 1.0.
  • known_regression: 1.0, or 0.8 when this HTS/origin carries a REGRESSION_KNOWN_FAILURE warning (our regression corpus currently flags the lane) or a KNOWN_DATA_DEFECT warning (we have an open defect on record for it); 0.8 either way, never both

A score of 1.0 means no factor found a reason to doubt the answer. Any measure with uncertain: true, and any lane with a known-regression warning, now scores below 1.0 (since 2026-09-08).

Normalization

normalization.input string

Original HTS input (mirrors requested_hts).

normalization.normalized string

Normalized digits-only version (dots/spaces/dashes removed).

normalization.attempted_candidates[] array

All HTS codes attempted during resolution. Each entry: { candidate, level, matched }.

Useful for debugging why a match succeeded or failed.

Summary

summary.applicable_ad_valorem_rate float

Total ad valorem rate as percentage (e.g., 25.0 = 25%). Includes base + additional ad valorem measures.

summary.notes[] array

Human-readable breakdown of tariff components (base duty, Section 301, etc.).

summary.resolved_additional_ad_valorem_rate optional

Only present when resolve_chapter_99=true. Sum of resolved Chapter 99 ad valorem rates.

summary.total_resolved_ad_valorem_rate optional

Only present when resolve_chapter_99=true. Total: base + resolved additional measures.

Example Scenarios

Exact Match (Confidence: 1.0)
{
  "request_id": "req_a1b2c3d4e5f6g7h8",
  "as_of": "2026-10-15",
  "resolution": {
    "match_type": "EXACT",
    "match_level": 10,
    "requested_level": 10,
    "resolved_hts_digits": "8541100000"
  },
  "confidence": {
    "score": 1.0,
    "method": "multiplicative",
    "factors": [
      { "factor": "hts_match", "score": 1.0, "reason": "Perfect match" }
    ]
  },
  "warnings": []
}
Inherited Match (Confidence: ~0.76)
{
  "request_id": "req_b2c3d4e5f6g7h8i9",
  "as_of": "2026-10-15",
  "resolution": {
    "requested_hts": "8541.10.00.80",
    "resolved_hts": "8541.10.00",
    "resolved_hts_digits": "854110",
    "match_type": "PARENT_INHERITANCE",
    "match_level": 6,
    "requested_level": 10
  },
  "confidence": {
    "score": 0.76,
    "method": "multiplicative",
    "factors": [
      { "factor": "hts_match", "score": 0.8, "reason": "Inherited from parent" },
      { "factor": "measure_semantics", "score": 1.0 },
      { "factor": "data_completeness", "score": 0.95 }
    ]
  },
  "warnings": [
    {
      "code": "HTS_INHERITED",
      "severity": "warning",
      "path": "resolution.match_type",
      "details": { "requested_level": 10, "matched_level": 6 }
    }
  ]
}
Not Found (Confidence: 0.0)
{
  "request_id": "req_c3d4e5f6g7h8i9j0",
  "as_of": "2026-10-15",
  "resolution": {
    "match_type": "NOT_FOUND",
    "match_level": null,
    "requested_level": 10,
    "resolved_hts_digits": null
  },
  "base_tariff": { "type": null },
  "confidence": {
    "score": 0.0,
    "method": "multiplicative"
  },
  "warnings": [
    {
      "code": "HTS_NOT_FOUND",
      "severity": "error",
      "path": "resolution.match_type"
    }
  ],
  "normalization": {
    "attempted_candidates": [
      { "candidate": "9999999999", "level": 10, "matched": false },
      { "candidate": "99999999", "level": 8, "matched": false },
      { "candidate": "999999", "level": 6, "matched": false },
      { "candidate": "9999", "level": 4, "matched": false }
    ]
  }
}

Batch Resolution Fields

meta.request_id string

Batch-level request ID (format: batch_[16 hex]).

results[].request_id string

Per-item request ID for tracing individual results within the batch.

results[].ok boolean

true if resolution succeeded, false if error occurred.

results[].error.details object

When ok=false, includes requested_hts and attempted_candidates for debugging.

results[].outcome string

On every item. resolved (a rate was stated), resolved_without_total (ok=true, and there is no rate in it — a declined total, or no base duty at the length you asked for), or rejected (ok=false). ok alone does not tell you whether you got a number.

results[].reason object

Present on anything that is not resolved. Carries code, category, caller_action and a plain-language explanation. category is unusable_input (not a code, not a country, not a date), unknown_line (well-formed, and we hold no such line) or declined (we identified the line and did not price it). caller_action is fix_input or contact_support, so a catalogue run can be sorted into what you can fix and what to ask us about. When code is total_declined, the block also carries declined_reason, the same value as summary.total_declined_reason. import_prohibited there means the entry is excluded from importation by proclamation; see the item's import_prohibition.

{
  "index": 3,
  "ok": false,
  "outcome": "rejected",
  "reason": {
    "code": "hts_chapter_not_in_schedule",
    "category": "unusable_input",
    "caller_action": "fix_input",
    "explanation": "\"0002.19.71.10\" begins with chapter 00, and no line in the tariff schedule is in that chapter."
  },
  "error": { "code": "HTS_NOT_FOUND", "message": "..." }
}