jaz-clio 4.4.0 → 4.6.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-api
3
- version: 4.4.0
3
+ version: 4.6.0
4
4
  description: Complete reference for the Jaz REST API — the accounting platform backend. Use this skill whenever building, modifying, debugging, or extending any code that calls the API — including API clients, integrations, data seeding, test data, or new endpoint work. Contains every field name, response shape, error, gotcha, and edge case discovered through live production testing.
5
5
  license: MIT
6
6
  compatibility: Requires Jaz API key (x-jk-api-key header). Works with Claude Code, Google Antigravity, OpenAI Codex, GitHub Copilot, Cursor, and any agent that reads markdown.
@@ -985,6 +985,12 @@ Content-Type: application/json
985
985
  - Tax amounts and profiles
986
986
  - Document reference numbers, dates, currency
987
987
 
988
+ **Encrypted PDFs:** Magic cannot process password-protected PDFs. The CLI auto-detects and decrypts before upload:
989
+ - Embed password in filename: `receipt__pw__s3cRetP@ss.pdf` → decrypts with password `s3cRetP@ss`, uploads as `receipt.pdf`
990
+ - `__pw__` delimiter is case-insensitive; password is case-sensitive
991
+ - Requires `qpdf` installed (`brew install qpdf`)
992
+ - If no password in filename, CLI prompts interactively (or errors in `--json` mode with actionable rename instructions)
993
+
988
994
  **Key gotchas:**
989
995
  - `sourceFile` is the field name (NOT `file`) — same pattern as bank statement endpoint
990
996
  - `EXPENSE` returns 422 — use one of the 4 valid types above
@@ -1056,6 +1062,41 @@ Content-Type: application/json
1056
1062
  3. When `COMPLETED` → read `businessTransactionDetails.businessTransactionResourceId`
1057
1063
  4. Use the BT resource ID with `GET /invoices/:id`, `GET /bills/:id`, `GET /customer-credit-notes/:id`, or `GET /supplier-credit-notes/:id`
1058
1064
 
1065
+ ### CLI: clio magic split — Merged PDF Splitting
1066
+
1067
+ Splits a merged PDF containing multiple documents (invoices, bills, credit notes) into individual files and uploads each to Magic. Uses structural PDF signals (bookmarks, page labels) + text heuristics (keywords, "Page 1 of N" patterns) for boundary detection. **No AI tokens used.**
1068
+
1069
+ ```bash
1070
+ # Auto-detect boundaries + upload
1071
+ clio magic split --file merged.pdf --type bill
1072
+
1073
+ # Manual page ranges (for scanned PDFs or override)
1074
+ clio magic split --file merged.pdf --type bill --pages "1-3,4-6,7-9"
1075
+
1076
+ # Dry-run: detect boundaries only (no qpdf needed)
1077
+ clio magic split --file merged.pdf --type bill --dry-run
1078
+
1079
+ # JSON output (for agents)
1080
+ clio magic split --file merged.pdf --type bill --dry-run --json
1081
+ ```
1082
+
1083
+ **Detection signals (score-based, threshold >= 50):**
1084
+ - outline-bookmark (+80): PDF bookmark points to this page
1085
+ - page-label-reset (+70): PDF page label restarts at "1"
1086
+ - keyword in header (+40): Document keyword (INVOICE, BILL, etc.) in upper 40%
1087
+ - page-one-of (+35): "Page 1 of N" pattern
1088
+ - keyword-large (+25): Large font (>18pt) keyword bonus
1089
+ - doc-ref (+20): Document reference (INV-001, SO-2024-100, etc.)
1090
+ - continuation (-60): "Page N>1 of M" anti-signal
1091
+ - continuation-text (-40): "Continued" anti-signal
1092
+
1093
+ **Edge cases:**
1094
+ - Scanned PDFs (no extractable text): warns and requires `--pages` manual override
1095
+ - Mixed scanned+digital: low confidence on scanned portions triggers confirmation prompt
1096
+ - Single document detected: suggests `clio magic create` instead
1097
+ - Encrypted PDFs: same `__pw__` pattern as `magic create`
1098
+ - Requires `qpdf` for splitting (not needed for `--dry-run` auto-detect mode)
1099
+
1059
1100
  ---
