@parseapi/sdk 1.7.0 → 1.9.0

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
@@ -17,7 +17,7 @@ Get a key at [parseapi.com](https://parseapi.com). The client also reads `PARSEA
17
17
 
18
18
  ## API versions
19
19
 
20
- Version 1.7.0 explicitly selects the API contract supported by this SDK. It sends `Parse-Version: 2.0.0` on every lookup so responses match the API contract supported by the package. Your key and the team's saved default stay the same.
20
+ This SDK explicitly selects the API contract supported by this SDK. It sends `Parse-Version: 2.0.0` on every lookup so responses match the API contract supported by the package. Your key and the team's saved default stay the same.
21
21
 
22
22
  Upgrade the dependency in staging, review the [release notes](https://parseapi.com/docs/releases), and test the application before deploying the same code and dependency version to production. Commit your dependency lockfile so the tested package travels with your deployment. Future major SDK upgrades can select a newer API contract.
23
23
 
@@ -80,6 +80,36 @@ behavior and does not inherit a previous request's choice.
80
80
 
81
81
  ## Calls
82
82
 
83
+ Search the company directory, then refetch an ID you have chosen:
84
+
85
+ ```ts
86
+ const candidates = await parse.company.search({ query: 'Cloudflare', country: 'US' });
87
+ // After the caller chooses a candidate, retain its id for later lookups.
88
+ const chosenId = 'co_xw3f22es6cjq';
89
+ const company = await parse.company.id(chosenId, { deep: true });
90
+ const coverage = await parse.company.coverage();
91
+ ```
92
+
93
+ Company search accepts at most one of `query`, `domain`, `ticker`, or `identifier`, or discovers candidates using country or exact industry filters.
94
+ Use `exchange` with a ticker, or `authority` with an identifier. `country`, `limit`
95
+ and the returned `next` cursor narrow or page the same search. Pass the cursor as
96
+ `cursor` with the same selector, filters and limit. Each result has its own
97
+ `match` and optional `deep`. The SDK returns all candidates without choosing one.
98
+ Directory methods accept `signal`, `timeoutMs` and `retries`, and omit `lang`.
99
+ Directory deep adds detail in the same pooled request on every plan.
100
+
101
+ Company directory social profiles contain `platform`, `url` and `handle`; unknown metadata stays null. Phone and email records use `type` for the source-reported purpose. Earlier response fields remain readable by the client.
102
+ Coverage describes the current directory edition. Empty listings do not establish
103
+ private ownership. Deep source entries attribute selected enrichment fields, and
104
+ logo URLs are returned without fetching images. The existing `company(number)`
105
+ call still parses national registration numbers and returns `Company`.
106
+
107
+ Discovery example: `await parse.company.search({ country: 'US', industry: '0700', industry_type: 'sic' });`
108
+
109
+ Supply `industry` and `industry_type` together. The supported namespace is `sic`, with an exact four-digit string such as `0700`; leading zeros are meaningful. Country-only discovery is also supported. Filters intersect and may narrow an existing selector. Country matches the profile country, not a headquarters or operating-presence claim. Unknown values do not match a requested filter. Filter-only candidates use `match.field: "filters"` and `match.value: null`; reuse the same filters and limit with a returned cursor. Counts describe this directory edition, not complete country coverage.
110
+
111
+
112
+
83
113
  One method per endpoint, named after the route.
84
114
 
85
115
  ```ts
@@ -87,9 +117,9 @@ await parse.ip('8.8.8.8');
87
117
  await parse.ip.self();
88
118
  await parse.email('hello@gmail.com');
89
119
  await parse.vat('DE136695976');
90
- await parse.iban('DE89370400440532013000');
91
- await parse.bin('424242');
92
- await parse.npi('1881018208');
120
+ await parse.bank('DE89370400440532013000');
121
+ await parse.card('424242');
122
+ await parse.provider('1881018208');
93
123
  await parse.phone('+14155552671');
94
124
  await parse.carrier('+14155552671');
95
125
  await parse.caller('+14155552671');
@@ -101,6 +131,11 @@ await parse.postal.distance('28202', '10001', { country: 'US' });
101
131
  await parse.address('1600 Pennsylvania Ave NW, Washington DC', { country: 'US' });
102
132
  await parse.address.search('1600 Pennsylvania', { country: 'US', postal: '20500' });
103
133
  await parse.company('51 824 753 556', { country: 'AU' });
134
+ await parse.company.id('co_xw3f22es6cjq', { deep: true });
135
+ await parse.company.search({ domain: 'cloudflare.com' });
136
+ await parse.company.search({ ticker: 'NET', exchange: 'NYSE' });
137
+ await parse.company.search({ identifier: '0001477333', authority: 'SEC' });
138
+ await parse.company.coverage();
104
139
  await parse.city('charlotte', { country: 'US' });
105
140
  await parse.city.id('city_mb8mbqrkz8zb');
106
141
  await parse.city.search('char', { country: 'US', limit: 10 });
@@ -143,16 +178,18 @@ await parse.mx('example.com');
143
178
  await parse.dns('example.com');
144
179
  await parse.dns('_dmarc.example.com', { type: 'TXT' });
145
180
  await parse.useragent(uaString);
146
- await parse.vin('1HGCM82633A004352');
147
- await parse.naics('541511');
148
- await parse.naics.search('coffee shop', { limit: 5 });
181
+ await parse.vehicle('1HGCM82633A004352');
182
+ await parse.industry('541511');
183
+ await parse.industry.search('coffee shop', { limit: 5 });
149
184
  await parse.tariff('8471.30.01.00');
150
185
  await parse.tariff.search('sunglasses');
151
186
  await parse.emoji('rocket');
152
187
  await parse.emoji.search('fire');
153
188
  ```
154
189
 
155
- NAICS paid deep records include classification `deep.exclusions`, each with a description and linked codes. Generic exclusions can have no linked codes. Omitted or null exclusions in older responses remain unknown. Search results also include `match`: the matched `field` (`name`, `term` or `naics`) and `text`, plus `corrections` with `from` and `to` tokens for typo fallback. Corrections are empty for exact, plural and prefix matches. Direct code lookups omit `match`. Older responses may omit it.
190
+ The existing NAICS lookup and search methods remain available as compatibility names for Industry.
191
+
192
+ Industry paid deep records include classification `deep.exclusions`, each with a description and linked codes. Generic exclusions can have no linked codes. Omitted or null exclusions in older responses remain unknown. Search results also include `match`: the matched `field` (`name`, `term` or `naics`) and `text`, plus `corrections` with `from` and `to` tokens for typo fallback. Corrections are empty for exact, plural and prefix matches. Direct code lookups omit `match`. Older responses may omit it.
156
193
 
157
194
  Responses are typed, plain JSON data. `country.states('US')` requests the states directly; it does not fetch a country first. Optional arguments go in the final options object, so new options can be added without changing your existing calls.
158
195
 
@@ -262,6 +299,32 @@ Address search uses context from the form: prefer postal, or city and state. An
262
299
 
263
300
  HLR reports status at the last check. `live` means assigned and `connected` means reachable at that check. Cached results may be returned. Null means unconfirmed. Deep diagnostics stay within the same metered lookup.
264
301
 
302
+ Bank returns core `checks` for input, country, length, structure, checksum and national rules, plus an `issues` list. States are `passed`, `failed`, `not_checked` or `not_supported`. Unsupported national checking is not a failure. `valid` covers the implemented format and checksum rules, not account existence, ownership or payment reachability. Directory names and BICs may be null independently. Older responses may omit `checks` and `issues`, and future states and issue codes remain strings. Pass the original input unchanged so the API can report invalid characters. Deep `account` remains the BBAN remainder.
303
+
304
+ Bank inputs use `POST /bank` JSON bodies, keeping IBAN and account values out of request URLs. Pass original strings; the server owns normalization and validation. Avoid logging request bodies. IBAN deep can include `directory` with the immutable `edition`, resolved `country` and actual `match` grain (`bank`, `branch`, `prefix` or `none`); it is absent if no directory lookup ran. A match does not prove complete country coverage or payment reachability.
305
+
306
+ Use country requirements to build supported input fields. US ACH has an explicit helper with no deep option. It checks the routing format/ABA checksum and account-field syntax; `account_checksum` is `not_supported`. It preserves account characters and leading zeros. A nullable bank name is routing-directory identity, not account existence, ownership or ACH eligibility. The examples below are synthetic test inputs, not payment instructions.
307
+
308
+ ```ts
309
+ await parse.bankRequirements('US', { format: 'us_ach' });
310
+ await parse.bankUsAch({ routing: '011000015', account: '0001234567' });
311
+ ```
312
+
313
+ ## Provider lookup
314
+
315
+ ```ts
316
+ const provider = await parse.provider('1881018208');
317
+ const profile = await parse.provider('1881018208', { deep: true });
318
+ ```
319
+
320
+ Pass the original NPI as a string. `valid` checks its format and checksum; `registered` means a match in the stored NPPES snapshot. `active` reflects recorded NPI deactivation, not licensure. `excluded` is an NPI-only OIG LEIE match; `false` is not a complete exclusion clearance. These directory facts do not verify credentials, current practice contact or payment eligibility.
321
+
322
+ Invalid input returns `valid: false` with unknown provider fields. A checksum-valid number missing from the snapshot returns `registered: false`; unavailable storage remains an API error. Preserve `null` as unknown.
323
+
324
+ The default pooled lookup includes provider identity, specialty and practice contact where held. Paid `deep` adds `deactivated_at`, `medicare`, `opt_out` and `enrollments` from stored source files, with no separate check meter or live verification. `enrollments: null` means unavailable; `[]` means no enrollment rows are returned. The API omits unrequested `deep` and returns `{}` when requested on Free.
325
+
326
+ Paid Deep also returns `taxonomies` in published order, with taxonomy code, specialty label, primary flag and provider-reported license number/state, plus `enumerated_at`, `updated_at` and `reactivated_at` record dates. Reported licenses are not verified licenses. Null lists mean unavailable; empty lists mean the edition contains no entries. Core `sources` is available on every plan: NPPES, LEIE, PECOS and opt-out each have nullable edition metadata (`edition`, `published_at`, `through`, `imported_at`). Provider record dates are separate from source publication and completed import dates. Older responses may omit these additions. Edition details remain null until a verified source is served.
327
+
265
328
  ## Deep
266
329
 
267
330
  Choose enrichment for the question you need answered.
@@ -272,10 +335,11 @@ Choose enrichment for the question you need answered.
272
335
  | Domain | Registration dates, registrar, status and DNSSEC, included with a paid plan. Use `dns` for DNS records and `mx` for mail routing. |
273
336
  | Email | A metered mailbox check with deliverability, catch-all, status, reason and address hints, using included email checks or enabled on-demand usage. |
274
337
  | VAT | A metered registry check where supported, using included VAT checks or enabled on-demand usage. |
275
- | Phone, Time, Date, Currency, Language, Emoji, IBAN, Point | Optional detail in the same pooled request on every plan. |
338
+ | Phone, Time, Date, Currency, Language, Emoji, Bank, Point | Optional detail in the same pooled request on every plan. |
276
339
  | Country, State, District, City, Postal | The place profile on paid plans, including demographic and tax facts where held. |
277
- | Name, NAICS | Name evidence or the industry definition profile on paid plans. |
278
- | VIN, NPI, Tariff, Company | The complete product detail bag on paid plans. |
340
+ | Name, Industry | Name evidence or the industry definition profile on paid plans. |
341
+ | Vehicle, Tariff, Company (national number) | The complete product detail bag on paid plans. |
342
+ | Company directory | Profile detail in the same pooled request on every plan. Search detail belongs to each company result. |
279
343
  | Weather | Specialist current measurements and the existing forecast, alert, air and history bag on paid plans. |
280
344
  | Carrier, HLR | Optional diagnostic detail within the same metered core unit, including Free allowance units. No second gate or additional check. |
281
345
 
@@ -331,16 +395,40 @@ Ordinary lookups retry network failures, 429, and 500/502/503/504 responses twic
331
395
 
332
396
  An explicit `retries` setting on the client or call overrides those defaults. Another attempt can consume additional usage if the earlier response was lost. Cancellation stops the request and any retry wait. Automatic redirects are disabled.
333
397
 
398
+ Automatic retries wait at most five seconds per attempt. A longer valid `Retry-After` returns the original API error immediately without retrying early. Read `retryAfter` on the error for the original header, or null when absent.
399
+
334
400
  ## Docs
335
401
 
336
402
  Full field reference for every endpoint: [parseapi.com/docs](https://parseapi.com/docs)
337
403
 
338
- BIN lookup accepts 6-11 digits as a string, including leading zeros. Spaces and hyphens are accepted. `prefix` is the actual longest match and can be shorter than the input. Unknown reference fields are null. `deep` adds an empty object on every plan.
404
+ ## Card
405
+
406
+ Send 2–11 leading digits as a string. Core returns `bin`, `brand`, `brand_name`
407
+ and a CDN SVG `logo`. Brand detection uses reviewed network rules independently
408
+ of issuer records. Unknown or ambiguous prefixes return null brand fields and a
409
+ generic logo; a known network without reviewed artwork also uses the generic logo.
410
+
411
+ Optional Deep adds `prefix`, `issuer`, `country`, `type` and `prepaid`, included
412
+ in the same pooled request on every plan. Six or more digits enable directory
413
+ matching. Fewer digits return all-null Deep fields. Compare `deep.prefix` with
414
+ `bin`: equal is an exact recorded match; shorter is broader; null is no match.
415
+ The longest row wins, including null fields. `prepaid: null` means unknown, not
416
+ false. This is partial reference data, not card validity or payment acceptance.
417
+
418
+ ```javascript
419
+ const card = await parse.card('51');
420
+ console.log(card.brand, card.logo);
421
+ const details = await parse.card('43737400', { deep: true });
422
+ console.log(details.deep?.prefix, details.deep?.issuer);
423
+ ```
339
424
 
425
+ Leading zeros are preserved. Only ASCII spaces, tabs, CR, LF and hyphens are
426
+ removed; raw input is limited to 64 characters. Invalid prefixes are rejected
427
+ before dispatch, accepted input is forwarded unchanged. Never send a full card number.
340
428
 
341
429
  ## Optional detail
342
430
 
343
- The default response answers the common task. Ask for `deep` when you need more detail about that same result. Core fields stay equal. City, NAICS and Emoji searches put detail inside each result. Postal nearby and distance put metropolitan detail beside the entity it describes. Time conversion keeps target detail in `to.deep`; only the source has `deep.next_dst`.
431
+ The default response answers the common task. Ask for `deep` when you need more detail about that same result. Core fields stay equal. City, Industry and Emoji searches put detail inside each result. Postal nearby and distance put metropolitan detail beside the entity it describes. Time conversion keeps target detail in `to.deep`; only the source has `deep.next_dst`.
344
432
 
345
433
  ```ts
346
434
  const basic = await parse.time('America/New_York');
@@ -361,3 +449,5 @@ Pass a public hostname without a scheme, path, port or IP address. Stack returns
361
449
  Successful checks may be reused for up to 24 hours. `pretty` optionally formats the wire JSON. Stack uses your plan's request allowance and API version 2.0.0 selected by this client.
362
450
 
363
451
  Stack defaults to 35 seconds per attempt so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout applies to every operation; JavaScript also supports a per-call `timeoutMs` override.
452
+
453
+ Vehicle lookups use `vin` as the input and response field. Existing VIN methods remain available for compatibility.
package/dist/index.cjs CHANGED
@@ -24,7 +24,7 @@ __export(index_exports, {
24
24
  parseAPI: () => parseAPI
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
- var VERSION = "1.7.0";
27
+ var VERSION = "1.9.0";
28
28
  var API_VERSION = "2.0.0";
29
29
  var DEFAULT_BASE_URL = "https://api.parseapi.com";
30
30
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -40,13 +40,16 @@ var ParseAPIError = class extends Error {
40
40
  docs;
41
41
  /** Send this if you contact support */
42
42
  requestId;
43
- constructor(status, code, message, docs, requestId) {
43
+ /** Original Retry-After response header, when supplied. */
44
+ retryAfter;
45
+ constructor(status, code, message, docs, requestId, retryAfter = null) {
44
46
  super(message);
45
47
  this.name = "ParseAPIError";
46
48
  this.status = status;
47
49
  this.code = code;
48
50
  this.docs = docs;
49
51
  this.requestId = requestId;
52
+ this.retryAfter = retryAfter;
50
53
  }
51
54
  };
52
55
  function tariffSelection(result, opts) {
@@ -55,6 +58,7 @@ function tariffSelection(result, opts) {
55
58
  }
56
59
  return result;
57
60
  }
61
+ var requestControls = (opts) => ({ signal: opts?.signal, timeoutMs: opts?.timeoutMs, retries: opts?.retries });
58
62
  function env(name) {
59
63
  return typeof process !== "undefined" ? process.env?.[name] : void 0;
60
64
  }
@@ -91,16 +95,20 @@ function metered(path, query) {
91
95
  }
92
96
  function retryDelayMs(attempt, retryAfter) {
93
97
  if (retryAfter) {
94
- const seconds = Number(retryAfter);
95
- if (Number.isFinite(seconds) && seconds >= 0) {
96
- return Math.min(seconds * 1e3, RETRY_AFTER_CAP_MS);
98
+ const value = retryAfter.trim();
99
+ if (/^[0-9]+(?:\.[0-9]+)?$/.test(value)) {
100
+ const seconds = Number(value);
101
+ return seconds > RETRY_AFTER_CAP_MS / 1e3 ? null : seconds * 1e3;
97
102
  }
98
- if (Number.isNaN(seconds)) {
99
- const date = Date.parse(retryAfter);
100
- if (Number.isFinite(date)) return Math.min(Math.max(date - Date.now(), 0), RETRY_AFTER_CAP_MS);
103
+ if (/^[A-Za-z]{3,9},? /.test(value)) {
104
+ const date = Date.parse(value);
105
+ if (Number.isFinite(date)) {
106
+ const delay = Math.max(date - Date.now(), 0);
107
+ return delay > RETRY_AFTER_CAP_MS ? null : delay;
108
+ }
101
109
  }
102
110
  }
103
- return Math.random() * 250 * 2 ** attempt;
111
+ return Math.random() * Math.min(250 * 2 ** Math.min(attempt, 16), RETRY_AFTER_CAP_MS);
104
112
  }
105
113
  function parseAPI(apiKey, options = {}) {
106
114
  const key = apiKey || env("PARSEAPI_KEY");
@@ -133,7 +141,7 @@ function parseAPI(apiKey, options = {}) {
133
141
  const onAbort = () => controller.abort(signal.reason);
134
142
  signal?.addEventListener("abort", onAbort, { once: true });
135
143
  const timer = setTimeout(() => controller.abort(new DOMException("Request timed out", "TimeoutError")), attemptTimeout);
136
- let retryAfter = null;
144
+ let retryDelay;
137
145
  try {
138
146
  let res;
139
147
  try {
@@ -159,8 +167,8 @@ function parseAPI(apiKey, options = {}) {
159
167
  res = void 0;
160
168
  }
161
169
  }
162
- if (res && RETRY_STATUS.has(res.status) && attempt < retries) {
163
- retryAfter = res.headers.get("Retry-After");
170
+ if (res) retryDelay = retryDelayMs(attempt, res.headers.get("Retry-After"));
171
+ if (res && RETRY_STATUS.has(res.status) && attempt < retries && retryDelay !== null) {
164
172
  await res.body?.cancel().catch(() => {
165
173
  });
166
174
  } else if (res) {
@@ -177,14 +185,15 @@ function parseAPI(apiKey, options = {}) {
177
185
  typeof body2.code === "string" ? body2.code : "unknown_error",
178
186
  typeof body2.message === "string" ? body2.message : `Request failed with status ${res.status}`,
179
187
  typeof body2.docs === "string" ? body2.docs : null,
180
- typeof body2.request_id === "string" ? body2.request_id : null
188
+ typeof body2.request_id === "string" ? body2.request_id : null,
189
+ res.headers.get("Retry-After")
181
190
  );
182
191
  }
183
192
  } finally {
184
193
  clearTimeout(timer);
185
194
  signal?.removeEventListener("abort", onAbort);
186
195
  }
187
- await sleep(retryDelayMs(attempt, retryAfter), signal);
196
+ await sleep(retryDelay ?? retryDelayMs(attempt, null), signal);
188
197
  }
189
198
  }
190
199
  const enc = encodeURIComponent;
@@ -198,6 +207,12 @@ function parseAPI(apiKey, options = {}) {
198
207
  for (const [name, value2] of Object.entries(query)) url.searchParams.set(name, String(value2));
199
208
  return !numericJson && url.href.length <= 8e3 ? request("/elevation", query, void 0, opts) : request("/elevation", void 0, void 0, opts, { [selector]: coordinates, ...sampling });
200
209
  }
210
+ const industry = Object.assign(
211
+ (code, opts) => request(`/industry/${enc(code)}`, deepQuery(opts), void 0, opts),
212
+ {
213
+ search: (query, opts) => request("/industry", { q: query, limit: opts?.limit, ...deepQuery(opts) }, void 0, opts)
214
+ }
215
+ );
201
216
  return {
202
217
  /** Estimate task access, capacity and additional charges. Requires a secret key. Reserves no units or money and does not enforce the supplied budget. */
203
218
  preflight: (task, opts) => request("/preflight", void 0, void 0, opts, task),
@@ -270,7 +285,17 @@ function parseAPI(apiKey, options = {}) {
270
285
  search: (query, opts) => request("/address", { q: query, country: opts?.country, postal: opts?.postal, city: opts?.city, state: opts?.state, ip: opts?.ip }, void 0, opts)
271
286
  }
272
287
  ),
273
- company: (number, opts) => request(`/company/${enc(number)}`, { country: opts?.country, ...deepQuery(opts) }, void 0, opts),
288
+ company: Object.assign(
289
+ (number, opts) => request(`/company/${enc(number)}`, { country: opts?.country, ...deepQuery(opts) }, void 0, opts),
290
+ {
291
+ /** Look up a stable directory ID. Requested deep belongs to this profile. */
292
+ id: (id, opts) => request(`/company/id/${enc(id)}`, deepQuery(opts), void 0, requestControls(opts)),
293
+ /** Return candidates without selecting a match. Use one selector, country, exact industry, filters. */
294
+ search: (opts) => request("/company", { q: opts.query, domain: opts.domain, ticker: opts.ticker, identifier: opts.identifier, country: opts.country, industry: opts.industry, industry_type: opts.industry_type, exchange: opts.exchange, authority: opts.authority, limit: opts.limit, cursor: opts.cursor, ...deepQuery(opts) }, void 0, requestControls(opts)),
295
+ /** Describe the directory edition and its counts. Counts do not establish complete country coverage. */
296
+ coverage: (opts) => request("/company/directory/coverage", void 0, void 0, requestControls(opts))
297
+ }
298
+ ),
274
299
  /**
275
300
  * Parse an email and check its format and domain.
276
301
  * `deep: true` explicitly requests a metered deliverability check using included checks or enabled on-demand usage.
@@ -283,8 +308,16 @@ function parseAPI(apiKey, options = {}) {
283
308
  from: opts?.from,
284
309
  ...deepQuery(opts)
285
310
  }, void 0, opts),
311
+ // Keep existing callers on their established HTTP contracts.
286
312
  iban: (iban, opts) => request(`/iban/${enc(iban)}`, { country: opts?.country, ...deepQuery(opts) }, void 0, opts),
313
+ bin: (bin, opts) => request(`/bin/${enc(bin)}`, { deep: opts?.deep }, void 0, opts),
287
314
  npi: (npi, opts) => request(`/npi/${enc(npi)}`, deepQuery(opts), void 0, opts),
315
+ bank: (iban, opts) => request("/bank", void 0, void 0, opts, { iban, country: opts?.country, deep: opts?.deep }),
316
+ /** Check US routing/account syntax using POST. Does not verify ownership, existence or ACH eligibility. */
317
+ bankUsAch: (input, opts) => request("/bank", void 0, void 0, opts, { format: "us_ach", country: "US", routing: input.routing, account: input.account }),
318
+ /** Describe supported input fields and check scope; this does not establish country directory coverage. */
319
+ bankRequirements: (country, opts) => request("/bank/requirements", { country, format: opts?.format }, void 0, opts),
320
+ provider: (npi, opts) => request(`/provider/${enc(npi)}`, deepQuery(opts), void 0, opts),
288
321
  /** Parse a phone number and its formats. Pass country for national numbers when needed. Deep adds numbering-plan geography. */
289
322
  phone: (number, opts) => request(`/phone/${enc(number)}`, { country: opts?.country, ...deepQuery(opts) }, void 0, opts),
290
323
  /** Request a metered carrier lookup. No automatic retries by default. */
@@ -299,8 +332,13 @@ function parseAPI(apiKey, options = {}) {
299
332
  domain: (domain, opts) => request(`/domain/${enc(domain)}`, deepQuery(opts), void 0, opts),
300
333
  asn: (asn, opts) => request(`/asn/${enc(asn)}`, void 0, void 0, opts),
301
334
  mac: (mac, opts) => request(`/mac/${enc(mac)}`, void 0, void 0, opts),
302
- /** Look up a 6-11 digit card prefix. Keep leading zeros in the input string. */
303
- bin: (bin, opts) => request(`/bin/${enc(bin)}`, { deep: opts?.deep }, void 0, opts),
335
+ /** Look up a 2-11 digit card prefix. Keep leading zeros in the input string. */
336
+ card: async (bin, opts) => {
337
+ if (typeof bin !== "string" || bin.length > 64 || !/^[0-9]{2,11}$/.test(bin.replace(/[ \t\r\n-]/g, ""))) {
338
+ throw new TypeError("parseAPI: Card requires a string containing 2 to 11 digits. Send a prefix only.");
339
+ }
340
+ return request(`/card/${enc(bin)}`, deepQuery(opts), void 0, opts);
341
+ },
304
342
  /** Parse a measurement or convert it to `to`. Without `to`, use its type's canonical unit. Invalid input is plain data with `valid: false`. */
305
343
  measure: Object.assign(
306
344
  (measure, opts) => request(`/measure/${enc(measure)}`, { to: opts?.to, locale: opts?.locale, system: opts?.system }, void 0, opts),
@@ -313,14 +351,14 @@ function parseAPI(apiKey, options = {}) {
313
351
  dns: (domain, opts) => request(`/dns/${enc(domain)}`, { type: opts?.type }, void 0, opts),
314
352
  mx: (domain, opts) => request(`/mx/${enc(domain)}`, void 0, void 0, opts),
315
353
  useragent: (ua, opts) => request("/useragent", deepQuery(opts), { "User-Agent": ua }, opts),
354
+ /** Identify a vehicle by VIN. Paid deep adds specifications and model-level recall campaigns. */
355
+ vehicle: (vin, opts) => request(`/vehicle/${enc(vin)}`, deepQuery(opts), void 0, opts),
356
+ /** Compatibility entry for VIN callers. */
316
357
  vin: (vin, opts) => request(`/vin/${enc(vin)}`, deepQuery(opts), void 0, opts),
317
358
  /** US NAICS 2022 definitions and hierarchy. */
318
- naics: Object.assign(
319
- (code, opts) => request(`/naics/${enc(code)}`, deepQuery(opts), void 0, opts),
320
- {
321
- search: (query, opts) => request("/naics", { q: query, limit: opts?.limit, ...deepQuery(opts) }, void 0, opts)
322
- }
323
- ),
359
+ industry,
360
+ /** Compatibility name for industry. */
361
+ naics: industry,
324
362
  /** Look up the general US duty schedule line. Paid deep adds units and the special and other schedule columns. Add origin with deep to resolve country-specific measures. Without origin, schedule detail remains available and origin-dependent fields are null. A null effective rate is not a zero rate. */
325
363
  tariff: Object.assign(
326
364
  (code, opts) => request(`/tariff/${enc(code)}`, { origin: opts?.origin, edition: opts?.edition, date: opts?.date, ...deepQuery(opts) }, void 0, opts).then((result) => tariffSelection(result, opts)),