@radsoft/markbase 2026.10.2 → 2026.11.2

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 CHANGED
@@ -157,12 +157,23 @@ Keep the key server-side. It's a secret; don't ship it to the browser.
157
157
 
158
158
  ---
159
159
 
160
+ ## Unreleased: opt-in phonetic clearance
161
+
162
+ The source client accepts an optional `phonetic` boolean in `clearance` and
163
+ `clearanceBatch` options. The API mode is not enabled in production yet; client
164
+ support alone does not make it available. Omit the option for current spelling-only
165
+ behavior. An unavailable opt-in request returns 503, and batches are never retried
166
+ automatically. See the [unreleased contract](../docs/phonetic-clearance.md) for
167
+ response evidence, ranking, and input limits. Existing spelling scores and risk
168
+ thresholds remain unchanged; phonetic evidence is not a legal availability decision.
169
+
160
170
  ## Reference
161
171
 
162
172
  | Method | Returns |
163
173
  |---|---|
164
174
  | `search(opts \| string)` | `SearchResult` — typo-tolerant full-text search |
165
175
  | `clearance(mark, opts?)` | `ClearanceResult` — ranked conflicts with a risk level |
176
+ | `clearanceBatch(marks, opts?)` | `ClearanceBatchResult` — 1–25 names in input order (**Launch or Scale**) |
166
177
  | `suggest(q, limit?)` | `Suggestion[]` — autocomplete, tuned for as-you-type |
167
178
  | `getTrademark(serial)` | `Trademark` — one full record |
168
179
  | `getTimeline(serial)` | prosecution history |
@@ -245,7 +256,14 @@ try {
245
256
  ## Rate limits and quota
246
257
 
247
258
  Per-minute limits and 5xx are **retried automatically** with backoff
248
- (`maxRetries`, default 2).
259
+ (`maxRetries`, default 2), except for `clearanceBatch()`.
260
+
261
+ Bulk clearance accepts 1–25 names, each up to 200 characters, with an optional
262
+ shared `trademarkClass` and `limit` of 1–50 matches per name (default 20). Every
263
+ submitted name counts toward monthly and per-minute limits, including duplicates.
264
+ A batch that exceeds the remaining monthly quota is rejected in full. Accepted
265
+ batches consume their full name count even if a search fails. The SDK never
266
+ automatically retries bulk requests because that could charge the names again.
249
267
 
250
268
  Monthly quota exhaustion is **not retried** — waiting a few seconds can't fix a
251
269
  month-long window, so it throws immediately with `resetsAt` rather than burning
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Trademark, SearchOptions, SearchResult, Suggestion, ClearanceResult, OwnerResult, BatchResult, AggregateField, AggregateResult, NiceClass, IndexInfo, RateLimitInfo, MarkStatus } from "./types.js";
1
+ import type { Trademark, SearchOptions, SearchResult, Suggestion, ClearanceOptions, ClearanceResult, ClearanceBatchResult, OwnerResult, BatchResult, AggregateField, AggregateResult, NiceClass, IndexInfo, RateLimitInfo, MarkStatus } from "./types.js";
2
2
  export interface MarkbaseOptions {
3
3
  /**
4
4
  * Get one free at https://markbase.co. Omitted falls back to
@@ -46,14 +46,13 @@ export declare class Markbase {
46
46
  limit?: number;
47
47
  }): Promise<OwnerResult>;
48
48
  /**
49
- * Screen a proposed name against the register, ranked by similarity.
49
+ * Screen a proposed name against the register. Spelling-only matching is the default.
50
50
  * A first-pass signal, not legal advice — pass a class, since trademark
51
51
  * rights are scoped to goods and services.
52
52
  */
53
- clearance(mark: string, opts?: {
54
- trademarkClass?: string | number;
55
- limit?: number;
56
- }): Promise<ClearanceResult>;
53
+ clearance(mark: string, opts?: ClearanceOptions): Promise<ClearanceResult>;
54
+ /** Screen 1–25 names on Launch or Scale. Each name consumes quota; retries are disabled. */
55
+ clearanceBatch(marks: string[], opts?: ClearanceOptions): Promise<ClearanceBatchResult>;
57
56
  /** Distribution counts across a facet. */
58
57
  aggregate(by: AggregateField): Promise<AggregateResult>;
59
58
  /** All 45 Nice classification classes. */
package/dist/client.js CHANGED
@@ -68,8 +68,9 @@ export class Markbase {
68
68
  };
69
69
  }