1060
1101
 
1061
1102
  ## 15. Bank Records
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-conversion
3
- version: 4.4.0
3
+ version: 4.6.0
4
4
  description: Accounting data conversion skill — migrates customer data from Xero, QuickBooks, Sage, MYOB, and Excel exports to Jaz. Covers config, quick, and full conversion workflows, Excel parsing, CoA/contact/tax/items mapping, clearing accounts, TTB, and TB verification.
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-jobs
3
- version: 4.4.0
3
+ version: 4.6.0
4
4
  description: 12 accounting jobs for SMB bookkeepers and accountants — month-end, quarter-end, and year-end close playbooks plus 9 ad-hoc operational jobs (bank recon, document collection, GST/VAT filing, payment runs, credit control, supplier recon, audit prep, fixed asset review, statutory filing). Jobs can have paired tools as nested subcommands (e.g., `clio jobs bank-recon match`, `clio jobs document-collection ingest`, `clio jobs statutory-filing sg-cs`). Paired with an interactive CLI blueprint generator (clio jobs).
5
5
  license: MIT
6
6
  compatibility: Works with Claude Code, Claude Cowork, Claude.ai, and any agent that reads markdown. For API payloads, load the jaz-api skill. For individual transaction patterns, load the jaz-recipes skill.
@@ -52,7 +52,8 @@ Multilingual support: Filipino/Tagalog, Bahasa Indonesia/Malay, Vietnamese, and
52
52
 
53
53
  - **Invoices/Bills/Credit Notes**: `.pdf`, `.jpg`, `.jpeg`, `.png`
54
54
  - **Bank Statements**: `.csv`, `.ofx`
55
- - **Skipped** (with warning): `.xlsx`, `.xls`, `.doc`, `.docx`, `.txt`, `.zip`, `.rar`, `.7z`
55
+ - **Containers** (auto-extracted): `.zip` contents extracted and processed individually
56
+ - **Skipped** (with warning): `.xlsx`, `.xls`, `.doc`, `.docx`, `.txt`, `.rar`, `.7z`
56
57
 
57
58
  ### Traversal Rules
58
59
 
@@ -60,6 +61,7 @@ Multilingual support: Filipino/Tagalog, Bahasa Indonesia/Malay, Vietnamese, and
60
61
  2. **Root-level files** — classified as UNKNOWN (no folder context)
61
62
  3. **Max depth** — 10 levels (prevents runaway recursion)
62
63
  4. **Hidden files/dirs** — skipped (anything starting with `.`)
64
+ 5. **ZIP files** — extracted to temp dir, contents scanned through same pipeline (max 1 level, no nested ZIPs)
63
65
 
64
66
  ## Cloud Provider Support
65
67
 
@@ -85,11 +87,17 @@ The `--source` flag accepts public share links from Dropbox, Google Drive, and O
85
87
  # Scan + classify local directory
86
88
  clio jobs document-collection ingest --source ./client-docs/ [--json]
87
89
 
90
+ # Scan + classify a ZIP file (auto-extracted)
91
+ clio jobs document-collection ingest --source ./client-docs.zip [--json]
92
+
88
93
  # Cloud sources — Dropbox, Google Drive, OneDrive
89
94
  clio jobs document-collection ingest --source "https://www.dropbox.com/scl/fo/.../folder?rlkey=..." [--json]
90
95
  clio jobs document-collection ingest --source "https://drive.google.com/file/d/FILE_ID/view" [--json]
91
96
  clio jobs document-collection ingest --source "https://1drv.ms/f/s!..." [--json]
92
97
 
98
+ # Dropbox file link to a ZIP (auto-extracted)
99
+ clio jobs document-collection ingest --source "https://www.dropbox.com/scl/fi/.../docs.zip?rlkey=...&dl=0" [--json]
100
+
93
101
  # With timeout for large cloud downloads
94
102
  clio jobs document-collection ingest --source "https://www.dropbox.com/..." --timeout 120000 [--json]
95
103
 
@@ -97,24 +105,38 @@ clio jobs document-collection ingest --source "https://www.dropbox.com/..." --ti
97
105
  clio jobs document-collection ingest --source ./scans/ --type invoice [--json]
98
106
  clio jobs document-collection ingest --source ./scans/ --type credit-note-customer [--json]
