@radsoft/markbase 2026.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # @radsoft/markbase
2
+
3
+ Official Node client for the [Markbase](https://markbase.co) USPTO trademark API.
4
+ Search, screen, and look up 14M+ US trademarks with full TypeScript types.
5
+
6
+ Covers the **USPTO only**. If you need EUIPO, WIPO, or UKIPO, a multi-office
7
+ platform is a better fit.
8
+
9
+ ```bash
10
+ npm i @radsoft/markbase
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ No key needed to try it — the keyless tier allows 1,000 requests a month.
16
+ [Get a free key](https://markbase.co/login) for 5,000.
17
+
18
+ ```ts
19
+ import { Markbase } from "@radsoft/markbase";
20
+
21
+ const mb = new Markbase({ apiKey: process.env.MARKBASE_API_KEY });
22
+
23
+ // "can we call it this?"
24
+ const result = await mb.clearance("Acme", { trademarkClass: 9 });
25
+ if (result.highest_risk === "high") {
26
+ console.log("Conflicts:", result.matches.map((m) => m.word_mark));
27
+ }
28
+ ```
29
+
30
+ The key is read from `MARKBASE_API_KEY` if you don't pass one. Pass `apiKey: null`
31
+ to force the keyless tier even when that variable is set.
32
+
33
+ ## Screening a name
34
+
35
+ Trademark rights are scoped to goods and services — DELTA coexists as an airline,
36
+ a faucet maker, and dental insurance. **Always pass a class**, or you get noise
37
+ instead of blockers.
38
+
39
+ ```ts
40
+ const { highest_risk, matches } = await mb.clearance("nikee", { trademarkClass: 25 });
41
+ // highest_risk: "high"
42
+ // matches[0]: { word_mark: "NIKE", owner_name: "Nike, Inc.", similarity: 0.85, risk: "high" }
43
+ ```
44
+
45
+ `mb.classes()` returns all 45 Nice classes if you need to pick one.
46
+
47
+ ## Searching
48
+
49
+ Typo-tolerant — `gogle` finds Google's marks.
50
+
51
+ ```ts
52
+ const res = await mb.search({ q: "gogle", limit: 10 });
53
+
54
+ // filters
55
+ await mb.search({
56
+ q: "acme",
57
+ trademarkClass: 9, // 9 and "009" both work
58
+ statusCode: "800", // registered
59
+ ownerCountry: "US",
60
+ filingDateFrom: "20200101",
61
+ });
62
+ ```
63
+
64
+ `total_hits` is capped at 1,000 by the search engine — narrow with filters when
65
+ you need an exact count, or use `aggregate()`.
66
+
67
+ Walk large result sets without managing pages:
68
+
69
+ ```ts
70
+ for await (const mark of mb.paginate({ q: "coffee", limit: 100 }, 5)) {
71
+ console.log(mark.word_mark);
72
+ }
73
+ ```
74
+
75
+ ## Everything else
76
+
77
+ ```ts
78
+ await mb.suggest("nik", 5); // autocomplete, tuned for as-you-type
79
+ await mb.getTrademark("72414177"); // one full record
80
+ await mb.getTimeline("72414177"); // prosecution history
81
+ await mb.byOwner("Nike, Inc."); // an owner's whole portfolio
82
+ await mb.aggregate("class"); // distribution counts
83
+ await mb.info(); // index size and freshness
84
+ await mb.getBatch([...]); // up to 50 at once — Scale plan only
85
+ ```
86
+
87
+ ## Helpers
88
+
89
+ USPTO records are raw. These make them presentable:
90
+
91
+ ```ts
92
+ import { statusOf, parseDate } from "@radsoft/markbase";
93
+
94
+ statusOf("800"); // "registered" (600s are dead, 710s cancelled, 900+ expired)
95
+ parseDate("19720131"); // Date — USPTO stores YYYYMMDD strings
96
+ ```
97
+
98
+ ## Errors and limits
99
+
100
+ Typed errors, so you can branch on what actually went wrong:
101
+
102
+ ```ts
103
+ import { RateLimitError, PlanError, AuthError, NotFoundError } from "@radsoft/markbase";
104
+
105
+ try {
106
+ await mb.getBatch(serials);
107
+ } catch (err) {
108
+ if (err instanceof PlanError) {
109
+ // batch is Scale-only
110
+ } else if (err instanceof RateLimitError && err.kind === "quota") {
111
+ // monthly allowance gone — err.resetsAt tells you when it returns.
112
+ // retrying sooner won't help, which is why the client doesn't.
113
+ }
114
+ }
115
+ ```
116
+
117
+ Per-minute limits and 5xx are retried automatically with backoff (`maxRetries`,
118
+ default 2). Monthly quota exhaustion is not — waiting seconds can't fix a
119
+ month-long window.
120
+
121
+ Current limits are on the client after any call:
122
+
123
+ ```ts
124
+ mb.rateLimit;
125
+ // { plan: "launch", rateLimit: 120, rateRemaining: 119,
126
+ // quotaLimit: 50000, quotaRemaining: 49873, quotaResetsAt: Date }
127
+ ```
128
+
129
+ ## Options
130
+
131
+ | Option | Default | |
132
+ |---|---|---|
133
+ | `apiKey` | `MARKBASE_API_KEY` | `null` forces keyless |
134
+ | `baseUrl` | `https://api.markbase.co` | |
135
+ | `timeout` | `30000` | ms before abort |
136
+ | `maxRetries` | `2` | rate limits and 5xx |
137
+ | `fetch` | global `fetch` | inject your own |
138
+
139
+ ## Not legal advice
140
+
141
+ Clearance returns a first-pass screening signal over the federal register. It
142
+ does not cover state trademarks, common-law rights, or the legal analysis of
143
+ likelihood of confusion. Treat a clean result as "worth pursuing", not
144
+ "cleared", and talk to a trademark attorney before adopting a name.
145
+
146
+ ## Also available
147
+
148
+ - **[@radsoft/markbase-mcp](https://www.npmjs.com/package/@radsoft/markbase-mcp)** — MCP server, so an AI assistant can screen names directly
149
+ - **[API docs](https://api.markbase.co/docs)** · **[OpenAPI spec](https://api.markbase.co/openapi.json)**
150
+
151
+ ## License
152
+
153
+ MIT — a product of [Rad Soft](https://radsoftinc.com).
@@ -0,0 +1,65 @@
1
+ import type { Trademark, SearchOptions, SearchResult, Suggestion, ClearanceResult, OwnerResult, BatchResult, AggregateField, AggregateResult, NiceClass, IndexInfo, RateLimitInfo, MarkStatus } from "./types.js";
2
+ export interface MarkbaseOptions {
3
+ /**
4
+ * Get one free at https://markbase.co. Omitted falls back to
5
+ * `MARKBASE_API_KEY`; pass `null` to force the keyless tier even when that
6
+ * variable is set.
7
+ */
8
+ apiKey?: string | null;
9
+ baseUrl?: string;
10
+ /** Milliseconds before a request is aborted. Default 30s. */
11
+ timeout?: number;
12
+ /** Retries on 429 and 5xx, honouring the reset window. Default 2. */
13
+ maxRetries?: number;
14
+ fetch?: typeof globalThis.fetch;
15
+ }
16
+ /** Class codes are stored zero-padded; the API accepts either but we normalise. */
17
+ export declare function padClass(v: string | number): string;
18
+ /** Turn a numeric USPTO status code into something you can show a user. */
19
+ export declare function statusOf(code: string | null | undefined): MarkStatus;
20
+ /** USPTO dates are YYYYMMDD strings; this gives you a Date or null. */
21
+ export declare function parseDate(d: string | null | undefined): Date | null;
22
+ export declare class Markbase {
23
+ readonly baseUrl: string;
24
+ private readonly apiKey?;
25
+ private readonly timeout;
26
+ private readonly maxRetries;
27
+ private readonly fetchImpl;
28
+ /** Limits from the most recent response. Null until you've made a call. */
29
+ rateLimit: RateLimitInfo | null;
30
+ constructor(opts?: MarkbaseOptions | string);
31
+ private readLimits;
32
+ private request;
33
+ /** Typo-tolerant full-text search across 14M+ US trademarks. */
34
+ search(options: SearchOptions | string): Promise<SearchResult>;
35
+ /** Lightweight autocomplete, tuned for as-you-type latency. */
36
+ suggest(q: string, limit?: number): Promise<Suggestion[]>;
37
+ /** Full record for one mark. */
38
+ getTrademark(serialNumber: string): Promise<Trademark>;
39
+ /** Prosecution history — filing, publication, office actions, registration. */
40
+ getTimeline(serialNumber: string): Promise<unknown>;
41
+ /** Up to 50 marks in one request. Requires the Scale plan. */
42
+ getBatch(serialNumbers: string[]): Promise<BatchResult>;
43
+ /** Every mark held by an owner. */
44
+ byOwner(name: string, opts?: {
45
+ page?: number;
46
+ limit?: number;
47
+ }): Promise<OwnerResult>;
48
+ /**
49
+ * Screen a proposed name against the register, ranked by similarity.
50
+ * A first-pass signal, not legal advice — pass a class, since trademark
51
+ * rights are scoped to goods and services.
52
+ */
53
+ clearance(mark: string, opts?: {
54
+ trademarkClass?: string | number;
55
+ limit?: number;
56
+ }): Promise<ClearanceResult>;
57
+ /** Distribution counts across a facet. */
58
+ aggregate(by: AggregateField): Promise<AggregateResult>;
59
+ /** All 45 Nice classification classes. */
60
+ classes(): Promise<NiceClass[]>;
61
+ /** Index size and freshness. */
62
+ info(): Promise<IndexInfo>;
63
+ /** Walk every page of a search, yielding marks one at a time. */
64
+ paginate(options: SearchOptions, maxPages?: number): AsyncGenerator<Trademark>;
65
+ }
package/dist/client.js ADDED
@@ -0,0 +1,222 @@
1
+ import { MarkbaseError, AuthError, RateLimitError, PlanError, NotFoundError, } from "./errors.js";
2
+ /** Class codes are stored zero-padded; the API accepts either but we normalise. */
3
+ export function padClass(v) {
4
+ const s = String(v);
5
+ return /^\d+$/.test(s) ? s.padStart(3, "0") : s;
6
+ }
7
+ /** Turn a numeric USPTO status code into something you can show a user. */
8
+ export function statusOf(code) {
9
+ if (!code)
10
+ return "unknown";
11
+ const n = parseInt(code, 10);
12
+ if (Number.isNaN(n))
13
+ return "unknown";
14
+ if (n >= 600 && n < 700)
15
+ return "dead";
16
+ if (n >= 710 && n < 720)
17
+ return "cancelled";
18
+ if (n >= 900)
19
+ return "expired";
20
+ if (n >= 800 || n === 700 || (n >= 730 && n < 740))
21
+ return "registered";
22
+ if (n < 600)
23
+ return "pending";
24
+ return "registered";
25
+ }
26
+ /** USPTO dates are YYYYMMDD strings; this gives you a Date or null. */
27
+ export function parseDate(d) {
28
+ if (!d || d.length !== 8)
29
+ return null;
30
+ const iso = `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`;
31
+ const parsed = new Date(iso);
32
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
33
+ }
34
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
35
+ export class Markbase {
36
+ baseUrl;
37
+ apiKey;
38
+ timeout;
39
+ maxRetries;
40
+ fetchImpl;
41
+ /** Limits from the most recent response. Null until you've made a call. */
42
+ rateLimit = null;
43
+ constructor(opts = {}) {
44
+ const o = typeof opts === "string" ? { apiKey: opts } : opts;
45
+ // null is an explicit "no key"; undefined means "look at the environment"
46
+ this.apiKey = o.apiKey === null ? undefined : (o.apiKey ?? process.env.MARKBASE_API_KEY);
47
+ this.baseUrl = (o.baseUrl ?? process.env.MARKBASE_API_URL ?? "https://api.markbase.co").replace(/\/$/, "");
48
+ this.timeout = o.timeout ?? 30_000;
49
+ this.maxRetries = o.maxRetries ?? 2;
50
+ this.fetchImpl = o.fetch ?? globalThis.fetch;
51
+ }
52
+ readLimits(h) {
53
+ const num = (v) => v == null || v === "unlimited" ? null : Number.isNaN(Number(v)) ? null : Number(v);
54
+ const reset = h.get("x-quota-reset");
55
+ this.rateLimit = {
56
+ plan: h.get("x-plan"),
57
+ rateLimit: num(h.get("x-ratelimit-limit")),
58
+ rateRemaining: num(h.get("x-ratelimit-remaining")),
59
+ quotaLimit: num(h.get("x-quota-limit")),
60
+ quotaRemaining: num(h.get("x-quota-remaining")),
61
+ quotaResetsAt: reset ? new Date(Number(reset) * 1000) : null,
62
+ };
63
+ }
64
+ async request(path, init) {
65
+ const url = new URL(this.baseUrl + path);
66
+ for (const [k, v] of Object.entries(init?.query ?? {})) {
67
+ if (v == null)
68
+ continue;
69
+ if (Array.isArray(v))
70
+ v.forEach((item) => url.searchParams.append(k, String(item)));
71
+ else
72
+ url.searchParams.set(k, String(v));
73
+ }
74
+ let lastErr;
75
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
76
+ const ctrl = new AbortController();
77
+ const timer = setTimeout(() => ctrl.abort(), this.timeout);
78
+ try {
79
+ const res = await this.fetchImpl(url, {
80
+ ...init,
81
+ signal: ctrl.signal,
82
+ headers: {
83
+ accept: "application/json",
84
+ "user-agent": "markbase-node",
85
+ ...(init?.body ? { "content-type": "application/json" } : {}),
86
+ ...(this.apiKey ? { "x-api-key": this.apiKey } : {}),
87
+ ...init?.headers,
88
+ },
89
+ });
90
+ this.readLimits(res.headers);
91
+ if (res.ok)
92
+ return (await res.json());
93
+ const body = await res.json().catch(() => ({}));
94
+ const message = body?.error ?? `Request failed (${res.status})`;
95
+ if (res.status === 401)
96
+ throw new AuthError(message);
97
+ if (res.status === 403)
98
+ throw new PlanError(message, body);
99
+ if (res.status === 404)
100
+ throw new NotFoundError(message);
101
+ if (res.status === 429) {
102
+ const isQuota = /quota/i.test(String(message));
103
+ const resetsAt = this.rateLimit?.quotaResetsAt ?? null;
104
+ // a monthly quota won't clear by waiting a few seconds, so only the
105
+ // per-minute limit is worth retrying
106
+ const retryAfterMs = isQuota ? null : 1000 * (attempt + 1) * 2;
107
+ const err = new RateLimitError(message, isQuota ? "quota" : "rate", retryAfterMs, resetsAt, body);
108
+ if (isQuota || attempt >= this.maxRetries)
109
+ throw err;
110
+ lastErr = err;
111
+ await sleep(retryAfterMs);
112
+ continue;
113
+ }
114
+ if (res.status >= 500 && attempt < this.maxRetries) {
115
+ lastErr = new MarkbaseError(message, res.status, body);
116
+ await sleep(1000 * (attempt + 1));
117
+ continue;
118
+ }
119
+ throw new MarkbaseError(message, res.status, body);
120
+ }
121
+ catch (err) {
122
+ if (err instanceof MarkbaseError)
123
+ throw err;
124
+ // network error or timeout — worth one more go
125
+ lastErr = err;
126
+ if (attempt >= this.maxRetries) {
127
+ const msg = err?.name === "AbortError" ? `Request timed out after ${this.timeout}ms` : String(err);
128
+ throw new MarkbaseError(msg, 0);
129
+ }
130
+ await sleep(1000 * (attempt + 1));
131
+ }
132
+ finally {
133
+ clearTimeout(timer);
134
+ }
135
+ }
136
+ throw lastErr instanceof Error ? lastErr : new MarkbaseError("Request failed", 0);
137
+ }
138
+ /** Typo-tolerant full-text search across 14M+ US trademarks. */
139
+ search(options) {
140
+ const o = typeof options === "string" ? { q: options } : options;
141
+ const cls = o.trademarkClass;
142
+ return this.request("/search", {
143
+ query: {
144
+ q: o.q,
145
+ status_code: o.statusCode,
146
+ international_code: cls == null ? undefined : Array.isArray(cls) ? cls.map(padClass) : padClass(cls),
147
+ filing_date_from: o.filingDateFrom,
148
+ filing_date_to: o.filingDateTo,
149
+ owner_country: o.ownerCountry,
150
+ owner_state: o.ownerState,
151
+ sort: o.sort,
152
+ page: o.page,
153
+ limit: o.limit,
154
+ facets: o.facets ? "true" : undefined,
155
+ },
156
+ });
157
+ }
158
+ /** Lightweight autocomplete, tuned for as-you-type latency. */
159
+ async suggest(q, limit = 5) {
160
+ const r = await this.request("/suggest", { query: { q, limit } });
161
+ return r.suggestions;
162
+ }
163
+ /** Full record for one mark. */
164
+ getTrademark(serialNumber) {
165
+ return this.request(`/trademark/${encodeURIComponent(serialNumber)}`);
166
+ }
167
+ /** Prosecution history — filing, publication, office actions, registration. */
168
+ getTimeline(serialNumber) {
169
+ return this.request(`/trademark/${encodeURIComponent(serialNumber)}/timeline`);
170
+ }
171
+ /** Up to 50 marks in one request. Requires the Scale plan. */
172
+ getBatch(serialNumbers) {
173
+ return this.request("/trademark/batch", {
174
+ method: "POST",
175
+ body: JSON.stringify({ serial_numbers: serialNumbers }),
176
+ });
177
+ }
178
+ /** Every mark held by an owner. */
179
+ byOwner(name, opts = {}) {
180
+ return this.request("/owner", { query: { name, ...opts } });
181
+ }
182
+ /**
183
+ * Screen a proposed name against the register, ranked by similarity.
184
+ * A first-pass signal, not legal advice — pass a class, since trademark
185
+ * rights are scoped to goods and services.
186
+ */
187
+ clearance(mark, opts = {}) {
188
+ return this.request("/clearance", {
189
+ query: {
190
+ mark,
191
+ class: opts.trademarkClass == null ? undefined : padClass(opts.trademarkClass),
192
+ limit: opts.limit,
193
+ },
194
+ });
195
+ }
196
+ /** Distribution counts across a facet. */
197
+ aggregate(by) {
198
+ return this.request("/aggregate", { query: { by } });
199
+ }
200
+ /** All 45 Nice classification classes. */
201
+ async classes() {
202
+ const r = await this.request("/classes");
203
+ return r.classes;
204
+ }
205
+ /** Index size and freshness. */
206
+ info() {
207
+ return this.request("/info");
208
+ }
209
+ /** Walk every page of a search, yielding marks one at a time. */
210
+ async *paginate(options, maxPages = 10) {
211
+ const limit = options.limit ?? 100;
212
+ for (let page = options.page ?? 1; page < (options.page ?? 1) + maxPages; page++) {
213
+ const res = await this.search({ ...options, page, limit });
214
+ if (!res.hits.length)
215
+ return;
216
+ for (const hit of res.hits)
217
+ yield hit;
218
+ if (res.hits.length < limit)
219
+ return;
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,28 @@
1
+ /** Any non-2xx response from the API. */
2
+ export declare class MarkbaseError extends Error {
3
+ readonly status: number;
4
+ readonly body?: unknown | undefined;
5
+ constructor(message: string, status: number, body?: unknown | undefined);
6
+ }
7
+ /** The API key was missing or not recognised. */
8
+ export declare class AuthError extends MarkbaseError {
9
+ constructor(message?: string);
10
+ }
11
+ /**
12
+ * Rate limit or monthly quota exhausted. `retryAfterMs` is populated for
13
+ * per-minute limits; a monthly quota sets `resetsAt` instead, and retrying
14
+ * before then will not help.
15
+ */
16
+ export declare class RateLimitError extends MarkbaseError {
17
+ readonly kind: "rate" | "quota";
18
+ readonly retryAfterMs: number | null;
19
+ readonly resetsAt: Date | null;
20
+ constructor(message: string, kind: "rate" | "quota", retryAfterMs: number | null, resetsAt: Date | null, body?: unknown);
21
+ }
22
+ /** The plan doesn't include this endpoint (e.g. batch is Scale-only). */
23
+ export declare class PlanError extends MarkbaseError {
24
+ constructor(message: string, body?: unknown);
25
+ }
26
+ export declare class NotFoundError extends MarkbaseError {
27
+ constructor(message?: string);
28
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,48 @@
1
+ /** Any non-2xx response from the API. */
2
+ export class MarkbaseError extends Error {
3
+ status;
4
+ body;
5
+ constructor(message, status, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = "MarkbaseError";
10
+ }
11
+ }
12
+ /** The API key was missing or not recognised. */
13
+ export class AuthError extends MarkbaseError {
14
+ constructor(message = "Invalid or missing API key") {
15
+ super(message, 401);
16
+ this.name = "AuthError";
17
+ }
18
+ }
19
+ /**
20
+ * Rate limit or monthly quota exhausted. `retryAfterMs` is populated for
21
+ * per-minute limits; a monthly quota sets `resetsAt` instead, and retrying
22
+ * before then will not help.
23
+ */
24
+ export class RateLimitError extends MarkbaseError {
25
+ kind;
26
+ retryAfterMs;
27
+ resetsAt;
28
+ constructor(message, kind, retryAfterMs, resetsAt, body) {
29
+ super(message, 429, body);
30
+ this.kind = kind;
31
+ this.retryAfterMs = retryAfterMs;
32
+ this.resetsAt = resetsAt;
33
+ this.name = "RateLimitError";
34
+ }
35
+ }
36
+ /** The plan doesn't include this endpoint (e.g. batch is Scale-only). */
37
+ export class PlanError extends MarkbaseError {
38
+ constructor(message, body) {
39
+ super(message, 403, body);
40
+ this.name = "PlanError";
41
+ }
42
+ }
43
+ export class NotFoundError extends MarkbaseError {
44
+ constructor(message = "Not found") {
45
+ super(message, 404);
46
+ this.name = "NotFoundError";
47
+ }
48
+ }
@@ -0,0 +1,4 @@
1
+ export { Markbase, padClass, statusOf, parseDate } from "./client.js";
2
+ export type { MarkbaseOptions } from "./client.js";
3
+ export * from "./types.js";
4
+ export * from "./errors.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Markbase, padClass, statusOf, parseDate } from "./client.js";
2
+ export * from "./types.js";
3
+ export * from "./errors.js";
@@ -0,0 +1,114 @@
1
+ /** A USPTO trademark record. */
2
+ export interface Trademark {
3
+ serial_number: string;
4
+ registration_number: string | null;
5
+ word_mark: string | null;
6
+ owner_name: string | null;
7
+ owner_city: string | null;
8
+ owner_state: string | null;
9
+ owner_country: string | null;
10
+ status_code: string | null;
11
+ filing_date: string | null;
12
+ registration_date: string | null;
13
+ international_codes: string[];
14
+ goods_services: string | null;
15
+ attorney_name: string | null;
16
+ }
17
+ /** Human-readable status derived from the numeric USPTO code. */
18
+ export type MarkStatus = "pending" | "registered" | "dead" | "cancelled" | "expired" | "unknown";
19
+ export type RiskLevel = "high" | "medium" | "low" | "none";
20
+ export interface SearchOptions {
21
+ /** Free-text query. Typo-tolerant. Optional if a filter is given. */
22
+ q?: string;
23
+ /** USPTO status code, e.g. "800" for registered. */
24
+ statusCode?: string;
25
+ /** Nice class 1-45. Accepts 9 or "009". */
26
+ trademarkClass?: string | number | Array<string | number>;
27
+ /** YYYYMMDD */
28
+ filingDateFrom?: string;
29
+ filingDateTo?: string;
30
+ ownerCountry?: string;
31
+ ownerState?: string;
32
+ /** e.g. "filing_date:desc" */
33
+ sort?: string;
34
+ page?: number;
35
+ limit?: number;
36
+ /** Include facet distribution counts. Slightly slower. */
37
+ facets?: boolean;
38
+ }
39
+ export interface SearchResult {
40
+ query: string;
41
+ hits: Trademark[];
42
+ /** Capped at 1000 by the search engine — narrow with filters for a precise count. */
43
+ total_hits: number;
44
+ facets?: Record<string, Record<string, number>>;
45
+ page: number;
46
+ limit: number;
47
+ processing_time_ms: number;
48
+ }
49
+ export interface Suggestion {
50
+ serial_number: string;
51
+ word_mark: string | null;
52
+ owner_name: string | null;
53
+ status_code: string | null;
54
+ }
55
+ export interface ClearanceMatch extends Pick<Trademark, "serial_number" | "word_mark" | "owner_name" | "status_code" | "international_codes"> {
56
+ /** 0-1, higher means more similar to the proposed mark. */
57
+ similarity: number;
58
+ risk: RiskLevel;
59
+ }
60
+ export interface ClearanceResult {
61
+ mark: string;
62
+ class: string | null;
63
+ highest_risk: RiskLevel;
64
+ matches: ClearanceMatch[];
65
+ }
66
+ export interface OwnerResult {
67
+ owner: string;
68
+ hits: Trademark[];
69
+ total_hits: number;
70
+ page: number;
71
+ limit: number;
72
+ }
73
+ export interface BatchResult {
74
+ found: Trademark[];
75
+ not_found: string[];
76
+ count: number;
77
+ }
78
+ export type AggregateField = "class" | "status" | "country" | "drawing" | "year";
79
+ export interface AggregateResult {
80
+ by: AggregateField;
81
+ buckets: Array<{
82
+ key: string;
83
+ count: number;
84
+ }>;
85
+ }
86
+ export interface NiceClass {
87
+ code: string;
88
+ title: string;
89
+ }
90
+ export interface IndexInfo {
91
+ total_documents: number;
92
+ indexing: boolean;
93
+ last_updated: string | null;
94
+ oldest_filing: {
95
+ serial_number: string;
96
+ filing_date: string | null;
97
+ word_mark: string | null;
98
+ } | null;
99
+ newest_filing: {
100
+ serial_number: string;
101
+ filing_date: string | null;
102
+ word_mark: string | null;
103
+ } | null;
104
+ }
105
+ /** Quota and rate-limit state, read from the response headers of the last call. */
106
+ export interface RateLimitInfo {
107
+ plan: string | null;
108
+ /** null means unlimited. */
109
+ rateLimit: number | null;
110
+ rateRemaining: number | null;
111
+ quotaLimit: number | null;
112
+ quotaRemaining: number | null;
113
+ quotaResetsAt: Date | null;
114
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@radsoft/markbase",
3
+ "version": "2026.8.1",
4
+ "description": "Official Node client for the Markbase USPTO trademark API — search, screen, and look up 14M+ US trademarks.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "keywords": [
17
+ "trademark",
18
+ "uspto",
19
+ "trademark-search",
20
+ "clearance",
21
+ "brand-name",
22
+ "markbase",
23
+ "api-client",
24
+ "sdk"
25
+ ],
26
+ "homepage": "https://markbase.co",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/Rad-Soft/marked-api.git",
30
+ "directory": "sdk"
31
+ },
32
+ "publishConfig": { "access": "public" },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "dev": "tsc --watch",
36
+ "prepublishOnly": "bun run build"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.10.2",
40
+ "typescript": "^5.7.2"
41
+ }
42
+ }