veryfi 2.0.0 → 4.0.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.
data/README.md CHANGED
@@ -17,9 +17,33 @@
17
17
  - [Table of Contents](#table-of-contents)
18
18
  - [Example](#example)
19
19
  - [Installation](#installation)
20
+ - [Versioning & compatibility](#versioning--compatibility)
20
21
  - [Getting Started](#getting-started)
21
22
  - [Obtaining Client ID and user keys](#obtaining-client-id-and-user-keys)
22
23
  - [Ruby API Client Library](#ruby-api-client-library)
24
+ - [Configuring the client](#configuring-the-client)
25
+ - [Custom Faraday configuration](#custom-faraday-configuration)
26
+ - [Handling errors](#handling-errors)
27
+ - [Response objects](#response-objects)
28
+ - [Common parameters & defaults](#common-parameters--defaults)
29
+ - [Available APIs](#available-apis)
30
+ - [Documents](#documents)
31
+ - [Tags](#tags)
32
+ - [Document Tags](#document-tags)
33
+ - [Line Items](#line-items)
34
+ - [Tax Lines](#tax-lines)
35
+ - [PDF Split (Documents Set)](#pdf-split-documents-set)
36
+ - [Any Document (A-Docs)](#any-document-a-docs)
37
+ - [Bank Statements](#bank-statements)
38
+ - [Bank Statement Split](#bank-statement-split)
39
+ - [Business Cards](#business-cards)
40
+ - [Checks](#checks)
41
+ - [Classify](#classify)
42
+ - [W-2](#w-2)
43
+ - [W-2 Split](#w-2-split)
44
+ - [W-8 BEN-E](#w-8-ben-e)
45
+ - [W-9](#w-9)
46
+ - [Per-resource tags](#per-resource-tags)
23
47
  - [Need help?](#need-help)
24
48
  - [For Developers](#for-developers)
25
49
  - [Install](#install)
@@ -225,13 +249,80 @@ This will produce the following response:
225
249
 
226
250
  ## Installation
227
251
 
252
+ Install the latest version of the gem:
253
+
228
254
  ```bash
229
255
  gem install veryfi
230
256
  ```
231
257
 
232
- Or add to your Gemfile:
258
+ Or pin it in your `Gemfile`. The current major series is **`4.x`**, so the
259
+ recommended pessimistic constraint is:
260
+
261
+ ```ruby
262
+ gem 'veryfi', '~> 4.0'
263
+ ```
264
+
265
+ That gives you all backwards-compatible improvements in the `4.x` line
266
+ (`>= 4.0, < 5.0`) without unexpectedly jumping to a future `5.0` that
267
+ might contain breaking changes.
268
+
269
+ After editing your `Gemfile`, run:
270
+
271
+ ```bash
272
+ bundle install
273
+ ```
274
+
275
+ Then in your Ruby code:
276
+
277
+ ```ruby
278
+ require 'veryfi'
279
+ ```
280
+
281
+ ## Versioning & compatibility
282
+
283
+ `veryfi-ruby` follows [Semantic Versioning](https://semver.org/):
284
+
285
+ - **MAJOR** (`4.x` → `5.x`): backwards-incompatible API changes. Read the
286
+ release notes before upgrading.
287
+ - **MINOR** (`4.0.x` → `4.1.0`): new endpoints or new methods, fully
288
+ backwards compatible. Safe to upgrade.
289
+ - **PATCH** (`4.0.0` → `4.0.1`): bug fixes only. Safe to upgrade.
290
+
291
+ | Series | Status | Notes |
292
+ | ------- | ----------- | ------------------------------------------------------------------------------------ |
293
+ | `4.x` | **Current** | Full coverage of the Veryfi v8 partner API including A-Docs, bank statements, checks (with remittance + async), W-2/W-8/W-9, classify, PDF split, W-2 split, bank-statement split, tax lines, and per-resource tag management. Responses are lightweight `Veryfi::Resource` objects (Hash-compatible). |
294
+ | `3.x` | Maintenance | Documents, line items, tags, document tags only. |
295
+ | `<=2.x` | Unsupported | Please upgrade. |
296
+
297
+ ### Supported Ruby versions
298
+
299
+ The minimum supported Ruby version is **3.0** (the floor of the Faraday
300
+ 2.x line, which is the gem's only non-stdlib runtime dependency). CI
301
+ verifies the gem against:
302
+
303
+ | Ruby | Upstream status | Notes |
304
+ | ------ | ---------------------------- | -------------------------------------- |
305
+ | `3.0` | End-of-life upstream | Floor — best-effort, no Ruby-side fixes |
306
+ | `3.1` | End-of-life upstream | Best-effort |
307
+ | `3.2` | Security maintenance | |
308
+ | `3.3` | Normal maintenance | |
309
+ | `3.4` | Normal maintenance | |
310
+ | `4.0` | Current stable | |
311
+
312
+ Ruby 2.7 and earlier are not supported. If you need to stay on Ruby 2.7
313
+ pin the `veryfi` 3.x release line, which targeted that vintage of Ruby.
314
+
315
+ The gem talks to Veryfi API version **`v8`** by default. You can override
316
+ this at construction time if you are working against a different version:
317
+
233
318
  ```ruby
234
- gem 'veryfi', '~> 0.1'
319
+ Veryfi::Client.new(
320
+ client_id: 'your_client_id',
321
+ client_secret: 'your_client_secret',
322
+ username: 'your_username',
323
+ api_key: 'your_api_key',
324
+ api_version: 'v8' # default
325
+ )
235
326
  ```
236
327
 
237
328
  ## Getting Started
@@ -244,6 +335,443 @@ If you don't have an account with Veryfi, please go ahead and register here: [ht
244
335
 
245
336
  The **veryfi-ruby** gem can be used to communicate with Veryfi API. All available functionality is described [in docs](https://veryfi.github.io/veryfi-ruby/)
246
337
 
338
+ ## Configuring the client
339
+
340
+ The most explicit way is to instantiate {`Veryfi::Client`} directly:
341
+
342
+ ```ruby
343
+ client = Veryfi::Client.new(
344
+ client_id: ENV.fetch('VERYFI_CLIENT_ID'),
345
+ client_secret: ENV.fetch('VERYFI_CLIENT_SECRET'),
346
+ username: ENV.fetch('VERYFI_USERNAME'),
347
+ api_key: ENV.fetch('VERYFI_API_KEY')
348
+ )
349
+ ```
350
+
351
+ For apps that want a single process-wide client (common in Rails / Sidekiq
352
+ setups), the `Veryfi.configure` / `Veryfi.client` shortcuts are provided:
353
+
354
+ ```ruby
355
+ # config/initializers/veryfi.rb
356
+ Veryfi.configure do |c|
357
+ c.client_id = ENV.fetch('VERYFI_CLIENT_ID')
358
+ c.client_secret = ENV.fetch('VERYFI_CLIENT_SECRET')
359
+ c.username = ENV.fetch('VERYFI_USERNAME')
360
+ c.api_key = ENV.fetch('VERYFI_API_KEY')
361
+ end
362
+
363
+ # anywhere in your app
364
+ Veryfi.client.document.process(file_path: './receipt.jpg')
365
+ ```
366
+
367
+ `Veryfi.client` is memoized; if you re-`configure` the SDK it's rebuilt
368
+ automatically on the next access. Call `Veryfi.reset!` in tests to drop
369
+ the memoized client and configuration.
370
+
371
+ ### Custom Faraday configuration
372
+
373
+ The underlying HTTP connection is a plain `Faraday::Connection`. Pass a
374
+ `faraday:` block to add middleware — retries, logging, persistent
375
+ connections, anything Faraday supports:
376
+
377
+ ```ruby
378
+ client = Veryfi::Client.new(
379
+ client_id: '…',
380
+ client_secret: '…',
381
+ username: '…',
382
+ api_key: '…',
383
+ faraday: ->(conn) {
384
+ # automatic exponential backoff on retryable statuses
385
+ conn.request :retry,
386
+ max: 3,
387
+ interval: 0.5,
388
+ backoff_factor: 2,
389
+ retry_statuses: [429, 502, 503, 504]
390
+
391
+ # log every request / response
392
+ conn.response :logger, Rails.logger if defined?(Rails)
393
+
394
+ # keep TCP connections open between API calls
395
+ conn.adapter :net_http_persistent
396
+ }
397
+ )
398
+ ```
399
+
400
+ (`faraday-retry` and `faraday-net_http_persistent` are not bundled with
401
+ this gem — install whichever extensions you need.)
402
+
403
+ ## Handling errors
404
+
405
+ Every error raised by the SDK descends from `Veryfi::Error::VeryfiError`,
406
+ so a catch-all rescue is always valid:
407
+
408
+ ```ruby
409
+ begin
410
+ client.document.process(file_path: './receipt.jpg')
411
+ rescue Veryfi::Error::VeryfiError => e
412
+ Rails.logger.error("Veryfi call failed (status=#{e.status}): #{e.message}")
413
+ raise
414
+ end
415
+ ```
416
+
417
+ When you want to react differently per HTTP status, rescue one of the
418
+ specific subclasses. Order matters — list narrower classes before broader
419
+ ones:
420
+
421
+ | HTTP status | Exception class | Typical reason |
422
+ | ------------------ | --------------------------------------- | ------------------------------------------- |
423
+ | `400 Bad Request` | `Veryfi::Error::BadRequest` | Malformed payload or failed validation |
424
+ | `401 Unauthorized` | `Veryfi::Error::Unauthorized` | Bad / missing / expired credentials |
425
+ | `403 Forbidden` | `Veryfi::Error::AccessLimitReached` | Credentials lack permission for this action |
426
+ | `404 Not Found` | `Veryfi::Error::NotFound` | Resource id does not exist |
427
+ | `408 Request Timeout` | `Veryfi::Error::RequestTimeout` | Request timed out before Veryfi responded |
428
+ | `409 Conflict` | `Veryfi::Error::Conflict` | Conflicts with current resource state |
429
+ | `415 Unsupported Media` | `Veryfi::Error::UnsupportedMediaType` | File type not supported by the endpoint |
430
+ | `429 Too Many Requests` | `Veryfi::Error::TooManyRequests` | Rate-limited; back off and retry |
431
+ | Other 4xx | `Veryfi::Error::ClientError` | Generic 4xx |
432
+ | Any 5xx | `Veryfi::Error::ServerError` | Server-side error; retry with backoff |
433
+
434
+ ```ruby
435
+ begin
436
+ client.document.process(file_path: path)
437
+ rescue Veryfi::Error::Unauthorized then refresh_credentials!
438
+ rescue Veryfi::Error::TooManyRequests then back_off
439
+ rescue Veryfi::Error::ServerError then schedule_retry
440
+ rescue Veryfi::Error::VeryfiError then notify_ops
441
+ end
442
+ ```
443
+
444
+ Each error exposes `#status` (Integer), `#response` (parsed body as a
445
+ `Veryfi::Resource`) and `#message` (the pretty-printed JSON body, falling
446
+ back to `"<status>"` when the response is empty).
447
+
448
+ ## Response objects
449
+
450
+ Every API call returns a `Veryfi::Resource`. A `Resource` is a tiny
451
+ subclass of `Hash`, so anything that already treats the response as a
452
+ hash keeps working unchanged — no migration required when upgrading
453
+ from earlier versions of this gem:
454
+
455
+ ```ruby
456
+ response = client.document.get(44_691_518)
457
+
458
+ response["id"] # => 44691518
459
+ response[:id] # => 44691518 (symbol keys work too)
460
+ response.dig("vendor", "name") # => "East Repair"
461
+ response.is_a?(Hash) # => true
462
+ JSON.pretty_generate(response) # works as you'd expect
463
+ ```
464
+
465
+ In addition, every key is exposed as a method, recursively:
466
+
467
+ ```ruby
468
+ response.id # => 44691518
469
+ response.vendor.name # => "East Repair"
470
+ response.line_items.first.description # => "Brake cables"
471
+ response.line_items.map(&:total) # => [100, 30, 15]
472
+ response.is_duplicate? # => true (boolean predicate sugar)
473
+ ```
474
+
475
+ Nested objects become `Resource`s and arrays of objects become arrays
476
+ of `Resource`s automatically. Leaf values (strings, numbers, booleans,
477
+ `nil`) pass through untouched. Unknown keys raise `NoMethodError`, so
478
+ typos surface immediately instead of silently returning `nil`.
479
+
480
+ If you need a plain `Hash` (e.g. to hand off to a serializer that
481
+ inspects the exact class), call `#to_h`, which recursively unwraps:
482
+
483
+ ```ruby
484
+ response.to_h.class # => Hash
485
+ response.to_h["vendor"].class # => Hash
486
+ ```
487
+
488
+ ## Common parameters & defaults
489
+
490
+ Most of the "process" methods (`process`, `process_url`, `process_async`,
491
+ `process_url_async`, `process_with_remittance`, …) accept the same set
492
+ of optional keys. **Omitting a key is exactly equivalent to passing its
493
+ default value** — both produce the same outbound request. Only pass a
494
+ key when you want a non-default value or want to make the intent
495
+ explicit in your own code.
496
+
497
+ | Key | Required? | Default | Meaning |
498
+ | ----------------------- | ------------------------------------------ | --------------------------------------------- | ------- |
499
+ | `file_path` | Yes for `process` / `process_async` etc. | — | Local path to the file to upload. Read and base64-encoded for you. |
500
+ | `file_url` | Yes for `process_url` (or `file_urls`) | — | Publicly accessible URL to a single file. |
501
+ | `file_urls` | Alternative to `file_url` | — | Array of publicly accessible URLs treated as one logical document. |
502
+ | `file_name` | No | basename of `file_path` / `file_url` | Display name sent to Veryfi. Useful when `file_path` is a tempfile with an ugly name. |
503
+ | `categories` | No | Veryfi's default list (see `Veryfi::Api::Document::CATEGORIES`) for receipts/invoices; `[]` for split endpoints | Restrict Veryfi's categorization output to this set of strings. |
504
+ | `tags` | No | `nil` (no tags) | Array of tag names to attach to the resulting document. |
505
+ | `auto_delete` | No | `false` | When `true`, Veryfi deletes the document from its storage immediately after extraction. Use when you don't need Veryfi to keep the file (e.g. you store it yourself). |
506
+ | `boost_mode` | No | `false` | When `true`, Veryfi skips data-enrichment steps (vendor lookup, logo, category enrichment, etc.). Processing is **faster** but the response has **less** extracted/enriched data. Useful for high-throughput pipelines that don't need the extras. |
507
+ | `async` | No | `false` | When `true`, the request returns immediately (with a processing-status payload) and Veryfi continues extraction in the background. **Prefer the dedicated `.process_async` / `.process_url_async` methods** where available — they hit the canonical async endpoint instead of piggybacking on the sync one. |
508
+ | `external_id` | No | `nil` | Your own identifier for the document. Echoed back in the response and searchable via `client.document.all(external_id: …)`. |
509
+ | `max_pages_to_process` | No | `nil` (= all pages) | Hard cap on the number of pages Veryfi reads, starting from page 1. Useful for very long PDFs when you only care about the first few pages. |
510
+ | `bounding_boxes` | No | `false` | When `true`, the response includes `bounding_box` / `bounding_region` metadata for each extracted field. |
511
+ | `confidence_details` | No | `false` | When `true`, the response includes per-field `score` and `ocr_score` values. |
512
+
513
+ So all four of the following calls are equivalent and produce the same
514
+ request payload — pick whichever style your team prefers:
515
+
516
+ ```ruby
517
+ # All defaults explicit
518
+ client.document.process(
519
+ file_path: './receipt.jpg',
520
+ auto_delete: false,
521
+ boost_mode: false,
522
+ async: false,
523
+ external_id: nil,
524
+ max_pages_to_process: nil,
525
+ tags: nil,
526
+ categories: Veryfi::Api::Document::CATEGORIES
527
+ )
528
+
529
+ # Defaults omitted (recommended — least noise)
530
+ client.document.process(file_path: './receipt.jpg')
531
+
532
+ # Only the non-defaults
533
+ client.document.process(
534
+ file_path: './receipt.jpg',
535
+ auto_delete: true,
536
+ tags: ['expense']
537
+ )
538
+
539
+ # Same call, mixing styles
540
+ client.document.process(
541
+ file_path: './receipt.jpg',
542
+ boost_mode: false, # explicit default — purely for readability
543
+ external_id: '123456789'
544
+ )
545
+ ```
546
+
547
+ If you ever need to know the default list of categories at runtime,
548
+ it lives at `Veryfi::Api::Document::CATEGORIES`.
549
+
550
+ ## Available APIs
551
+
552
+ The `Veryfi::Client` exposes the full Veryfi API surface through namespaced sub-objects. All sub-objects share the same underlying authenticated `Veryfi::Request`, so a single client instance is enough.
553
+
554
+ ```ruby
555
+ client = Veryfi::Client.new(
556
+ client_id: 'your_client_id',
557
+ client_secret: 'your_client_secret',
558
+ username: 'your_username',
559
+ api_key: 'your_api_key'
560
+ )
561
+ ```
562
+
563
+ ### Documents
564
+
565
+ ```ruby
566
+ client.document.all # GET /partner/documents/
567
+ client.document.get(document_id) # GET /partner/documents/:id
568
+ client.document.process(file_path: 'receipt.jpg') # POST /partner/documents/ (upload)
569
+ client.document.process_url(file_url: 'https://...') # POST /partner/documents/ (url)
570
+ client.document.process_bulk(%w[https://... https://...]) # POST /partner/documents/bulk/
571
+ client.document.update(document_id, notes: 'edited') # PUT /partner/documents/:id
572
+ client.document.delete(document_id) # DELETE /partner/documents/:id
573
+ ```
574
+
575
+ ### Tags
576
+
577
+ ```ruby
578
+ client.tag.all # GET /partner/tags/
579
+ client.tag.delete(tag_id) # DELETE /partner/tags/:id
580
+ ```
581
+
582
+ ### Document Tags
583
+
584
+ ```ruby
585
+ client.document_tag.all(document_id) # GET /partner/documents/:id/tags/
586
+ client.document_tag.add(document_id, name: 'priority') # PUT /partner/documents/:id/tags/
587
+ client.document_tag.add_multiple(document_id, %w[a b c]) # POST /partner/documents/:id/tags/
588
+ client.document_tag.replace(document_id, %w[a b c]) # PUT /partner/documents/:id/
589
+ client.document_tag.delete(document_id, tag_id) # DELETE /partner/documents/:id/tags/:tag_id
590
+ client.document_tag.delete_all(document_id) # DELETE /partner/documents/:id/tags/
591
+ ```
592
+
593
+ ### Line Items
594
+
595
+ ```ruby
596
+ client.line_item.all(document_id) # GET /partner/documents/:id/line-items/
597
+ client.line_item.get(document_id, line_item_id) # GET /partner/documents/:id/line-items/:line_id
598
+ client.line_item.create(document_id, description: 'Foo') # POST /partner/documents/:id/line-items/
599
+ client.line_item.update(document_id, line_item_id, discount: 0.9) # PUT /partner/documents/:id/line-items/:line_id
600
+ client.line_item.delete(document_id, line_item_id) # DELETE /partner/documents/:id/line-items/:line_id
601
+ client.line_item.delete_all(document_id) # DELETE /partner/documents/:id/line-items
602
+ ```
603
+
604
+ ### Tax Lines
605
+
606
+ ```ruby
607
+ client.tax_line.all(document_id) # GET /partner/documents/:id/tax-lines
608
+ client.tax_line.get(document_id, tax_line_id) # GET /partner/documents/:id/tax-lines/:tax_id
609
+ client.tax_line.create(document_id, name: 'Sales Tax', rate: 6.25) # POST /partner/documents/:id/tax-lines
610
+ client.tax_line.update(document_id, tax_line_id, rate: 7.0) # PUT /partner/documents/:id/tax-lines/:tax_id
611
+ client.tax_line.delete(document_id, tax_line_id) # DELETE /partner/documents/:id/tax-lines/:tax_id
612
+ ```
613
+
614
+ ### PDF Split (Documents Set)
615
+
616
+ Split a multi-page PDF into multiple processed documents.
617
+
618
+ ```ruby
619
+ client.pdf_split.all # GET /partner/documents-set/
620
+ client.pdf_split.get(documents_set_id) # GET /partner/documents-set/:id
621
+ client.pdf_split.process(file_path: 'multi.pdf') # POST /partner/documents-set/ (upload)
622
+ client.pdf_split.process_url(file_url: 'https://...') # POST /partner/documents-set/ (url)
623
+ ```
624
+
625
+ ### Any Document (A-Docs)
626
+
627
+ Process arbitrary document types using a Veryfi blueprint.
628
+
629
+ ```ruby
630
+ client.any_document.all # GET /partner/any-documents/
631
+ client.any_document.get(document_id) # GET /partner/any-documents/:id
632
+ client.any_document.process(
633
+ blueprint_name: 'us_w2_2022',
634
+ file_path: 'doc.pdf'
635
+ ) # POST /partner/any-documents/ (upload)
636
+ client.any_document.process_url(
637
+ blueprint_name: 'us_w2_2022',
638
+ file_url: 'https://...'
639
+ ) # POST /partner/any-documents/ (url)
640
+ client.any_document.process_async(
641
+ blueprint_name: 'us_w2_2022',
642
+ file_path: 'doc.pdf'
643
+ ) # POST /partner/any-documents/async (upload)
644
+ client.any_document.process_url_async(
645
+ blueprint_name: 'us_w2_2022',
646
+ file_url: 'https://...'
647
+ ) # POST /partner/any-documents/async (url)
648
+ client.any_document.update(document_id, notes: 'edited') # PUT /partner/any-documents/:id
649
+ client.any_document.delete(document_id) # DELETE /partner/any-documents/:id
650
+ ```
651
+
652
+ ### Bank Statements
653
+
654
+ ```ruby
655
+ client.bank_statement.all # GET /partner/bank-statements/
656
+ client.bank_statement.get(document_id) # GET /partner/bank-statements/:id
657
+ client.bank_statement.process(file_path: 'statement.pdf') # POST /partner/bank-statements/ (upload)
658
+ client.bank_statement.process_url(file_url: 'https://...') # POST /partner/bank-statements/ (url)
659
+ client.bank_statement.process_async(file_path: 'statement.pdf') # POST /partner/bank-statements/async (upload)
660
+ client.bank_statement.process_url_async(file_url: 'https://...') # POST /partner/bank-statements/async (url)
661
+ client.bank_statement.update(document_id, notes: 'edited') # PUT /partner/bank-statements/:id
662
+ client.bank_statement.delete(document_id) # DELETE /partner/bank-statements/:id
663
+ ```
664
+
665
+ ### Bank Statement Split
666
+
667
+ Split a multi-statement file into individually processed bank statements.
668
+
669
+ ```ruby
670
+ client.bank_statement_split.all # GET /partner/bank-statements-set/
671
+ client.bank_statement_split.get(id) # GET /partner/bank-statements-set/:id
672
+ client.bank_statement_split.process(file_path: 'multi.pdf') # POST /partner/bank-statements-set/ (upload)
673
+ client.bank_statement_split.process_url(file_urls: ['https://...']) # POST /partner/bank-statements-set/ (url)
674
+ ```
675
+
676
+ ### Business Cards
677
+
678
+ ```ruby
679
+ client.business_card.all # GET /partner/business-cards/
680
+ client.business_card.get(document_id) # GET /partner/business-cards/:id
681
+ client.business_card.process(file_path: 'card.jpg') # POST /partner/business-cards/ (upload)
682
+ client.business_card.process_url(file_url: 'https://...') # POST /partner/business-cards/ (url)
683
+ client.business_card.update(document_id, company: 'Globex') # PUT /partner/business-cards/:id
684
+ client.business_card.delete(document_id) # DELETE /partner/business-cards/:id
685
+ ```
686
+
687
+ ### Checks
688
+
689
+ ```ruby
690
+ client.check.all # GET /partner/checks/
691
+ client.check.get(document_id) # GET /partner/checks/:id
692
+ client.check.process(file_path: 'check.jpg') # POST /partner/checks/ (upload)
693
+ client.check.process_url(file_url: 'https://...') # POST /partner/checks/ (url)
694
+ client.check.process_with_remittance(file_path: 'check.jpg') # POST /partner/check-with-document/ (upload)
695
+ client.check.process_with_remittance_url(file_url: 'https://...') # POST /partner/check-with-document/ (url)
696
+ client.check.process_async(file_path: 'check.jpg') # POST /partner/checks/async (upload)
697
+ client.check.process_url_async(file_url: 'https://...') # POST /partner/checks/async (url)
698
+ client.check.update(document_id, notes: 'edited') # PUT /partner/checks/:id
699
+ client.check.delete(document_id) # DELETE /partner/checks/:id
700
+ ```
701
+
702
+ ### Classify
703
+
704
+ Predict the document type of a file.
705
+
706
+ ```ruby
707
+ client.classify.process(
708
+ file_path: 'unknown.jpg',
709
+ document_types: %w[invoice receipt w2]
710
+ ) # POST /partner/classify/ (upload)
711
+ client.classify.process_url(
712
+ file_url: 'https://...',
713
+ document_types: %w[invoice receipt w2]
714
+ ) # POST /partner/classify/ (url)
715
+ ```
716
+
717
+ ### W-2
718
+
719
+ ```ruby
720
+ client.w2.all # GET /partner/w2s/
721
+ client.w2.get(document_id) # GET /partner/w2s/:id
722
+ client.w2.process(file_path: 'w2.pdf') # POST /partner/w2s/ (upload)
723
+ client.w2.process_url(file_url: 'https://...') # POST /partner/w2s/ (url)
724
+ client.w2.update(document_id, notes: 'edited') # PUT /partner/w2s/:id
725
+ client.w2.delete(document_id) # DELETE /partner/w2s/:id
726
+ ```
727
+
728
+ ### W-2 Split
729
+
730
+ Split a file containing multiple W-2 forms into individually processed W-2s.
731
+
732
+ ```ruby
733
+ client.w2_split.all # GET /partner/w2s-set/
734
+ client.w2_split.get(w2s_set_id) # GET /partner/w2s-set/:id
735
+ client.w2_split.process(file_path: 'multi_w2.pdf') # POST /partner/w2s-set/ (upload)
736
+ client.w2_split.process_url(file_urls: ['https://...']) # POST /partner/w2s-set/ (url)
737
+ ```
738
+
739
+ ### W-8 BEN-E
740
+
741
+ ```ruby
742
+ client.w8.all # GET /partner/w-8ben-e/
743
+ client.w8.get(document_id) # GET /partner/w-8ben-e/:id
744
+ client.w8.process(file_path: 'w8.pdf') # POST /partner/w-8ben-e/ (upload)
745
+ client.w8.process_url(file_url: 'https://...') # POST /partner/w-8ben-e/ (url)
746
+ client.w8.update(document_id, notes: 'edited') # PUT /partner/w-8ben-e/:id
747
+ client.w8.delete(document_id) # DELETE /partner/w-8ben-e/:id
748
+ ```
749
+
750
+ ### W-9
751
+
752
+ ```ruby
753
+ client.w9.all # GET /partner/w9s/
754
+ client.w9.get(document_id) # GET /partner/w9s/:id
755
+ client.w9.process(file_path: 'w9.pdf') # POST /partner/w9s/ (upload)
756
+ client.w9.process_url(file_url: 'https://...') # POST /partner/w9s/ (url)
757
+ client.w9.update(document_id, notes: 'edited') # PUT /partner/w9s/:id
758
+ client.w9.delete(document_id) # DELETE /partner/w9s/:id
759
+ ```
760
+
761
+ ### Per-resource tags
762
+
763
+ Every processed-document resource (`any_document`, `bank_statement`, `business_card`, `check`, `w2`, `w8`, `w9`) exposes the same tag-management methods directly on the resource, mirroring the per-document tag endpoints:
764
+
765
+ ```ruby
766
+ client.check.tags(check_id) # GET /partner/checks/:id/tags
767
+ client.check.add_tag(check_id, name: 'priority') # PUT /partner/checks/:id/tags (single)
768
+ client.check.add_tags(check_id, %w[priority urgent]) # POST /partner/checks/:id/tags (multiple)
769
+ client.check.delete_tag(check_id, tag_id) # DELETE /partner/checks/:id/tags/:tag_id
770
+ client.check.delete_tags(check_id) # DELETE /partner/checks/:id/tags (all)
771
+ ```
772
+
773
+ The same five methods are available on `client.any_document`, `client.bank_statement`, `client.business_card`, `client.w2`, `client.w8`, and `client.w9`.
774
+
247
775
  ## Need help?
248
776
 
249
777
  If you run into any issue or need help installing or using the library, please contact support@veryfi.com.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rspec/core/rake_task"
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+ RuboCop::RakeTask.new(:rubocop)
10
+
11
+ begin
12
+ require "yard"
13
+ YARD::Rake::YardocTask.new(:yard)
14
+ rescue LoadError
15
+ # yard is not required to run the test suite — silently skip if missing
16
+ end
17
+
18
+ desc "Run the full quality + test suite"
19
+ task ci: %i[rubocop spec]
20
+
21
+ task default: :ci