99
107
 
100
- # Scan + upload (decrypt encrypted PDFs with password)
101
- clio jobs document-collection ingest --source ./bank-docs/ --upload --bank-account "DBS Checking" --pdf-password "mypass" --json
108
+ # Upload a ZIP of invoices via Magic (all files treated as same type)
109
+ clio magic create --file ./invoices.zip --type invoice --json
110
+
111
+ # Scan + upload (encrypted PDFs with password embedded in filename)
112
+ clio jobs document-collection ingest --source ./bank-docs/ --upload --bank-account "DBS Checking" --json
113
+ # To decrypt: rename encrypted PDF to: receipt__pw__actualPassword.pdf
114
+ # The __pw__ delimiter is case-insensitive; the password itself is case-sensitive.
102
115
  ```
103
116
 
104
117
  ### Options
105
118
 
106
119
  | Flag | Description |
107
120
  |------|-------------|
108
- | `--source <path\|url>` | Local directory path or public cloud share link — Dropbox, Google Drive, OneDrive (required) |
121
+ | `--source <path\|url>` | Local directory, .zip file, or public cloud share link — Dropbox, Google Drive, OneDrive (required). ZIPs are auto-extracted. |
109
122
  | `--type <type>` | Force all files to: `invoice`, `bill`, `credit-note-customer`, `credit-note-supplier`, or `bank-statement` |
110
123
  | `--upload` | Upload classified files to Jaz after scanning (requires auth) |
111
124
  | `--bank-account <name-or-id>` | Bank account name or resourceId (required for bank statements) |
112
- | `--pdf-password <password>` | Password for encrypted PDFs — same password applied to all. Requires `qpdf` installed (`brew install qpdf`) |
113
125
  | `--api-key <key>` | API key for upload (or use `JAZ_API_KEY` env var) |
114
126
  | `--timeout <ms>` | Download timeout in milliseconds (default: 30000 for files, 120000 for folders) |
115
127
  | `--currency <code>` | Functional/reporting currency label |
116
128
  | `--json` | Structured JSON output with absolute file paths |
117
129
 
130
+ ### Encrypted PDF Passwords
131
+
132
+ Embed the password in the filename using the `__pw__` pattern:
133
+ ```
134
+ receipt__pw__s3cRetP@ss.pdf → password: "s3cRetP@ss", display name: "receipt.pdf"
135
+ ```
136
+ - `__pw__` is case-insensitive (`__PW__`, `__Pw__`, etc.)
137
+ - The password after `__pw__` is case-sensitive
138
+ - Requires `qpdf` installed (`brew install qpdf`)
139
+
118
140
  ### JSON Output
119
141
 
120
142
  The `--json` output includes absolute file paths, classification, and size for each file. The AI agent uses these paths to upload via the api skill.
@@ -171,7 +193,7 @@ sudo apt install qpdf # Ubuntu/Debian
171
193
  choco install qpdf # Windows
172
194
  ```
173
195
 
174
- When `--upload` is used with `--pdf-password`, encrypted PDFs are decrypted to a temp file, uploaded, then cleaned up. The same password is applied to all encrypted files in the batch.
196
+ Passwords are embedded in the filename using the `__pw__` pattern: `receipt__pw__myPass.pdf`. During upload, encrypted PDFs with a filename password are decrypted to a temp file, uploaded, then cleaned up.
175
197
 
176
198
  ### Error Handling (JSON mode)
177
199
 
@@ -180,7 +202,7 @@ If encrypted PDFs are found during `--upload` without the required dependencies,
180
202
  | Error Code | Condition | Action |
181
203
  |------------|-----------|--------|
182
204
  | `ENCRYPTED_PDF_NO_QPDF` | qpdf not installed | Install qpdf, then retry |
183
- | `ENCRYPTED_PDF_NO_PASSWORD` | No `--pdf-password` provided | Retry with `--pdf-password <password>` |
205
+ | `ENCRYPTED_PDF_NO_PASSWORD` | Encrypted PDF without `__pw__` in filename | Rename file to embed password: `name__pw__password.pdf` |
184
206
 
185
207
  ## Phases (Blueprint)
186
208
 
