The @signa-so/sdk package is a typed, ergonomic client for the Signa API: full types for every endpoint and response, automatic pagination, built-in retries, and typed error classes.
The SDK is designed for server-side use. A Signa API key grants full access to your org’s data; putting one in browser code exposes it to every visitor. Proxy requests through your own backend instead.
Install
npm install @signa-so/sdk
Requires Node.js 18+ or Bun 1.0+. TypeScript 5.0+ is recommended but not required.
import { Signa } from '@signa-so/sdk';
const signa = new Signa({
api_key: process.env.SIGNA_API_KEY,
// Optional overrides
base_url: 'https://api.signa.so', // default
timeout: 30_000, // 30s default
max_retries: 2, // default, retries on 429/5xx
});
If you omit api_key, the client reads SIGNA_API_KEY from the environment:
export SIGNA_API_KEY=sig_your_key_here
// Uses SIGNA_API_KEY automatically
const signa = new Signa();
Resources
The client organizes the API into 19 resource namespaces:
| Namespace | Description |
|---|
signa.trademarks | Search, retrieve, history, batch lookup, related marks, proceedings, coverage, media |
signa.owners | Owner profiles, trademark portfolios, GLEIF corporate relationships |
signa.entities | Resolved entities: one company across all offices, trademarks, corporate family |
signa.attorneys | Attorney profiles, trademark portfolios, client lists |
signa.firms | Law firm profiles, attorneys, trademark portfolios |
signa.proceedings | Oppositions, cancellations, and other tribunal proceedings |
signa.suggest | Cross-entity typeahead across trademarks, owners, attorneys, and firms |
signa.references | Classifications, offices, jurisdictions, event types, design codes, deadline and opposition rules |
signa.goodsServices | Goods & services term catalog and AI-assisted specification drafting |
signa.deadlines | Batch maintenance-deadline computations |
signa.oppositions | Batch opposition-window computations |
signa.reconcile | Compare your own records against register data |
signa.portfolios | Portfolio CRUD, member marks, deadlines |
signa.savedSearches | Saved search CRUD and re-execute |
signa.watches | Watch CRUD, pause/resume, preview, bulk create, diagnostics |
signa.alerts | Read-only alert listing, retrieval, and bulk lookup |
signa.webhooks | Webhook endpoint CRUD, secret rotation, test deliveries, delivery audit |
signa.events | Org event stream: list and retrieve with diffs |
signa.organization | Account identity, usage, API key management, request logs |
Search and list trademarks
search() takes a text query with structured filters under filters, plus options for aggregations and totals:
const results = await signa.trademarks.search({
query: 'SIGNA',
strategies: ['exact', 'phonetic'],
filters: { jurisdictions: ['US', 'EU'], nice_classes: [9, 42] },
options: { aggregations: ['office_code'], include_total: true },
});
for (const hit of results.data) {
console.log(hit.mark_text, hit.score);
}
console.log(results.aggregations);
console.log(results.search_meta);
list() is for filter-only or simple-query listing. Its filters are flat top-level params, not nested:
const page = await signa.trademarks.list({
offices: ['uspto'],
status_primary: 'active',
filing_date_gte: '2024-01-01',
sort: '-filing_date',
limit: 50,
});
Both return a SignaList, see Pagination below.
Retrieve and batch
const tm = await signa.trademarks.retrieve('tm_8kLm2nPq');
console.log(tm.mark_text, tm.status.primary);
// Embed the 50 most recent prosecution events inline
const withHistory = await signa.trademarks.retrieve('tm_8kLm2nPq', { include: ['history'] });
Look up multiple trademarks in one call, by Signa ID or office identifier (max 100 per call):
const result = await signa.trademarks.batch({ ids: ['tm_8kLm2nPq', 'tm_9jNq3rTw'] });
console.log(result.data); // trademarks that matched
console.log(result.not_found); // IDs or identifiers that didn't match
Every list and search method returns a SignaList<T>, which supports three consumption patterns. The first page is fetched eagerly; later pages are fetched lazily.
Async iteration, the simplest approach:
for await (const tm of signa.trademarks.list({ offices: ['uspto'] })) {
console.log(tm.mark_text);
}
Collect to an array with toArray(). A safety cap of 10,000 items applies by default; pass { limit } to change it:
const page = await signa.trademarks.list({ jurisdictions: ['US'], status_primary: 'active' });
const allMarks = await page.toArray(); // up to 10,000
const first500 = await page.toArray({ limit: 500 });
Manual paging, for full control over when the next request fires:
let page = await signa.trademarks.list({ offices: ['uspto'], limit: 100 });
console.log(`Page 1: ${page.data.length} items`);
console.log(`Request ID: ${page.request_id}`);
while (page.has_more) {
page = await page.getNextPage();
console.log(`Next page: ${page.data.length} items`);
}
getNextPage() returns an empty list (not an error) once there are no more pages. Every SignaList exposes data, has_more, request_id, and, on search responses, search_meta and aggregations. There is no public pagination field on the list itself, use has_more and getNextPage() to drive pagination rather than reaching for a cursor directly.
Error handling
All errors extend SignaError. API errors (4xx/5xx) extend SignaAPIError and carry a typed subclass per status code:
SignaError
├── SignaAPIError
│ ├── BadRequestError 400
│ ├── AuthenticationError 401
│ ├── PermissionError 403
│ ├── NotFoundError 404
│ ├── ConflictError 409
│ ├── RateLimitError 429 (has retry_after)
│ └── InternalServerError 5xx
├── ConnectionError DNS, TCP, TLS failures
└── TimeoutError request exceeded timeout
Use instanceof to handle specific error types:
import { Signa } from '@signa-so/sdk';
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });
try {
const tm = await signa.trademarks.retrieve('tm_invalid');
} catch (err) {
if (err instanceof Signa.NotFoundError) {
console.log('Trademark not found');
} else if (err instanceof Signa.RateLimitError) {
console.log(`Rate limited, retry after ${err.retry_after}s`);
} else if (err instanceof Signa.AuthenticationError) {
console.log('Invalid API key');
} else if (err instanceof Signa.SignaAPIError) {
console.log(`API error ${err.status}: ${err.message}`);
} else if (err instanceof Signa.ConnectionError) {
console.log('Network issue:', err.message);
} else if (err instanceof Signa.TimeoutError) {
console.log('Request timed out');
}
}
Every SignaAPIError carries the structured error body plus the request ID:
try {
await signa.trademarks.list({ offices: ['invalid_office'] });
} catch (err) {
if (err instanceof Signa.BadRequestError) {
console.log(err.error.type); // machine-readable slug, e.g. "validation_error"
console.log(err.error.detail); // human-readable detail
console.log(err.request_id); // include this in support requests
}
}
Automatic retries
| Error type | Retried? |
|---|
ConnectionError | Yes |
TimeoutError | Yes |
RateLimitError (429) | Yes, after the retry_after delay |
InternalServerError (5xx) | Yes, unless the error body says retryable: false |
BadRequestError (400) | No |
AuthenticationError (401) | No |
NotFoundError (404) | No |
An explicit retryable: false in the server’s error body overrides the status-based rule: a deterministic failure like the watch preview’s 504 timeout is not retried, since a blind retry would re-run the same over-budget work. Retries use exponential backoff with jitter.
const signa = new Signa({
api_key: process.env.SIGNA_API_KEY,
max_retries: 3, // default: 2, set to 0 to disable
});
Override retry behavior per request:
const tm = await signa.trademarks.retrieve('tm_8kLm2nPq', undefined, { max_retries: 0 });
Timeouts
The default timeout is 30 seconds. Configure it globally or per request:
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY, timeout: 60_000 });
const tm = await signa.trademarks.retrieve('tm_8kLm2nPq', undefined, { timeout: 10_000 });
Debug mode
const signa = new Signa({ api_key: process.env.SIGNA_API_KEY, debug: true });
Logs each request and response (method, URL, status, timing) to stderr.