@trustrails/sdk
v0.6.0
Published
Official TypeScript SDK for TrustRails API
Maintainers
Readme
TrustRails TypeScript SDK
Official TypeScript/JavaScript client library for the TrustRails API.
Installation
npm install @trustrails/sdkQuick Start
import TrustRails from '@trustrails/sdk';
// Initialize the SDK (baseUrl defaults to production)
const trustrails = new TrustRails('your-api-key');
// Search for products with filters
const results = await trustrails.search({
brand: 'HP',
category: 'Laptops',
minPrice: 400,
maxPrice: 600,
});
console.log(`Found ${results.total} products`);
results.products.forEach(product => {
console.log(`${product.title} - £${product.price}`);
});
// Get a specific product
const product = await trustrails.product('product-id');
console.log(product);API Reference
TrustRails
Constructor
new TrustRails(apiKey: string)Or use a config object for custom baseUrl:
new TrustRails({ apiKey: string, baseUrl?: string })Parameters:
apiKey- Your TrustRails API keybaseUrl(optional) - Custom base URL (defaults tohttps://trustrails.app)
Methods
search(options?: SearchOptions): Promise<SearchResponse>
Search for products. With lite: true the products are trimmed (SearchResponse<LiteProduct>). Search results leave out specs and delivery_time, but carry every known attributes spec, so compare specs from the search results (a missing name is unknown, conflicting means retailers disagree). Call product(id) only for your final 1-3 picks, and only for what search lacks: the retailer's description (processor, GPU, ports, weight, battery; if it doesn't give a detail, it isn't listed), or every retailer's offer and buy link (search carries only the best offer's purchase_url, cheapest in stock). State the value of a confirmed or inferred attribute; only conflicting or a missing name needs a caveat. See What each call returns.
Parameters:
options.query?: string- Refinement terms only — model lines, series, variants, model numbers (e.g.'neo','ultra','oled','WH-1000XM5'). Never put category names here (query='tablet'→ usecategory='Tablets'). Never put brand names or prices — use filters. Omit entirely when browsing a category or brand. Query words must appear in the title, except words naming the category ('router'in Networking) and bare numbers or specs ('4070','16GB'), which are ignored when finding products and only rank them: put a model number with its prefix ('RTX 4070', not'4070') and check each title for it.options.brand?: string- Filter by brand name (exact match, case-insensitive), e.g. 'Sony', 'HP', 'Anker'options.category?: string- Filter by product category. Use only these exact values: Laptops, Desktops, Tablets, Phones, TVs, Monitors, Headphones, Speakers, Cameras, Keyboards, Mice, Printers, Networking, Storage, Gaming, Wearables, Drones, Audio, Cables & Chargers. 'Smartphones' is not valid (use 'Phones'), nor is 'Televisions' (use 'TVs')options.minPrice?: number- Minimum price filter in GBPoptions.maxPrice?: number- Maximum price filter in GBPoptions.constraints?: Constraints- Hard spec requirements, checked per product against itsattributes, e.g.{ memory_gb: { gte: 24 }, storage_gb: { gte: 1000 }, screen_in: { eq: 15 } }. Operators:eq,gte,lte; a range is{ gte, lte }. Names and units:memory_gb(RAM, GB),storage_gb(GB, 1TB = 1000),screen_in(inches),resolution_p(pixels high: 4K = 2160, QHD = 1440, Full HD = 1080),refresh_hz(Hz),power_w(W),wifi_gen(Wi-Fi generation: 6, 6E = 6.5, 7). On every operator, storage matches within 3% (1TB = 1000–1024GB, sogte 1024accepts a 1TB drive but not 960GB) and screen size within 0.5 inch; a whole-number screen size N also covers up to N+1 oneqandlte(eq 13accepts 13.6",lte 15accepts 15.6"); other specs exactly. Each product then hasconstraint_statusper name:matched(a retailer's title states a value that meets it), orunverified(not known to meet it, never treat as a match: inattributes[name],conflictingmeans retailers disagree and missing means unknown). Products whose stated value fails are left out and counted inexcluded_by_constraints. Wins over the same spec written inquery, where only a spec that says what it is counts (24GB RAM,1TB; a bare24GBdoes not) and means at least, except screen size. A plain16GBor65Wisgte; useeqonly when the user says exactly, and for screen size. Always pass acategoryorbrandwith constraints. A bad request (unknown name or operator, negative value, a combination nothing can meet such asgteabovelte, or constraints with no category, brand or search words) throws aTrustRailsErrorwithstatusCode400; the message naming the valid values is inerror.response, the raw JSON body.options.lite?: boolean- Return trimmed product objects (LiteProduct) with only essential fields (id, title, brand, price, currency, availability, image_url, purchase_url, offer_count), every knownattributesspec asstatusand value withoutsources, and, with constraints,constraint_statusoptions.limit?: number- Maximum number of results (default: 50, max: 100)options.sort?: string- Sort order:'relevance'(default),'price_asc'(in stock first, then cheapest),'price_desc'(in stock first, then most expensive). With constraints, matched products still come first.
Returns: Promise resolving to SearchResponse containing products array and total count, plus constraints (as applied), excluded_by_constraints and unverified_total whenever constraints applied, whether passed or written in query. With constraints, total counts the products that match every constraint and unverified_total the unverified products that passed the other filters (only some may be in products). candidates_truncated: true means the first 2,000 candidates in the chosen sort order were checked and more exist: add a brand or category, or a narrower query, and search again. If the search was already narrowed, the results may be incomplete.
Example — brand + category only (query omitted):
const results = await trustrails.search({
brand: 'HP',
category: 'Laptops',
minPrice: 400,
maxPrice: 1000,
sort: 'price_asc'
});Example — with model line in query:
// 'neo', 'ultra', 'oled' etc. go in query — they refine within brand/category
const results = await trustrails.search({
brand: 'Samsung',
category: 'TVs',
query: 'qled',
});Example — spec constraints:
// MacBooks whose titles state 24GB+ RAM and 1TB+ storage come back as 'matched'
const results = await trustrails.search({
brand: 'Apple',
category: 'Laptops',
maxPrice: 2000,
constraints: { memory_gb: { gte: 24 }, storage_gb: { gte: 1000 } },
});Lite mode (faster responses, smaller payloads):
const results = await trustrails.search({
brand: 'Anker',
category: 'Cables & Chargers',
lite: true // Returns id, title, brand, price, currency, availability, image_url, purchase_url, offer_count and attributes (without sources)
});product(id: string): Promise<Product>
Get full details for a single product. Returns attributes: structured specs read from retailer titles, each with its status and the retailers behind it (see Product). Also returns specs.description (the retailer's own prose, for processor, ports, graphics and anything attributes does not cover), availability, delivery time, and all retailer offers with per-retailer pricing. specs.description can describe another configuration or a maximum ("up to 32GB"), so it never overrides or fills in an attribute: a spec missing from attributes is unknown. Accepts canonical product IDs or original retailer offer IDs. Use this after search() only for your final 1-3 picks, and only for what search lacks (the description's details, or every offer and buy link): search() already carries attributes to compare specs.
Parameters:
id: string- The product ID
Returns: Promise resolving to Product object with all fields populated
Example:
const product = await trustrails.product('prod_123');What each call returns
| Call | Returns |
|------|---------|
| search({ lite: true }) (what agents should always set) | id, title, brand, price, currency, availability, image_url, purchase_url, offer_count, every known attributes spec as { status, value } (or { status, values } when conflicting) without sources, and constraint_status when constrained |
| search() | the lite fields plus ean, category, product_type, provenance and every attribute with its sources |
| product(id) | everything: specs.description and dimensions, offers, delivery_time |
A product with no attributes key has none of these seven specs known.
Types
Product
interface Product {
id: string;
ean?: string;
title: string;
brand?: string;
price: number;
currency: string;
availability: "in_stock" | "low_stock" | "out_of_stock" | "unknown";
delivery_time?: string; // product() only
image_url?: string;
category: string;
product_type: "product" | "accessory";
specs?: { // product() only
description?: string; // Retailer prose; never overrides attributes
model_number?: string;
dimensions?: string;
};
provenance: {
source: string;
last_updated: string;
};
purchase_url: string;
attributes?: Attributes; // structured specs (search results and product()), absent when none is known, see below
constraint_status?: Partial<Record<ConstraintName, "matched" | "unverified">>; // only when constraints applied
offer_count?: number; // number of retailer offers (when >1, call product() for your final picks to compare prices)
offers?: Offer[]; // per-retailer offers, in stock first then cheapest (returned by product())
}
interface Offer {
id: string;
source: string;
title: string;
price: number;
currency: string;
availability: "in_stock" | "low_stock" | "out_of_stock" | "unknown";
stock: number | null; // unit count only when the retailer supplies one
delivery_time: string;
purchase_url: string;
image_url?: string;
last_updated: string;
}Attributes
Structured specs read from retailer titles, by name. A name nobody states is absent (unknown).
confirmed: two or more retailers state the same value.inferred: one retailer's title states it.conflicting: retailers state different values; none is picked, so any constraint on it isunverified. A retailer can appear under two values, so checkoffers[].title.
type Attributes = Partial<Record<ConstraintName, Attribute>>;
type Attribute =
| { status: "confirmed" | "inferred"; value: number; sources: { retailer: string; field: "title" }[] }
| { status: "conflicting"; values: { value: number; sources: { retailer: string; field: "title" }[] }[] };
// An attribute without its sources, as a lite search result carries it
type LiteAttribute =
| { status: "confirmed" | "inferred"; value: number }
| { status: "conflicting"; values: { value: number }[] };
type LiteAttributes = Partial<Record<ConstraintName, LiteAttribute>>;
// memory_gb (RAM, GB), storage_gb (GB, 1TB = 1000), screen_in (inches), resolution_p (pixels high: 4K = 2160),
// refresh_hz (Hz), power_w (W), wifi_gen (Wi-Fi generation: 6, 6E = 6.5, 7)
type ConstraintName = "memory_gb" | "storage_gb" | "screen_in" | "resolution_p" | "refresh_hz" | "power_w" | "wifi_gen";
type Constraints = Partial<Record<ConstraintName, { eq?: number; gte?: number; lte?: number }>>;LiteProduct
A product in a lite: true search.
interface LiteProduct {
id: string;
title: string;
brand?: string;
price: number;
currency: string;
availability: "in_stock" | "low_stock" | "out_of_stock" | "unknown";
image_url?: string;
purchase_url: string;
offer_count: number;
constraint_status?: Partial<Record<ConstraintName, "matched" | "unverified">>; // only when constraints applied
attributes?: LiteAttributes; // every known spec, without sources; absent when none is known
}SearchOptions
interface SearchOptions {
query?: string;
brand?: string;
category?: string;
minPrice?: number;
maxPrice?: number;
constraints?: Constraints;
lite?: boolean;
limit?: number;
sort?: 'relevance' | 'price_asc' | 'price_desc';
}SearchResponse
interface SearchResponse<P = Product> { // SearchResponse<LiteProduct> when lite: true
products: P[];
total: number; // with constraints: the products that match every constraint
constraints?: Constraints; // as applied, when constraints applied
excluded_by_constraints?: number; // products left out because their stated value fails a constraint
unverified_total?: number; // with constraints: the unverified products that passed the other filters; only some may be in products
candidates_truncated?: boolean; // true when the first 2,000 candidates were checked and more exist
}Error Handling
The SDK throws TrustRailsError for API-related errors:
import TrustRails, { TrustRailsError } from '@trustrails/sdk';
const trustrails = new TrustRails('your-api-key');
try {
const product = await trustrails.product('invalid-id');
} catch (error) {
if (error instanceof TrustRailsError) {
console.error(`API Error: ${error.message}`);
console.error(`Status Code: ${error.statusCode}`);
}
}License
MIT