@@ -193,6 +215,24 @@ When run without `ingest` subcommand, produces a 4-phase blueprint:
193
215
 
194
216
  The AI agent then uses the classified file paths to upload via the Jaz Magic API (see api skill for endpoint details).
195
217
 
218
+ ## ZIP File Support
219
+
220
+ ZIP files are treated as containers — their contents are extracted and each file is processed individually through the same scan/classify pipeline.
221
+
222
+ ### Supported Scenarios
223
+
224
+ 1. **ZIP in a scanned directory**: Extracted to a temp dir, contents scanned recursively
225
+ 2. **ZIP as --source**: `clio jobs document-collection ingest --source ./archive.zip` extracts and scans
226
+ 3. **ZIP from Dropbox file link**: Auto-extracted after download
227
+ 4. **ZIP via clio magic create**: `clio magic create --file archive.zip --type invoice` extracts and uploads each file
228
+
229
+ ### Limitations
230
+
231
+ - Nested ZIPs (ZIP inside ZIP) are not extracted — only one level
232
+ - Password-protected ZIPs are not supported (adm-zip limitation)
233
+ - Max ZIP size: 500MB
234
+ - `.rar` and `.7z` are still skipped (no built-in support)
235
+
196
236
  ## Relationship to Other Skills
197
237
 
198
238
  - **api skill** — Field names, auth headers, error codes for Magic endpoints. Agent uses this to upload classified files.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: jaz-recipes
3
- version: 4.4.0
3
+ version: 4.6.0
4
4
  description: 16 IFRS-compliant recipes for complex multi-step accounting in Jaz — prepaid amortization, deferred revenue, loan schedules, IFRS 16 leases, hire purchase, fixed deposits, asset disposal, FX revaluation, ECL provisioning, IAS 37 provisions, dividends, intercompany, and capital WIP. Each recipe includes journal entries, capsule structure, and verification steps. Paired with 10 financial calculators that produce execution-ready blueprints with workings.
5
5
  license: MIT
6
6
  compatibility: Works with Claude Code, Claude Cowork, Claude.ai, and any agent that reads markdown. For API payloads, load the jaz-api skill alongside this one.
@@ -27,7 +27,9 @@ export const JOURNAL_REQUIRED_FIELDS = [
27
27
  { field: 'valueDate', label: 'Date', hint: '--date <YYYY-MM-DD>', check: (j) => !!j.valueDate },
28
28
  { field: 'journalEntries', label: 'Journal entries', hint: '--entries <json>', check: (j) => j.journalEntries?.length > 0 },
29
29
  { field: 'accountResourceId', label: 'Account', hint: '--account <name or UUID>', check: (e) => !!(e.accountResourceId || e.organizationAccountResourceId), perLineItem: true },
30
- { field: 'amount', label: 'Amount', hint: 'via --entries', check: (e) => e.amount != null && e.amount > 0, perLineItem: true },
30
+ { field: 'amount', label: 'Amount', hint: 'via --entries', check: (e) => (e.amount != null && e.amount > 0) ||
31
+ (e.debitAmount != null && e.debitAmount > 0) ||
32
+ (e.creditAmount != null && e.creditAmount > 0), perLineItem: true },
31
33
  ];
32
34
  // ── Core Validation ─────────────────────────────────────────────