70
70
  async request(path, init) {
71
+ const { query, retry, ...requestInit } = init ?? {};
71
72
  const url = new URL(this.baseUrl + path);
72
- for (const [k, v] of Object.entries(init?.query ?? {})) {
73
+ for (const [k, v] of Object.entries(query ?? {})) {
73
74
  if (v == null)
74
75
  continue;
75
76
  if (Array.isArray(v))
@@ -77,13 +78,14 @@ export class Markbase {
77
78
  else
78
79
  url.searchParams.set(k, String(v));
79
80
  }
81
+ const maxRetries = retry === false ? 0 : this.maxRetries;
80
82
  let lastErr;
81
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
83
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
82
84
  const ctrl = new AbortController();
83
85
  const timer = setTimeout(() => ctrl.abort(), this.timeout);
84
86
  try {
85
87
  const res = await this.fetchImpl(url, {
86
- ...init,
88
+ ...requestInit,
87
89
  signal: ctrl.signal,
88
90
  headers: {
89
91
  accept: "application/json",
@@ -111,13 +113,13 @@ export class Markbase {
111
113
  // per-minute limit is worth retrying
112
114
  const retryAfterMs = isQuota ? null : 1000 * (attempt + 1) * 2;
113
115
  const err = new RateLimitError(message, isQuota ? "quota" : "rate", retryAfterMs, resetsAt, body);
114
- if (isQuota || attempt >= this.maxRetries)
116
+ if (isQuota || attempt >= maxRetries)
115
117
  throw err;
116
118
  lastErr = err;
117
119
  await sleep(retryAfterMs);
118
120
  continue;
119
121
  }
120
- if (res.status >= 500 && attempt < this.maxRetries) {
122
+ if (res.status >= 500 && attempt < maxRetries) {
121
123
  lastErr = new MarkbaseError(message, res.status, body);
122
124
  await sleep(1000 * (attempt + 1));
123
125
  continue;
@@ -129,7 +131,7 @@ export class Markbase {
129
131
  throw err;
130
132
  // network error or timeout — worth one more go
131
133
  lastErr = err;
132
- if (attempt >= this.maxRetries) {
134
+ if (attempt >= maxRetries) {
133
135
  const msg = err?.name === "AbortError" ? `Request timed out after ${this.timeout}ms` : String(err);
134
136
  throw new MarkbaseError(msg, 0);
135
137
  }
@@ -186,7 +188,7 @@ export class Markbase {
186
188
  return this.request("/owner", { query: { name, ...opts } });
187
189
  }
188
190
  /**
189
- * Screen a proposed name against the register, ranked by similarity.
191
+ * Screen a proposed name against the register. Spelling-only matching is the default.
190
192
  * A first-pass signal, not legal advice — pass a class, since trademark
191
193
  * rights are scoped to goods and services.
192
194
  */
@@ -196,9 +198,17 @@ export class Markbase {
196
198
  mark,
197
199
  class: opts.trademarkClass == null ? undefined : padClass(opts.trademarkClass),
198
200
  limit: opts.limit,
201
+ phonetic: opts.phonetic,
199
202
  },
200
203
  });
201
204
  }
205
+ /** Screen 1–25 names on Launch or Scale. Each name consumes quota; retries are disabled. */
206
+ clearanceBatch(marks, opts = {}) {
207
+ return this.request("/clearance/batch", {
208
+ method: "POST", retry: false,
209
+ body: JSON.stringify({ marks, class: opts.trademarkClass == null ? undefined : padClass(opts.trademarkClass), limit: opts.limit, phonetic: opts.phonetic }),
210
+ });
211
+ }
202
212
  /** Distribution counts across a facet. */
203
213
  aggregate(by) {
204
214
  return this.request("/aggregate", { query: { by } });
package/dist/types.d.ts CHANGED
@@ -52,16 +52,54 @@ export interface Suggestion {
52
52
  owner_name: string | null;
53
53
  status_code: string | null;
54
54
  }
55
+ export interface ClearanceOptions {
56
+ trademarkClass?: string | number;
57
+ limit?: number;
58
+ /** Unreleased opt-in; unavailable mode returns 503. Omit for spelling-only matching. */
59
+ phonetic?: boolean;
60
+ }
61
+ export interface ClearanceMatching {
62
+ mode: "spelling_and_phonetic";
63
+ algorithm: "double_metaphone_v1";
64
+ ranking: "phonetic_v1";
65
+ phonetic_status: "evaluated" | "not_evaluated";
66
+ truncated: boolean;
67
+ }
68
+ export interface ClearanceSignals {
69
+ exact: boolean;
70
+ spelling: {
71
+ score: number;
72
+ };
73
+ phonetic: {
74
+ algorithm: "double_metaphone_v1";
75
+ status: "matched" | "not_matched" | "not_evaluated";
76
+ scope?: "whole_name";
77
+ tokens?: Array<{
78
+ query: string;
79
+ candidate: string;
80
+ variant: "primary" | "alternate";
81
+ }>;
82
+ };
83
+ }
55
84
  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. */
85
+ /** 0-1 spelling score; phonetic evidence does not change this value. */
57
86
  similarity: number;
58
87
  risk: RiskLevel;
88
+ signals?: ClearanceSignals;
59
89
  }
60
90
  export interface ClearanceResult {
61
91
  mark: string;
62
92
  class: string | null;
93
+ /** Maximum spelling-based risk among retrieved candidates before the result limit. */
63
94
  highest_risk: RiskLevel;
64
95
  matches: ClearanceMatch[];
96
+ matching?: ClearanceMatching;
97
+ }
98
+ export interface ClearanceBatchResult {
99
+ results: ClearanceResult[];
100
+ usage: {
101
+ names: number;
102
+ };
65
103
  }
66
104
  export interface OwnerResult {
67
105
  owner: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radsoft/markbase",
3
- "version": "2026.10.2",
3
+ "version": "2026.11.2",
4
4
  "description": "Official Node client for the Markbase USPTO trademark API — search, screen, and look up 14M+ US trademarks.",
5
5
  "license": "MIT",
6
6
  "type": "module",