csv2geo-sdk 1.0.1 → 1.2.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 +6 -1
- package/package.json +2 -2
- package/src/index.d.ts +39 -1
- package/src/index.js +332 -2
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ if (result) {
|
|
|
51
51
|
const { Client } = require('csv2geo-sdk');
|
|
52
52
|
|
|
53
53
|
const client = new Client('your_api_key', {
|
|
54
|
-
baseUrl: 'https://
|
|
54
|
+
baseUrl: 'https://csv2geo.com/api/v1', // optional
|
|
55
55
|
timeout: 30000, // optional, milliseconds
|
|
56
56
|
autoRetry: true, // optional, retry on rate limit
|
|
57
57
|
});
|
|
@@ -203,6 +203,11 @@ Sign up at [csv2geo.com](https://csv2geo.com) to get your API key.
|
|
|
203
203
|
- [API Documentation](https://acenji.github.io/csv2geo-api/docs/)
|
|
204
204
|
- [OpenAPI Specification](https://github.com/acenji/csv2geo-api/blob/main/openapi.yaml)
|
|
205
205
|
|
|
206
|
+
## Community & Support
|
|
207
|
+
|
|
208
|
+
- **Questions & feature requests:** [GitHub Discussions](https://github.com/acenji/csv2geo-api/discussions)
|
|
209
|
+
- **Bug reports:** [GitHub Issues](https://github.com/acenji/csv2geo-api/issues)
|
|
210
|
+
|
|
206
211
|
## License
|
|
207
212
|
|
|
208
213
|
MIT License - see [LICENSE](https://github.com/acenji/csv2geo-api/blob/main/LICENSE) for details.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "csv2geo-sdk",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Node.js SDK for CSV2GEO Geocoding API — 461M+ addresses,
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Node.js SDK for CSV2GEO Geocoding API — 461M+ addresses, 39 countries, 42+ endpoints. Forward, reverse, batch, places, divisions (incl. postcode → boundary), IP geolocation with county overlay, coverage, autocomplete. 3,000 free requests/day.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"files": [
|
package/src/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export interface ClientOptions {
|
|
6
|
-
/** API base URL (default: https://
|
|
6
|
+
/** API base URL (default: https://csv2geo.com/api/v1) */
|
|
7
7
|
baseUrl?: string;
|
|
8
8
|
/** Request timeout in milliseconds (default: 30000) */
|
|
9
9
|
timeout?: number;
|
|
@@ -105,6 +105,44 @@ export class Client {
|
|
|
105
105
|
* @returns Array of responses
|
|
106
106
|
*/
|
|
107
107
|
reverseBatch(coordinates: Coordinate[]): Promise<GeocodeResponse[]>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* IP geolocation. Returns country/region/city/postcode/location/timezone/ISP,
|
|
111
|
+
* plus county + locality + confidence for residential IPs (Sprint 2.7).
|
|
112
|
+
* @param ip IPv4 or IPv6 string
|
|
113
|
+
*/
|
|
114
|
+
ip(ip: string): Promise<IPGeoResponse>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Like {@link ip}, but uses the requester's IP.
|
|
118
|
+
*/
|
|
119
|
+
ipMe(): Promise<IPGeoResponse>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Batch IP lookup. Up to 1000 IPs per call.
|
|
123
|
+
*/
|
|
124
|
+
ipBatch(ips: string[]): Promise<{ results: IPGeoResponse[] }>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface IPGeoResponse {
|
|
128
|
+
ip: string;
|
|
129
|
+
country?: { code: string; name?: string; wikidata?: string };
|
|
130
|
+
region?: { code?: string; name?: string };
|
|
131
|
+
city?: { name: string };
|
|
132
|
+
postcode?: string;
|
|
133
|
+
location?: {
|
|
134
|
+
latitude: number;
|
|
135
|
+
longitude: number;
|
|
136
|
+
accuracy_radius_km: number;
|
|
137
|
+
};
|
|
138
|
+
timezone?: string;
|
|
139
|
+
isp?: { asn?: number; name?: string };
|
|
140
|
+
county?: { name: string; subtype?: string; wikidata?: string };
|
|
141
|
+
locality?: { name: string; subtype?: string; wikidata?: string };
|
|
142
|
+
confidence?: 'high' | 'medium' | 'low';
|
|
143
|
+
accuracy_disclaimer?: string;
|
|
144
|
+
source: string;
|
|
145
|
+
db_build_at?: string;
|
|
108
146
|
}
|
|
109
147
|
|
|
110
148
|
export class CSV2GEOError extends Error {
|
package/src/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const {
|
|
|
20
20
|
APIError,
|
|
21
21
|
} = require('./errors');
|
|
22
22
|
|
|
23
|
-
const DEFAULT_BASE_URL = 'https://
|
|
23
|
+
const DEFAULT_BASE_URL = 'https://csv2geo.com/api/v1';
|
|
24
24
|
const DEFAULT_TIMEOUT = 30000;
|
|
25
25
|
const MAX_RETRIES = 3;
|
|
26
26
|
|
|
@@ -75,7 +75,7 @@ class Client {
|
|
|
75
75
|
headers: {
|
|
76
76
|
'Authorization': `Bearer ${this.apiKey}`,
|
|
77
77
|
'Content-Type': 'application/json',
|
|
78
|
-
'User-Agent': 'csv2geo-node/1.
|
|
78
|
+
'User-Agent': 'csv2geo-node/1.2.0',
|
|
79
79
|
},
|
|
80
80
|
signal: controller.signal,
|
|
81
81
|
};
|
|
@@ -260,6 +260,286 @@ class Client {
|
|
|
260
260
|
}));
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
// ─────────────────────────────────────────────────────────
|
|
264
|
+
// Address Tools
|
|
265
|
+
// ─────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/** Validate an address. GET /validate */
|
|
268
|
+
async validate(address, options = {}) {
|
|
269
|
+
const params = { q: address };
|
|
270
|
+
if (options.country) params.country = options.country;
|
|
271
|
+
return this._request('GET', '/validate', params);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Validate up to 10,000 addresses. POST /validate */
|
|
275
|
+
async validateBatch(addresses) {
|
|
276
|
+
if (addresses.length > 10000) throw new InvalidRequestError('Max 10,000 per batch');
|
|
277
|
+
return this._request('POST', '/validate', {}, { addresses });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Address autocomplete suggestions. GET /autocomplete */
|
|
281
|
+
async autocomplete(query, options = {}) {
|
|
282
|
+
const params = { q: query };
|
|
283
|
+
if (options.country) params.country = options.country;
|
|
284
|
+
if (options.limit) params.limit = options.limit;
|
|
285
|
+
return this._request('GET', '/autocomplete', params);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Parse a free-form address into components. GET /parse */
|
|
289
|
+
async parse(address) {
|
|
290
|
+
return this._request('GET', '/parse', { q: address });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Parse up to 10,000 addresses. POST /parse */
|
|
294
|
+
async parseBatch(addresses) {
|
|
295
|
+
if (addresses.length > 10000) throw new InvalidRequestError('Max 10,000 per batch');
|
|
296
|
+
return this._request('POST', '/parse', {}, { addresses });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Standardize an address. GET /standardize */
|
|
300
|
+
async standardize(address) {
|
|
301
|
+
return this._request('GET', '/standardize', { q: address });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Score similarity between two addresses. GET /addresses/compare */
|
|
305
|
+
async compareAddresses(address1, address2) {
|
|
306
|
+
return this._request('GET', '/addresses/compare', { a: address1, b: address2 });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ─────────────────────────────────────────────────────────
|
|
310
|
+
// Address inspection
|
|
311
|
+
// ─────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
/** Find addresses within radius of a coordinate. GET /addresses/nearby */
|
|
314
|
+
async addressesNearby(lat, lng, options = {}) {
|
|
315
|
+
const params = { lat, lng, radius: options.radius || 200 };
|
|
316
|
+
if (options.limit) params.limit = options.limit;
|
|
317
|
+
return this._request('GET', '/addresses/nearby', params);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Get all addresses on a street. GET /addresses/street */
|
|
321
|
+
async addressesStreet(country, city, street) {
|
|
322
|
+
return this._request('GET', '/addresses/street', { country, city, street });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Address counts. GET /addresses/stats */
|
|
326
|
+
async addressesStats(country) {
|
|
327
|
+
const params = {};
|
|
328
|
+
if (country) params.country = country;
|
|
329
|
+
return this._request('GET', '/addresses/stats', params);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Random sample of addresses. GET /addresses/random */
|
|
333
|
+
async addressesRandom(options = {}) {
|
|
334
|
+
const params = { limit: options.limit || 1 };
|
|
335
|
+
if (options.country) params.country = options.country;
|
|
336
|
+
return this._request('GET', '/addresses/random', params);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Interpolate a coordinate from address-range data. GET /addresses/interpolate */
|
|
340
|
+
async addressesInterpolate(country, city, street, houseNumber) {
|
|
341
|
+
return this._request('GET', '/addresses/interpolate',
|
|
342
|
+
{ country, city, street, house_number: houseNumber });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Find the intersection of two streets. GET /addresses/crossstreet */
|
|
346
|
+
async addressesCrossstreet(country, city, streetA, streetB) {
|
|
347
|
+
return this._request('GET', '/addresses/crossstreet',
|
|
348
|
+
{ country, city, street_a: streetA, street_b: streetB });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ─────────────────────────────────────────────────────────
|
|
352
|
+
// Places
|
|
353
|
+
// ─────────────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
/** Search places (POIs). GET /places */
|
|
356
|
+
async places(options = {}) {
|
|
357
|
+
const params = {};
|
|
358
|
+
if (options.q || options.query) params.q = options.q || options.query;
|
|
359
|
+
if (options.country) params.country = options.country;
|
|
360
|
+
if (options.category) params.category = options.category;
|
|
361
|
+
if (options.limit) params.limit = options.limit;
|
|
362
|
+
return this._request('GET', '/places', params);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Places within radius of a coordinate. GET /places/nearby */
|
|
366
|
+
async placesNearby(lat, lng, options = {}) {
|
|
367
|
+
const params = { lat, lng, radius: options.radius || 200 };
|
|
368
|
+
if (options.category) params.category = options.category;
|
|
369
|
+
if (options.limit) params.limit = options.limit;
|
|
370
|
+
return this._request('GET', '/places/nearby', params);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** List all place categories. GET /places/categories */
|
|
374
|
+
async placesCategories() {
|
|
375
|
+
return this._request('GET', '/places/categories');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Random places. GET /places/random */
|
|
379
|
+
async placesRandom(options = {}) {
|
|
380
|
+
const params = { limit: options.limit || 1 };
|
|
381
|
+
if (options.country) params.country = options.country;
|
|
382
|
+
if (options.category) params.category = options.category;
|
|
383
|
+
return this._request('GET', '/places/random', params);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Places counts. GET /places/stats */
|
|
387
|
+
async placesStats(country) {
|
|
388
|
+
const params = {};
|
|
389
|
+
if (country) params.country = country;
|
|
390
|
+
return this._request('GET', '/places/stats', params);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** List brand-tagged places. GET /places/brands */
|
|
394
|
+
async placesBrands(country) {
|
|
395
|
+
const params = {};
|
|
396
|
+
if (country) params.country = country;
|
|
397
|
+
return this._request('GET', '/places/brands', params);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** All locations of a brand/chain. GET /places/chain */
|
|
401
|
+
async placesChain(brand, country) {
|
|
402
|
+
const params = { brand };
|
|
403
|
+
if (country) params.country = country;
|
|
404
|
+
return this._request('GET', '/places/chain', params);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Count places matching filter. GET /places/count */
|
|
408
|
+
async placesCount(options = {}) {
|
|
409
|
+
const params = {};
|
|
410
|
+
if (options.country) params.country = options.country;
|
|
411
|
+
if (options.category) params.category = options.category;
|
|
412
|
+
return this._request('GET', '/places/count', params);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Places similar to a given one. GET /places/similar */
|
|
416
|
+
async placesSimilar(placeId, options = {}) {
|
|
417
|
+
const params = { id: placeId };
|
|
418
|
+
if (options.limit) params.limit = options.limit;
|
|
419
|
+
return this._request('GET', '/places/similar', params);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Batch nearby-places lookup. POST /places/batch */
|
|
423
|
+
async placesBatch(coordinates, options = {}) {
|
|
424
|
+
if (coordinates.length > 10000) throw new InvalidRequestError('Max 10,000 per batch');
|
|
425
|
+
const body = { coordinates, radius: options.radius || 200 };
|
|
426
|
+
if (options.category) body.category = options.category;
|
|
427
|
+
return this._request('POST', '/places/batch', {}, body);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Single place by id. GET /places/{id} */
|
|
431
|
+
async placeById(placeId) {
|
|
432
|
+
return this._request('GET', `/places/${encodeURIComponent(placeId)}`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ─────────────────────────────────────────────────────────
|
|
436
|
+
// Divisions (Sprint 1 — postcode boundary)
|
|
437
|
+
// ─────────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
/** Search administrative divisions. GET /divisions */
|
|
440
|
+
async divisionsSearch(options = {}) {
|
|
441
|
+
const params = {};
|
|
442
|
+
if (options.q || options.query) params.q = options.q || options.query;
|
|
443
|
+
if (options.country) params.country = options.country;
|
|
444
|
+
if (options.subtype) params.subtype = options.subtype;
|
|
445
|
+
if (options.limit) params.limit = options.limit;
|
|
446
|
+
return this._request('GET', '/divisions', params);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** Point-in-polygon: divisions containing a point. GET /divisions/contains */
|
|
450
|
+
async divisionsContains(lat, lng) {
|
|
451
|
+
return this._request('GET', '/divisions/contains', { lat, lng });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Postcode → boundary (bbox + optional polygon + population + wikidata).
|
|
456
|
+
* GET /divisions/by-postcode
|
|
457
|
+
*
|
|
458
|
+
* @param {string} code - Postcode (e.g. "90210", "SW1A 1AA")
|
|
459
|
+
* @param {string} country - ISO 3166-1 alpha-2
|
|
460
|
+
* @param {Object} [options]
|
|
461
|
+
* @param {string} [options.include] - "geometry" to include polygon
|
|
462
|
+
* @param {string} [options.precision] - "simplified" (default) or "full"
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* const r = await client.divisionsByPostcode('90210', 'US', { include: 'geometry' });
|
|
466
|
+
* console.log(r.result.population, r.result.bbox);
|
|
467
|
+
*/
|
|
468
|
+
async divisionsByPostcode(code, country, options = {}) {
|
|
469
|
+
const params = { code, country };
|
|
470
|
+
if (options.include) params.include = options.include;
|
|
471
|
+
if (options.precision) params.precision = options.precision;
|
|
472
|
+
return this._request('GET', '/divisions/by-postcode', params);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** List available division subtypes. GET /divisions/subtypes */
|
|
476
|
+
async divisionsSubtypes() {
|
|
477
|
+
return this._request('GET', '/divisions/subtypes');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** List countries with division coverage. GET /divisions/countries */
|
|
481
|
+
async divisionsCountries() {
|
|
482
|
+
return this._request('GET', '/divisions/countries');
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Division counts. GET /divisions/stats */
|
|
486
|
+
async divisionsStats(country) {
|
|
487
|
+
const params = {};
|
|
488
|
+
if (country) params.country = country;
|
|
489
|
+
return this._request('GET', '/divisions/stats', params);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Random divisions. GET /divisions/random */
|
|
493
|
+
async divisionsRandom(options = {}) {
|
|
494
|
+
const params = { limit: options.limit || 1 };
|
|
495
|
+
if (options.country) params.country = options.country;
|
|
496
|
+
if (options.subtype) params.subtype = options.subtype;
|
|
497
|
+
return this._request('GET', '/divisions/random', params);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Full parent/child chain for a division. GET /divisions/hierarchy/{id} */
|
|
501
|
+
async divisionHierarchy(divisionId) {
|
|
502
|
+
return this._request('GET', `/divisions/hierarchy/${encodeURIComponent(divisionId)}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Single division by id. GET /divisions/{id} */
|
|
506
|
+
async divisionById(divisionId) {
|
|
507
|
+
return this._request('GET', `/divisions/${encodeURIComponent(divisionId)}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ─────────────────────────────────────────────────────────
|
|
511
|
+
// Coverage
|
|
512
|
+
// ─────────────────────────────────────────────────────────
|
|
513
|
+
|
|
514
|
+
/** Live tier-by-country coverage matrix. GET /coverage */
|
|
515
|
+
async coverage() {
|
|
516
|
+
return this._request('GET', '/coverage');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** Aggregate coverage totals. GET /coverage-stats */
|
|
520
|
+
async coverageStats() {
|
|
521
|
+
return this._request('GET', '/coverage-stats');
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ─────────────────────────────────────────────────────────
|
|
525
|
+
// Utilities
|
|
526
|
+
// ─────────────────────────────────────────────────────────
|
|
527
|
+
|
|
528
|
+
/** Timezone at a coordinate. GET /timezone */
|
|
529
|
+
async timezone(lat, lng) {
|
|
530
|
+
return this._request('GET', '/timezone', { lat, lng });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Great-circle distance between two coordinates. GET /distance */
|
|
534
|
+
async distance(lat1, lng1, lat2, lng2) {
|
|
535
|
+
return this._request('GET', '/distance', { lat1, lng1, lat2, lng2 });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Service health check. GET /health */
|
|
539
|
+
async health() {
|
|
540
|
+
return this._request('GET', '/health');
|
|
541
|
+
}
|
|
542
|
+
|
|
263
543
|
/**
|
|
264
544
|
* Parse API result into GeocodeResult
|
|
265
545
|
* @private
|
|
@@ -283,6 +563,56 @@ class Client {
|
|
|
283
563
|
},
|
|
284
564
|
};
|
|
285
565
|
}
|
|
566
|
+
|
|
567
|
+
// ─────────────────────────────────────────────────────────
|
|
568
|
+
// IP geolocation (Sprint 2.7)
|
|
569
|
+
//
|
|
570
|
+
// MaxMind GeoLite2 .mmdb lookup with our county overlay.
|
|
571
|
+
// Bundled into every plan (including Free); no separate billing.
|
|
572
|
+
// ─────────────────────────────────────────────────────────
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* IP → geolocation. Returns country, region, city, postcode, location,
|
|
576
|
+
* timezone, ISP, and (for residential IPs) county + locality + confidence.
|
|
577
|
+
*
|
|
578
|
+
* @param {string} ip - IPv4 or IPv6 address (e.g. "8.8.8.8")
|
|
579
|
+
* @returns {Promise<Object>} canonical /v1/ip response shape
|
|
580
|
+
* @example
|
|
581
|
+
* const r = await client.ip('8.8.8.8');
|
|
582
|
+
* console.log(r.country.code, r.county?.name, r.confidence);
|
|
583
|
+
*/
|
|
584
|
+
async ip(ip) {
|
|
585
|
+
if (!ip || typeof ip !== 'string') {
|
|
586
|
+
throw new InvalidRequestError('ip must be a non-empty string');
|
|
587
|
+
}
|
|
588
|
+
return this._request('GET', '/ip', { ip });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Like {@link ip}, but uses the requester's IP (the one Laravel sees).
|
|
593
|
+
* Useful from browser/server contexts where you want "where is the caller".
|
|
594
|
+
*
|
|
595
|
+
* @returns {Promise<Object>} canonical /v1/ip response shape
|
|
596
|
+
*/
|
|
597
|
+
async ipMe() {
|
|
598
|
+
return this._request('GET', '/ip/me');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Batch IP lookup. Up to 1000 IPs per call.
|
|
603
|
+
*
|
|
604
|
+
* @param {string[]} ips - array of IPs
|
|
605
|
+
* @returns {Promise<{results: Object[]}>}
|
|
606
|
+
*/
|
|
607
|
+
async ipBatch(ips) {
|
|
608
|
+
if (!Array.isArray(ips) || ips.length === 0) {
|
|
609
|
+
throw new InvalidRequestError('ips must be a non-empty array');
|
|
610
|
+
}
|
|
611
|
+
if (ips.length > 1000) {
|
|
612
|
+
throw new InvalidRequestError('ipBatch supports at most 1000 IPs per call');
|
|
613
|
+
}
|
|
614
|
+
return this._request('POST', '/ip/batch', {}, { ips });
|
|
615
|
+
}
|
|
286
616
|
}
|
|
287
617
|
|
|
288
618
|
module.exports = {
|