parseapi 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6598fb8f4a05494418599738f387ab8137099ddb12ac09f63ca42410d6b4b89
4
- data.tar.gz: 9de6f2540a617508c1c9a298361db8218e5f0460fb4a03e1e2c04a85179badca
3
+ metadata.gz: b27f8a145b9ad37ccd82a61e8e175665416036116ed037bb68640d5bd17d3d5c
4
+ data.tar.gz: 216cee2f23076dbe2cdcb1339313a157356d63cae236c2204d795fa17242cda7
5
5
  SHA512:
6
- metadata.gz: 2599c0832bed2f4306ac4f1b5eb31a2d4757546a7349178a8c7bd693605ee091b107cec97c2005d0ed002afa9e2758f0b22d86c679fd15efffad1bd6fa0453d4
7
- data.tar.gz: 3d6a7f151089a85c0e76624f84cdd788df87a329371669505efd3179ec98c682017248e969072949f3975c70687ae4d9dfc766fec0949211df22a89374c80533
6
+ metadata.gz: 63b2f6b437f1c2e77b8d39a7da11b497a473616f22ac819cc838253495f1ec2b9b146a5f7f6f89025df9a2bc66e18b58a7037b9156bdfc84bc39116280757df2
7
+ data.tar.gz: 3fd6e9ef3a55d5c35a7ae693573b17b1fe84aa41936e2836de0def9c1819f0347ee8d49bbff0718d794fc279a26a7b4520d3383805f6e56ca83b6ad806808ec6
data/README.md CHANGED
@@ -13,7 +13,7 @@ Get a key at [parseapi.com](https://parseapi.com). The client also reads `PARSEA
13
13
 
14
14
  ## API versions
15
15
 
16
- 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.
16
+ 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.
17
17
 
18
18
  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.
19
19
 
@@ -44,6 +44,35 @@ Results are plain data. Pass a returned code or coordinate to another operation
44
44
 
45
45
  Use `parse.postal('28202', country: 'US', deep: true)` for US ZIP tax references. `deep.tax` names the levy and `deep.tax_rate` is a percentage, so `7.9` means 7.9%. The state, county, city and special components explain that combined rate. An exact address can differ. Country and state lookups provide their own geographic reference rates, which should not be added to the ZIP rate. `nil` means unknown and `0` means known zero. Country `deep.tax_id_format` and `deep.tax_id_regex` describe registration-number format only. Use `vat` for a metered registration check with `deep` explicitly enabled.
46
46
 
47
+ ## Company directory
48
+
49
+ Find candidates, then fetch the profile you selected.
50
+
51
+ ```ruby
52
+ candidates = parse.company_search(query: 'GitLab', country: 'US', limit: 5)
53
+ selected_id = 'co_caczn6wf36hj' # Explicitly chosen after reviewing candidates.
54
+ profile = parse.company_id(selected_id, deep: true)
55
+ unless candidates['next'].nil?
56
+ next_page = parse.company_search(
57
+ query: 'GitLab', country: 'US', limit: 5, cursor: candidates['next']
58
+ )
59
+ end
60
+ coverage = parse.company_coverage
61
+ ```
62
+
63
+ Use at most one selector: `query` for a name, `domain`, `ticker`, or `identifier`; country or exact industry filters also allow discovery without a selector. The API validates selectors and filters. Use `country` to scope candidates, `exchange` with a ticker, and `authority` with an identifier. Search returns `companies` and an opaque `next` cursor. Review candidate identity and match details before choosing a stable Company ID. Pass `next` as `cursor` with the same selector, filters and limit to continue that result set.
64
+
65
+ Directory profiles are plain JSON data. `deep` belongs to each company in search results and adds legal/reference detail plus nullable `description`, `logo`, `founded`, and the `social_profiles`, `phone_numbers`, `email_addresses`, `domains` and `sources` collections. A logo is a reported URL. `founded` has `value` and `precision`, distinct from incorporation. Sources identify the website, filing or business register, supported fields and observation/update timestamps. Employee observations retain count, measurement date, organization scope and approximation; null means unknown. Missing, null, empty and unknown fields retain their response values. An empty search is a successful result. Invalid inputs and unknown IDs raise the existing API error.
66
+
67
+ 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.
68
+
69
+ The recipe requests one candidate page, one explicitly chosen profile and directory coverage, plus a second page when a cursor is returned. Each operation uses the existing retry settings. The number-validation call remains unchanged. `lang` applies to national company-number lookup, while directory calls use the source labels.
70
+
71
+ Discovery example: `parse.company_search(country: 'US', industry: '0700', industry_type: 'sic')`
72
+
73
+ 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.
74
+
75
+
47
76
  ## Calls
48
77
 
49
78
  One method per endpoint, named after the route.
@@ -53,9 +82,9 @@ parse.ip('8.8.8.8')
53
82
  parse.ip_self
54
83
  parse.email('hello@gmail.com')
55
84
  parse.vat('DE136695976')
56
- parse.iban('DE89370400440532013000')
57
- parse.bin('424242')
58
- parse.npi('1881018208')
85
+ parse.bank('DE89370400440532013000')
86
+ parse.card('424242')
87
+ parse.provider('1881018208')
59
88
  parse.phone('+14155552671')
60
89
  parse.carrier('+14155552671')
61
90
  parse.caller('+14155552671')
@@ -67,6 +96,11 @@ parse.postal_distance('28202', '10001', country: 'US')
67
96
  parse.address('1600 Pennsylvania Ave NW, Washington, DC 20500', country: 'US')
68
97
  parse.address_search('123 main', country: 'US', postal: '27401')
69
98
  parse.company('732829320', country: 'FR')
99
+ parse.company_id('co_caczn6wf36hj', deep: true)
100
+ parse.company_search(domain: 'about.gitlab.com')
101
+ parse.company_search(ticker: 'GTLB', exchange: 'Nasdaq')
102
+ parse.company_search(identifier: '0001653482', authority: 'sec')
103
+ parse.company_coverage
70
104
  parse.city('charlotte', country: 'US')
71
105
  parse.city_id('city_mb8mbqrkz8zb')
72
106
  parse.city_search('char', country: 'US', limit: 10)
@@ -107,16 +141,18 @@ parse.mx('example.com')
107
141
  parse.dns('example.com')
108
142
  parse.dns('_dmarc.example.com', type: 'TXT')
109
143
  parse.useragent(ua_string)
110
- parse.vin('1HGCM82633A004352')
111
- parse.naics('541511')
112
- parse.naics_search('coffee shop', limit: 5)
144
+ parse.vehicle('1HGCM82633A004352')
145
+ parse.industry('541511')
146
+ parse.industry_search('coffee shop', limit: 5)
113
147
  parse.tariff('8471.30.01.00', origin: 'CN', deep: true)
114
148
  parse.tariff_search('sunglasses')
115
149
  parse.emoji('rocket')
116
150
  parse.emoji_search('fire')
117
151
  ```
118
152
 
119
- 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.
153
+ The existing NAICS lookup and search methods remain available as compatibility names for Industry.
154
+
155
+ 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.
120
156
 
121
157
  Each lookup returns a plain hash with string keys. Related lookups are separate calls, such as `country_states('US')`. Reading the result makes no further requests. New response fields and `nil` values are preserved.
122
158
 
@@ -132,7 +168,7 @@ Choose display names for one request:
132
168
  parse.country('DE', lang: 'fr')
133
169
  ```
134
170
 
135
- `lang` is optional on geography lookups and their lists/searches, Currency lookup, Language, Date, Time/Timezone, Emoji lookup/search, and unit discovery. IP, ASN, Company and NPI also accept it for their geographic labels. Codes, native names, quantities and response structure retain their meanings. Source coverage determines which labels are translated; unavailable labels use the API's documented fallback.
171
+ `lang` is optional on geography lookups and their lists/searches, Currency lookup, Language, Date, Time/Timezone, Emoji lookup/search, and unit discovery. IP, ASN, national Company number lookup and NPI also accept it for their geographic labels. Codes, native names, quantities and response structure retain their meanings. Source coverage determines which labels are translated; unavailable labels use the API's documented fallback.
136
172
 
137
173
  The next call keeps its usual default unless it also supplies `lang`. Existing `deep` rules still apply. Date `format` and measurement `locale` remain explicit input-parsing controls.
138
174
 
@@ -235,6 +271,32 @@ Address search uses context from the form: prefer postal, or city and state. An
235
271
 
236
272
  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.
237
273
 
274
+ 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.
275
+
276
+ 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.
277
+
278
+ 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.
279
+
280
+ ```ruby
281
+ parse.bank_requirements('US', format: 'us_ach')
282
+ parse.bank_us_ach(routing: '011000015', account: '0001234567')
283
+ ```
284
+
285
+ ## Provider lookup
286
+
287
+ ```ruby
288
+ provider = parse.provider('1881018208')
289
+ profile = parse.provider('1881018208', deep: true)
290
+ ```
291
+
292
+ 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.
293
+
294
+ 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.
295
+
296
+ 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.
297
+
298
+ 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.
299
+
238
300
  ## Deep
239
301
 
240
302
  Choose enrichment for the question you need answered.
@@ -245,10 +307,11 @@ Choose enrichment for the question you need answered.
245
307
  | Domain | Registration dates, registrar, status and DNSSEC, included with a paid plan. Use `dns` for DNS records and `mx` for mail routing. |
246
308
  | Email | A metered mailbox check with deliverability, catch-all, status, reason and address hints, using included email checks or enabled on-demand usage. |
247
309
  | VAT | A metered registry check where supported, using included VAT checks or enabled on-demand usage. |
248
- | Phone, Time, Date, Currency, Language, Emoji, IBAN, Point | Optional detail in the same pooled request on every plan. |
310
+ | Phone, Time, Date, Currency, Language, Emoji, Bank, Point | Optional detail in the same pooled request on every plan. |
249
311
  | Country, State, District, City, Postal | The place profile on paid plans, including demographic and tax facts where held. |
250
- | Name, NAICS | Name evidence or the industry definition profile on paid plans. |
251
- | VIN, NPI, Tariff, Company | The complete product detail bag on paid plans. |
312
+ | Name, Industry | Name evidence or the industry definition profile on paid plans. |
313
+ | NPI | Deactivation date, Medicare enrollment, opt-out and enrollment rows from stored sources on paid plans. Exclusion evidence stays core. |
314
+ | Vehicle, Tariff, Company | The complete product detail bag on paid plans. |
252
315
  | Weather | Specialist current measurements and the existing forecast, alert, air and history bag on paid plans. |
253
316
  | Carrier, HLR | Optional diagnostic detail within the same metered core unit, including Free allowance units. No second gate or additional check. |
254
317
 
@@ -267,7 +330,7 @@ ip.dig('deep', 'datacenter') # true, false, or nil
267
330
 
268
331
  ## Errors
269
332
 
270
- Every non-2xx response raises `ParseAPI::Error` with `status`, `code`, `docs`, and `request_id`. Branch on `code`.
333
+ Every non-2xx response raises `ParseAPI::Error` with `status`, `code`, `docs`, and `request_id`, plus nullable `retry_after` header metadata. Branch on `code`.
271
334
 
272
335
  ```ruby
273
336
  begin
@@ -292,11 +355,13 @@ Ordinary lookups retry network errors and HTTP 429, 500, 502, 503, and 504 up to
292
355
 
293
356
  Pass `retries: 0` to make every lookup a single attempt. An explicit count such as `retries: 2` applies to every lookup, including paid ones. A retried request can count toward usage even when the first response was lost. Omit `retries` or pass `nil` to use the defaults above.
294
357
 
358
+ Automatic retries honor numeric and HTTP-date `Retry-After` values up to five seconds. A longer server wait returns the original API error immediately, with the raw header in `retry_after`, so the application can schedule a later attempt. Missing or invalid headers use ordinary backoff.
359
+
295
360
  Reuse one client for successive lookups. Call `parse.close` to release its connection when finished. A later lookup opens a new connection. Use a separate client in each concurrent thread.
296
361
 
297
362
  Network failures raise native Ruby exceptions. An invalid JSON response raises `JSON::ParserError`.
298
363
 
299
- For testing or instrumentation, pass a callable as `transport:`. It receives the URL and request-header hash and returns `[status, lowercase_response_headers, body]`. Custom transports should use the supplied headers and keep redirect following disabled.
364
+ For testing or instrumentation, pass a callable as `transport:`. For GET it receives the URL and request-header hash. For Bank POST it also receives a third `"POST"` argument and fourth serialized JSON body argument. Accept optional method/body parameters when supplying a custom transport. It returns `[status, lowercase_response_headers, body]`. Custom transports should use the supplied headers and keep redirect following disabled.
300
365
 
301
366
  Requires Ruby 3.0 or later. Standard library only, zero dependencies.
302
367
 
@@ -304,12 +369,34 @@ Requires Ruby 3.0 or later. Standard library only, zero dependencies.
304
369
 
305
370
  Full field reference for every endpoint: [parseapi.com/docs](https://parseapi.com/docs)
306
371
 
307
- 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.
372
+ ## Card
373
+
374
+ Send 2–11 leading digits as a string. Core returns `bin`, `brand`, `brand_name`
375
+ and a CDN SVG `logo`. Brand detection uses reviewed network rules independently
376
+ of issuer records. Unknown or ambiguous prefixes return null brand fields and a
377
+ generic logo; a known network without reviewed artwork also uses the generic logo.
378
+
379
+ Optional Deep adds `prefix`, `issuer`, `country`, `type` and `prepaid`, included
380
+ in the same pooled request on every plan. Six or more digits enable directory
381
+ matching. Fewer digits return all-null Deep fields. Compare `deep.prefix` with
382
+ `bin`: equal is an exact recorded match; shorter is broader; null is no match.
383
+ The longest row wins, including null fields. `prepaid: null` means unknown, not
384
+ false. This is partial reference data, not card validity or payment acceptance.
308
385
 
386
+ ```ruby
387
+ card = parse.card("51")
388
+ puts card["logo"]
389
+ details = parse.card("43737400", deep: true)
390
+ issuer = details["deep"]["issuer"]
391
+ ```
392
+
393
+ Leading zeros are preserved. Only ASCII spaces, tabs, CR, LF and hyphens are
394
+ removed; raw input is limited to 64 characters. Invalid prefixes are rejected
395
+ before dispatch, accepted input is forwarded unchanged. Never send a full card number.
309
396
 
310
397
  ## Optional detail
311
398
 
312
- 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`.
399
+ The default response answers the common task. Ask for `deep` when you need more detail about that same result. Core fields stay equal. City, Company directory, 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` or each `targets` item; only the source has `deep.next_dst`.
313
400
 
314
401
  ```ruby
315
402
  basic = parse.time('America/New_York')
@@ -330,3 +417,5 @@ Pass a public hostname without a scheme, path, port or IP address. Stack returns
330
417
  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.
331
418
 
332
419
  Stack defaults to a 35-second transport timeout so a first scan has time to finish. Other lookups retain their 10-second default. An explicit client timeout takes precedence.
420
+
421
+ Vehicle lookups use `vin` as the input and response field. Existing VIN methods remain available for compatibility.
@@ -6,14 +6,15 @@ require 'time'
6
6
  module ParseAPI
7
7
  # Every non-2xx response from the API. Branch on +code+, never on the message.
8
8
  class Error < StandardError
9
- attr_reader :status, :code, :docs, :request_id
9
+ attr_reader :status, :code, :docs, :request_id, :retry_after
10
10
 
11
- def initialize(status:, code:, message:, docs: nil, request_id: nil)
11
+ def initialize(status:, code:, message:, docs: nil, request_id: nil, retry_after: nil)
12
12
  super(message)
13
13
  @status = status
14
14
  @code = code
15
15
  @docs = docs
16
16
  @request_id = request_id
17
+ @retry_after = retry_after
17
18
  end
18
19
  end
19
20
 
@@ -155,6 +156,23 @@ module ParseAPI
155
156
  get("/company/#{seg(number)}", country: country, deep: deep, lang: lang)
156
157
  end
157
158
 
159
+ # Fetch a directory profile by its stable Company ID.
160
+ def company_id(id, deep: false)
161
+ get("/company/id/#{seg(id)}", deep: deep)
162
+ end
163
+
164
+ # Use at most one selector, or discover by country, industry or selected registration. The API
165
+ # validates filters; pair the four-digit industry string with industry_type: "sic". Pass a
166
+ # returned cursor with the same selector and filters. Deep belongs to each result.
167
+ def company_search(query: nil, domain: nil, ticker: nil, identifier: nil, country: nil, exchange: nil, authority: nil, limit: nil, cursor: nil, deep: false, industry: nil, industry_type: nil)
168
+ get('/company', q: query, domain: domain, ticker: ticker, identifier: identifier, country: country, exchange: exchange, authority: authority, limit: limit, cursor: cursor, deep: deep, industry: industry, industry_type: industry_type)
169
+ end
170
+
171
+ # Read the directory edition and source coverage.
172
+ def company_coverage
173
+ get('/company/directory/coverage')
174
+ end
175
+
158
176
  # Parse an email and check its format and domain. Deep explicitly requests a metered
159
177
  # deliverability check. Deep checks use one attempt by default. An explicit retry count can
160
178
  # repeat paid usage.
@@ -173,16 +191,39 @@ module ParseAPI
173
191
  get("/iban/#{seg(iban)}", country: country, deep: deep)
174
192
  end
175
193
 
176
- # Look up a 6-11 digit card prefix, preserving leading zeros.
177
194
  def bin(bin, deep: false)
178
195
  get("/bin/#{seg(bin)}", deep: deep)
179
196
  end
180
197
 
181
-
182
198
  def npi(npi, deep: false, lang: nil)
183
199
  get("/npi/#{seg(npi)}", deep: deep, lang: lang)
184
200
  end
185
201
 
202
+ def bank(iban, country: nil, deep: false)
203
+ get("/bank", {}, {}, { iban: iban, country: country, deep: deep }.reject { |_key, value| value.nil? })
204
+ end
205
+
206
+ # Look up a 2-11 digit card prefix, preserving leading zeros.
207
+ def card(bin, deep: false)
208
+ raise ArgumentError, 'parseapi: Card requires a 2-11 digit prefix string.' unless bin.is_a?(String) && bin.length <= 64 && /\A[0-9]{2,11}\z/.match?(bin.delete(" \t\r\n-"))
209
+ get("/card/#{seg(bin)}", deep: deep)
210
+ end
211
+
212
+
213
+ # US routing/account syntax only; not account or ACH eligibility verification.
214
+ def bank_us_ach(routing:, account:)
215
+ get('/bank', {}, {}, { format: 'us_ach', country: 'US', routing: routing, account: account })
216
+ end
217
+
218
+ # Describe accepted fields and check scope, not directory completeness.
219
+ def bank_requirements(country, format: nil)
220
+ get('/bank/requirements', country: country, format: format)
221
+ end
222
+
223
+ def provider(npi, deep: false, lang: nil)
224
+ get("/provider/#{seg(npi)}", deep: deep, lang: lang)
225
+ end
226
+
186
227
  # Parse a phone number and its formats. Pass country for national numbers when needed. Deep
187
228
  # adds numbering-plan geography on every plan. Carrier, caller, and HLR are separate metered lookups.
188
229
  def phone(number, country: nil, deep: false)
@@ -240,18 +281,31 @@ module ParseAPI
240
281
  get('/useragent', { deep: deep }, { 'User-Agent' => ua })
241
282
  end
242
283
 
284
+ def vehicle(vin, deep: false)
285
+ get("/vehicle/#{seg(vin)}", deep: deep)
286
+ end
287
+
243
288
  def vin(vin, deep: false)
244
289
  get("/vin/#{seg(vin)}", deep: deep)
245
290
  end
246
291
 
247
292
  # US NAICS 2022 definition and hierarchy.
293
+ # Compatibility names for Industry.
248
294
  def naics(code, deep: false)
249
- get("/naics/#{seg(code)}", deep: deep)
295
+ industry(code, deep: deep)
250
296
  end
251
297
 
252
- # Keyword search. Limit defaults to 10 and accepts 1-50.
253
298
  def naics_search(query, limit: nil, deep: false)
254
- get('/naics', q: query, limit: limit, deep: deep)
299
+ industry_search(query, limit: limit, deep: deep)
300
+ end
301
+
302
+ def industry(code, deep: false)
303
+ get("/industry/#{seg(code)}", deep: deep)
304
+ end
305
+
306
+ # Keyword search. Limit defaults to 10 and accepts 1-50.
307
+ def industry_search(query, limit: nil, deep: false)
308
+ get('/industry', q: query, limit: limit, deep: deep)
255
309
  end
256
310
 
257
311
  # Look up the general US duty schedule line. Paid deep adds units and the special and other
@@ -393,17 +447,22 @@ module ParseAPI
393
447
  URI.encode_www_form_component(value.to_s).gsub('+', '%20')
394
448
  end
395
449
 
396
- def get(path, params = {}, headers = {})
450
+ def get(path, params = {}, headers = {}, json = nil)
397
451
  retries = retries_for(path, params)
398
452
  query = params.reject { |_name, value| value.nil? || value == false }
399
453
  uri = @base_url.dup
400
454
  uri.path = path
401
455
  uri.query = URI.encode_www_form(query) unless query.empty?
402
456
 
457
+ encoded = json.nil? ? nil : JSON.generate(json)
403
458
  attempt = 0
404
459
  loop do
405
460
  begin
406
- status, response_headers, body = execute(uri, request_headers(headers))
461
+ status, response_headers, body = if encoded.nil?
462
+ execute(uri, request_headers(headers))
463
+ else
464
+ execute(uri, request_headers(headers.merge('Content-Type' => 'application/json')), 'POST', encoded)
465
+ end
407
466
  rescue *NETWORK_ERRORS
408
467
  raise if attempt >= retries
409
468
 
@@ -414,13 +473,14 @@ module ParseAPI
414
473
 
415
474
  return JSON.parse(body) if (200..299).cover?(status)
416
475
 
417
- if RETRY_STATUS.include?(status) && attempt < retries
418
- sleep(retry_delay(attempt, response_headers['retry-after']))
476
+ retry_after = response_headers['retry-after']
477
+ if RETRY_STATUS.include?(status) && attempt < retries && (wait = retry_delay(attempt, retry_after))
478
+ sleep(wait)
419
479
  attempt += 1
420
480
  next
421
481
  end
422
482
 
423
- raise build_error(status, body)
483
+ raise build_error(status, body, retry_after)
424
484
  end
425
485
  end
426
486
 
@@ -429,12 +489,13 @@ module ParseAPI
429
489
  end
430
490
 
431
491
  # Returns [status, headers_hash, body_string]. Overridden in tests.
432
- def execute(uri, headers)
433
- return @transport.call(uri.to_s, headers) if @transport
492
+ def execute(uri, headers, method = 'GET', body = nil)
493
+ return (method == 'GET' ? @transport.call(uri.to_s, headers) : @transport.call(uri.to_s, headers, method, body)) if @transport
434
494
 
435
495
  timeout = !@timeout_explicit && uri.path.start_with?('/stack/') ? 35 : @timeout
436
496
  http = connection(timeout)
437
- request = Net::HTTP::Get.new(uri.request_uri)
497
+ request = (method == 'POST' ? Net::HTTP::Post : Net::HTTP::Get).new(uri.request_uri)
498
+ request.body = body unless body.nil?
438
499
  headers.each { |name, value| request[name] = value }
439
500
  response = http.request(request)
440
501
  header_hash = {}
@@ -459,15 +520,18 @@ module ParseAPI
459
520
 
460
521
  def retry_delay(attempt, retry_after)
461
522
  if retry_after
462
- seconds = Float(retry_after, exception: false)
463
- return [seconds, RETRY_AFTER_CAP].min if seconds && seconds.finite? && seconds >= 0
523
+ if /\A[0-9]+(?:\.[0-9]+)?\z/.match?(retry_after.strip)
524
+ seconds = Float(retry_after, exception: false)
525
+ return seconds && seconds.finite? && seconds <= RETRY_AFTER_CAP ? seconds : nil
526
+ end
464
527
  begin
465
- return [[Time.httpdate(retry_after) - Time.now, 0].max, RETRY_AFTER_CAP].min
528
+ seconds = [Time.httpdate(retry_after) - Time.now, 0].max
529
+ return seconds > RETRY_AFTER_CAP ? nil : seconds
466
530
  rescue ArgumentError
467
531
  # Fall back to jitter when the header is not a delay or HTTP date.
468
532
  end
469
533
  end
470
- rand * 0.25 * (2**attempt)
534
+ rand * [0.25 * (2**[attempt, 5].min), RETRY_AFTER_CAP].min
471
535
  end
472
536
 
473
537
  def retries_for(path, params)
@@ -478,7 +542,7 @@ module ParseAPI
478
542
  metered ? 0 : DEFAULT_RETRIES
479
543
  end
480
544
 
481
- def build_error(status, body)
545
+ def build_error(status, body, retry_after = nil)
482
546
  parsed = begin
483
547
  JSON.parse(body)
484
548
  rescue JSON::ParserError
@@ -490,7 +554,8 @@ module ParseAPI
490
554
  code: parsed['code'].is_a?(String) ? parsed['code'] : 'unknown_error',
491
555
  message: parsed['message'].is_a?(String) ? parsed['message'] : "Request failed with status #{status}",
492
556
  docs: parsed['docs'].is_a?(String) ? parsed['docs'] : nil,
493
- request_id: parsed['request_id'].is_a?(String) ? parsed['request_id'] : nil
557
+ request_id: parsed['request_id'].is_a?(String) ? parsed['request_id'] : nil,
558
+ retry_after: retry_after
494
559
  )
495
560
  end
496
561
  end
@@ -1,3 +1,3 @@
1
1
  module ParseAPI
2
- VERSION = '1.7.0'.freeze
2
+ VERSION = '1.9.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parseapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ParseAPI
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-24 00:00:00.000000000 Z
11
+ date: 2026-09-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: