npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@trustrails/sdk

v0.6.0

Published

Official TypeScript SDK for TrustRails API

Readme

TrustRails TypeScript SDK

Official TypeScript/JavaScript client library for the TrustRails API.

Installation

npm install @trustrails/sdk

Quick 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 key
  • baseUrl (optional) - Custom base URL (defaults to https://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' → use category='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 GBP
  • options.maxPrice?: number - Maximum price filter in GBP
  • options.constraints?: Constraints - Hard spec requirements, checked per product against its attributes, 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, so gte 1024 accepts 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 on eq and lte (eq 13 accepts 13.6", lte 15 accepts 15.6"); other specs exactly. Each product then has constraint_status per name: matched (a retailer's title states a value that meets it), or unverified (not known to meet it, never treat as a match: in attributes[name], conflicting means retailers disagree and missing means unknown). Products whose stated value fails are left out and counted in excluded_by_constraints. Wins over the same spec written in query, where only a spec that says what it is counts (24GB RAM, 1TB; a bare 24GB does not) and means at least, except screen size. A plain 16GB or 65W is gte; use eq only when the user says exactly, and for screen size. Always pass a category or brand with constraints. A bad request (unknown name or operator, negative value, a combination nothing can meet such as gte above lte, or constraints with no category, brand or search words) throws a TrustRailsError with statusCode 400; the message naming the valid values is in error.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 known attributes spec as status and value without sources, and, with constraints, constraint_status
  • options.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 is unverified. A retailer can appear under two values, so check offers[].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