Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3-beta] - 2026-04-17

### Added
- `searchProductCodes()` - Search for product codes (TICs) by natural language description, returning all matching Taxability Information Codes ranked and scored by relevance
- `recommendProductCode()` - Get an AI-powered product code (TIC) recommendation with higher accuracy than the standard search
- `ProductCodeSearchRequest`, `ProductCodeSearchResult`, `ProductCodeSearchResponse` types for TIC search
- `ProductCodeRecommendation`, `ProductCodeRecommendationResponse` types for TIC recommendation
- `validateProductQuery()` validation helper for product query inputs (non-empty, max 500 characters)

## [0.2.2-beta] - 2026-03-11

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ziptax/node-sdk",
"version": "0.2.2-beta",
"version": "0.2.3-beta",
"description": "Official Node.js SDK for the ZipTax API",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
71 changes: 71 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
validateRequired,
validateMaxLength,
validatePattern,
validateProductQuery,
parseAddressString,
} from './utils/validation';
import {
Expand All @@ -32,6 +33,9 @@ import {
UpdateOrderRequest,
RefundTransactionRequest,
RefundTransactionResponse,
ProductCodeSearchRequest,
ProductCodeSearchResponse,
ProductCodeRecommendationResponse,
} from './models';

/**
Expand Down Expand Up @@ -171,6 +175,73 @@ export class ZiptaxClient {
});
}

/**
* Search for product codes (TICs) by natural language description.
* Returns all matching Taxability Information Codes ranked and scored
* by relevance.
*
* Use the returned ticId as the taxabilityCode parameter in rate requests
* or cart line items. For v60 rate requests (e.g., getSalesTaxByAddress),
* pass ticId as a string. For cart line items, convert to number.
*
* @param query - Natural language product description
* (e.g., "baked goods sold in plastic packaging")
* @returns ProductCodeSearchResponse with ranked search results
*
* @example
* ```typescript
* const response = await client.searchProductCodes(
* "baked goods sold in plastic packaging"
* );
* for (const result of response.results) {
* console.log(`${result.ticId}: ${result.label} (score=${result.score})`);
* }
* ```
*/
async searchProductCodes(query: string): Promise<ProductCodeSearchResponse> {
validateProductQuery(query);

const reqBody: ProductCodeSearchRequest = { query };

return this.httpClient.post<ProductCodeSearchResponse>('/search/tic', reqBody);
}

/**
* Get an AI-powered product code (TIC) recommendation.
* Returns a single best-match TIC code with higher accuracy than
* searchProductCodes. Has slightly higher latency due to the AI
* processing step.
*
* Use the returned ticId as the taxabilityCode parameter in rate requests
* or cart line items. For v60 rate requests (e.g., getSalesTaxByAddress),
* pass ticId as a string. For cart line items, convert to number.
*
* @param query - Natural language product description
* (e.g., "baked goods sold in plastic packaging")
* @returns ProductCodeRecommendationResponse with AI-powered recommendation
*
* @example
* ```typescript
* const response = await client.recommendProductCode(
* "baked goods sold in plastic packaging"
* );
* const prediction = response.predictions[0];
* if (prediction.status === "success") {
* console.log(`Recommended TIC: ${prediction.ticId} (${prediction.label})`);
* }
* ```
*/
async recommendProductCode(query: string): Promise<ProductCodeRecommendationResponse> {
validateProductQuery(query);

const reqBody: ProductCodeSearchRequest = { query };

return this.httpClient.post<ProductCodeRecommendationResponse>(
'/search/tic/recommend',
reqBody
);
}

/**
* Calculate sales tax for a shopping cart.
*
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export type {
CartLineItemResponse,
CartItemResponse,
CalculateCartResponse,
ProductCodeSearchRequest,
ProductCodeSearchResult,
ProductCodeSearchResponse,
ProductCodeRecommendation,
ProductCodeRecommendationResponse,
} from './models';

// Export TaxCloud models
Expand Down
72 changes: 72 additions & 0 deletions src/models/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,3 +363,75 @@ export interface CalculateCartResponse {
/** Array of cart results (mirrors request items array order) */
items: CartItemResponse[];
}