33
35
  /**
@@ -91,14 +93,17 @@ specs, lineItemsKey = 'lineItems') {
91
93
  ? { status: 'ok', resourceId: acctId }
92
94
  : { status: 'missing', hint: '--account <name or UUID>' };
93
95
  // Journal entries: show amount/type instead of name/unitPrice
96
+ // GET returns debitAmount/creditAmount (not amount/type)
94
97
  if (lineItemsKey === 'journalEntries') {
95
98
  const amountSpec = liSpecs.find((s) => s.field === 'amount');
96
99
  const amountOk = amountSpec ? amountSpec.check(li) : true;
100
+ const entryAmount = li.amount ?? li.debitAmount ?? li.creditAmount ?? null;
101
+ const entryType = li.type ?? (li.debitAmount > 0 ? 'DEBIT' : li.creditAmount > 0 ? 'CREDIT' : null);
97
102
  return {
98
103
  index: i,
99
- name: li.description || li.type || null,
104
+ name: li.description || entryType || null,
100
105
  nameStatus: 'ok',
101
- unitPrice: li.amount ?? null,
106
+ unitPrice: entryAmount,
102
107
  unitPriceStatus: amountOk ? 'ok' : 'missing',
103
108
  account,
104
109
  };
@@ -358,10 +363,25 @@ function sanitizeJournalEntry(e) {
358
363
  const acctId = e.accountResourceId || e.organizationAccountResourceId;
359
364
  if (acctId)
360
365
  clean.accountResourceId = acctId;
361
- if (e.amount != null)
366
+ // Amount/type: GET uses debitAmount/creditAmount, PUT uses amount+type
367
+ if (e.amount != null) {
362
368
  clean.amount = e.amount;
363
- if (e.type)
364
- clean.type = e.type; // DEBIT or CREDIT
369
+ }
370
+ else if (e.debitAmount != null && e.debitAmount > 0) {
371
+ clean.amount = e.debitAmount;
372
+ }
373
+ else if (e.creditAmount != null && e.creditAmount > 0) {
374
+ clean.amount = e.creditAmount;
375
+ }
376
+ if (e.type) {
377
+ clean.type = e.type; // Already in PUT form (DEBIT or CREDIT)
378
+ }
379
+ else if (e.debitAmount != null && e.debitAmount > 0) {
380
+ clean.type = 'DEBIT';
381
+ }
382
+ else if (e.creditAmount != null && e.creditAmount > 0) {
383
+ clean.type = 'CREDIT';
384
+ }
365
385
  if (e.description)
366
386
  clean.description = e.description;
367
387
  if (e.contactResourceId)
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import prompts from 'prompts';
2
3
  import { readFileSync } from 'node:fs';
3
4
  import { generateMonthEndBlueprint } from '../core/jobs/month-end/blueprint.js';
4
5
  import { generateQuarterEndBlueprint } from '../core/jobs/quarter-end/blueprint.js';
@@ -272,11 +273,10 @@ export function registerJobsCommand(program) {
272
273
  const ingestCmd = docCollection
273
274
  .command('ingest')
274
275
  .description('Scan and classify client documents — optionally upload via Magic API')
275
- .requiredOption('--source <path>', 'Local directory path or public share URL (Dropbox, Google Drive, OneDrive)')
276
+ .requiredOption('--source <path>', 'Local directory, .zip file, or public share URL (Dropbox, Google Drive, OneDrive). ZIPs are auto-extracted.')
276
277
  .option('--type <type>', 'Force document type: invoice, bill, credit-note-customer, credit-note-supplier, or bank-statement')
277
278
  .option('--upload', 'Upload classified files to Jaz after scanning (requires auth)')
278
279
  .option('--bank-account <name-or-id>', 'Bank account name or resourceId (required for bank statements)')
279
- .option('--pdf-password <password>', 'Password for encrypted PDFs (same password applied to all)')
280
280
  .option('--api-key <key>', 'API key (or use JAZ_API_KEY env var)')
281
281
  .option('--timeout <ms>', 'Download timeout in milliseconds for cloud sources (default: 30000)', parseInt)
282
282
  .option('--currency <code>', 'Functional/reporting currency (e.g. SGD)')
@@ -342,46 +342,55 @@ export function registerJobsCommand(program) {
342
342
  process.exit(1);
343
343
  }
344
344
  // ── Encrypted PDF checks ──────────────────────────────────────
345
- const encryptedFiles = plan.folders
346
- .flatMap(f => f.files.filter(file => file.encrypted))
347
- .map(f => `${f.folder}/${f.filename}`);
348
- if (encryptedFiles.length > 0) {
349
- if (!isQpdfAvailable()) {
350
- if (opts.json) {
351
- console.log(JSON.stringify({
352
- error: 'ENCRYPTED_PDF_NO_QPDF',
353
- message: 'Encrypted PDFs found but qpdf is not installed',
354
- action: 'Install qpdf: brew install qpdf (macOS) or sudo apt install qpdf (Linux), then retry',
355
- encryptedFiles,
356
- }));
357
- }
358
- else {
359
- console.error(chalk.red('Error: Encrypted PDFs found but qpdf is not installed.'));
360
- console.error(chalk.yellow('Install qpdf to decrypt before upload:'));
361
- console.error(chalk.dim(' macOS: brew install qpdf'));
362
- console.error(chalk.dim(' Ubuntu: sudo apt install qpdf'));
363
- console.error(chalk.dim(' Windows: choco install qpdf'));
364
- }
345
+ // Files with __pw__<password> in the filename auto-supply their password.
346
+ // Only files without a filePassword need qpdf + user action.
347
+ const encryptedFiles = plan.folders.flatMap(f => f.files.filter(file => file.encrypted));
348
+ const needPassword = encryptedFiles.filter(f => !f.filePassword);
349
+ if (encryptedFiles.length > 0 && !isQpdfAvailable()) {
350
+ const paths = encryptedFiles.map(f => `${f.folder}/${f.filename}`);
351
+ if (opts.json) {
352
+ console.log(JSON.stringify({
353
+ error: 'ENCRYPTED_PDF_NO_QPDF',
354
+ message: 'Encrypted PDFs found but qpdf is not installed',
355
+ action: 'Install qpdf: brew install qpdf (macOS) or sudo apt install qpdf (Linux), then retry',
356
+ encryptedFiles: paths,
357
+ }));
358
+ }
359
+ else {
360
+ console.error(chalk.red('Error: Encrypted PDFs found but qpdf is not installed.'));
361
+ console.error(chalk.yellow('Install qpdf to decrypt before upload:'));
362
+ console.error(chalk.dim(' macOS: brew install qpdf'));
363
+ console.error(chalk.dim(' Ubuntu: sudo apt install qpdf'));
364
+ console.error(chalk.dim(' Windows: choco install qpdf'));
365
+ }
366
+ process.exit(1);
367
+ }
368
+ if (needPassword.length > 0) {
369
+ const paths = needPassword.map(f => `${f.folder}/${f.filename}`);
370
+ if (opts.json) {
371
+ // Agents can't type — error with actionable instructions
372
+ console.log(JSON.stringify({
373
+ error: 'ENCRYPTED_PDF_NO_PASSWORD',
374
+ message: `${needPassword.length} encrypted PDF(s) found without a password`,
375
+ action: 'Embed password in filename: rename to filename__pw__password.pdf',
376
+ encryptedFiles: paths,
377
+ }));
365
378
  process.exit(1);
366
379
  }
367
- if (!opts.pdfPassword) {
368
- if (opts.json) {
369
- console.log(JSON.stringify({
370
- error: 'ENCRYPTED_PDF_NO_PASSWORD',
371
- message: `${encryptedFiles.length} encrypted PDF(s) found — password required`,
372
- action: 'Retry with --pdf-password <password>',
373
- encryptedFiles,
374
- }));
380
+ // Interactive mode — prompt user for each encrypted file
381
+ console.error(chalk.yellow(`\n${needPassword.length} encrypted PDF(s) need a password:\n`));
382
+ for (const f of needPassword) {
383
+ const { password } = await prompts({
384
+ type: 'text',
385
+ name: 'password',
386
+ message: `PDF password for ${f.folder}/${f.filename}`,
387
+ });
388
+ if (!password) {
389
+ console.error(chalk.red('Aborted — no password provided.'));
390
+ console.error(chalk.dim('Tip: embed password in filename to skip prompts: filename__pw__password.pdf'));
391
+ process.exit(1);
375
392
  }
376
- else {
377
- console.error(chalk.red(`Error: ${encryptedFiles.length} encrypted PDF(s) found — password required.`));
378
- console.error(chalk.dim('Encrypted files:'));
379
- for (const f of encryptedFiles)
380
- console.error(chalk.dim(` ${f}`));
381
- console.error();
382
- console.error(chalk.dim('Retry with: --pdf-password <password>'));
383
- }
384
- process.exit(1);
393
+ f.filePassword = password;
385
394
  }
386
395
  }
387
396
  // ── Upload with auth ─────────────────────────────────────────
@@ -402,7 +411,6 @@ export function registerJobsCommand(program) {
402
411
  plan,
403
412
  client,
404
413
  bankAccountId,
405
- pdfPassword: opts.pdfPassword,
406
414
  onProgress: apiOpts.json ? undefined : printUploadProgress,
407
415
  });
408
416
  const result = { ...plan, upload };