@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 +153 -0
- package/dist/client.d.ts +65 -0
- package/dist/client.js +222 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +48 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +114 -0
- package/dist/types.js +1 -0
- package/package.json +42 -0
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).
|
package/dist/client.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|