// ---------------------------------------------------------------------------
// Product Code (TIC) Search Models
// ---------------------------------------------------------------------------

/**
* Request payload for product code search and recommendation endpoints
*/
export interface ProductCodeSearchRequest {
/** Natural language product description to search */
query: string;
}

/**
* A single product code search result ranked and scored by relevance
*/
export interface ProductCodeSearchResult {
/** Taxability Information Code. Use as the taxabilityCode parameter
* in rate requests or cart line items. */
ticId: string;
/** TIC label from the TIC data */
label: string;
/** Natural language label aligned with the full description */
naturalLabel: string;
/** Full description of the taxability code line item */
description: string;
/** Long-form documentation of the TIC code */
documentation: string;
/** Itemized rank for the result ("1" = best match) */
rank: string;
/** Confidence score ("0.0"-"1.0"), independent of rank */
score: string;
}

/**
* Response from the product code search endpoint
*/
export interface ProductCodeSearchResponse {
/** Original search query sent in the request */
query: string;
/** Matching product codes ranked by relevance */
results: ProductCodeSearchResult[];
}

/**
* A single AI-powered product code recommendation
*/
export interface ProductCodeRecommendation {
/** Prediction result status ("success" or "fail") */
status: string;
/** Non-null error message when the prediction fails.
* Only populated when status is "fail". */
error: string | null;
/** Recommended Taxability Information Code */
ticId: string;
/** TIC label from the TIC data */
label: string;
/** Natural language label aligned with the description */
naturalLabel: string;
/** Full description of the recommended TIC (snake_case to match API) */
tic_description: string;
/** Original product description sent in the query (snake_case to match API) */
product_description: string;
Comment on lines +425 to +428
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 ProductCodeRecommendation uses snake_case fields violating CLAUDE.md naming convention

The ProductCodeRecommendation interface introduces tic_description and product_description fields in snake_case. CLAUDE.md mandates under "Key Design Decisions" that the naming convention is "camelCase for all fields" and under "Important Type Conventions" that "ZipTax Responses: Use camelCase (e.g., baseRates, taxSummaries)". The only documented exception is Account Metrics (V60AccountMetrics), which is explicitly called out as using snake_case. These new endpoints go through the ZipTax HTTP client (this.httpClient at src/client.ts:206 and src/client.ts:239), making them ZipTax responses subject to the camelCase convention. Every other field in this same interface (ticId, naturalLabel) correctly uses camelCase, making the inconsistency within a single interface even more problematic.

Prompt for agents
The ProductCodeRecommendation interface at src/models/responses.ts:413-429 has two snake_case fields (tic_description and product_description) that violate the CLAUDE.md naming convention requiring camelCase for ZipTax response types. The only documented exception is V60AccountMetrics.

Two approaches to fix:
1. If the API actually returns snake_case for these fields, rename them to camelCase (ticDescription, productDescription) and add a response transformation layer, or update CLAUDE.md to document this as another exception.
2. If the API returns camelCase, simply rename the fields to ticDescription and productDescription.

Also update the test mock data in tests/client.test.ts (lines 486-488 and 540-542) to use the corrected field names.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}

/**
* Response from the product code recommendation endpoint
*/
export interface ProductCodeRecommendationResponse {
/** AI-powered product code recommendations */
predictions: ProductCodeRecommendation[];
}
20 changes: 20 additions & 0 deletions src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,23 @@ export function parseAddressString(address: string): ParsedAddress {
countryCode: 'US',
};
}

/**
* Validate product description query for TIC search endpoints.
*
* @param query - Natural language product description to validate
* @throws ZiptaxValidationError if query is empty, not a string, or exceeds 500 characters
*/
export function validateProductQuery(query: string): void {
if (typeof query !== 'string') {
throw new ZiptaxValidationError('Product query must be a string');
}

if (!query || !query.trim()) {
throw new ZiptaxValidationError('Product query cannot be empty');
}

if (query.length > 500) {
throw new ZiptaxValidationError('Product query exceeds maximum length of 500 characters');
}
}
Loading
Loading