jaz-cli 2.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.
@@ -0,0 +1,751 @@
1
+ # Jaz API Error Catalog
2
+
3
+ > Every error encountered during live testing against production Jaz API, with root cause and fix.
4
+ > Organized by endpoint. Use this to diagnose issues fast.
5
+
6
+ ---
7
+
8
+ ## Error Response Format
9
+
10
+ All Jaz API errors follow this shape:
11
+
12
+ ```json
13
+ {
14
+ "error": {
15
+ "error_type": "validation_error",
16
+ "errors": ["human-readable message"],
17
+ "error_details": [{ "attribute_name": "fieldName", "error_message": "specific issue" }]
18
+ }
19
+ }
20
+ ```
21
+
22
+ Common `error_type` values:
23
+ | error_type | HTTP Status | Meaning |
24
+ |-----------|-------------|---------|
25
+ | `validation_error` | 422 | Required field missing or invalid value |
26
+ | `invalid_request_body` | 400 | Malformed JSON or unexpected structure |
27
+ | `not_found` | 404 | Resource or endpoint doesn't exist |
28
+ | `internal_server_error` | 500 | Server-side issue (may be transient) |
29
+ | `mutation_error` | 422 | GraphQL mutation rejected by platform backend (added in backend PR #112) |
30
+
31
+ ---
32
+
33
+ ## Chart of Accounts Errors
34
+
35
+ ### "Account Classification Type not found" (400)
36
+ **Cause**: `classificationType` value doesn't match one of the 12 valid values.
37
+ **Wrong values we tried**: `"Revenue"`, `"REVENUE"`, `"revenue"`, `"INCOME"`, `"OPERATING_REVENUE"`, `"Sales"`, `"Asset"`, `"ASSET"`
38
+ **Fix**: Use the exact `accountType` values from GET response:
39
+ ```
40
+ "Bank Accounts", "Cash", "Current Asset", "Current Liability", "Direct Costs",
41
+ "Fixed Asset", "Inventory", "Non-current Liability", "Operating Expense",
42
+ "Operating Revenue", "Other Revenue", "Shareholders Equity"
43
+ ```
44
+ **Key insight**: `classificationType` in POST uses the same values as `accountType` from GET. NOT `accountClass` values (which are broader: Asset, Liability, Equity, Revenue, Expense).
45
+
46
+ ### "ORGANIZATION_CHART_OF_ACCOUNT_DUPLICATED" (400)
47
+ **Cause**: Sending an account `name` that already exists in the org.
48
+ **Fix**: Fetch existing accounts first (`GET /chart-of-accounts`), skip names already present. Upsert matches by name — if you want to update an existing account, the name match handles it automatically.
49
+
50
+ ### Wrong wrapper field
51
+ **Cause**: Using `{ chartOfAccounts: [...] }` instead of `{ accounts: [...] }`.
52
+ **Fix**: Body must be `{ "accounts": [...] }`.
53
+
54
+ ### Wrong currency field
55
+ **Cause**: Using `currencyCode` instead of `currency` in the POST body.
56
+ **Fix**: Use `currency` (not `currencyCode`) even though GET returns `currencyCode`.
57
+
58
+ ---
59
+
60
+ ## Contact Errors
61
+
62
+ ### "phone must be a valid E.164 formatted phone number" (422)
63
+ **Cause**: Phone number has spaces, dashes, or wrong digit count.
64
+ **Wrong**: `"+65 6234 5678"`, `"+63 2 8876 5432"`, `"6591234567"` (no +)
65
+ **Fix**: Strict E.164 — no spaces, no dashes, starts with `+`:
66
+ - SG landlines: `+65` + 8 digits → `"+6562345678"`
67
+ - SG mobile: `+65` + 8 digits → `"+6591234567"`
68
+ - PH mobile: `+63` + 10 digits → `"+639171234567"`
69
+ - PH landline: `+63` + 10 digits → `"+63288765432"` (area code included in 10 digits)
70
+ - US: `+1` + 10 digits → `"+12125550199"`
71
+ - If no valid phone, omit the field entirely (don't send empty string)
72
+
73
+ ### "billingName is a required field" (422)
74
+ **Cause**: Missing `billingName` field in POST body.
75
+ **Fix**: Always include `billingName` — set it to same value as `name`.
76
+
77
+ ---
78
+
79
+ ## Tag Errors
80
+
81
+ ### "tagName is a required field" (422)
82
+ **Cause**: Using an unrecognized field name in POST body.
83
+ **Fix**: POST body accepts either `{ "tagName": "Department: Sales" }` or `{ "name": "Department: Sales" }` (alias). Both work after the DX overhaul.
84
+
85
+ ---
86
+
87
+ ## Custom Field Errors
88
+
89
+ ### "Invalid request body" (400) — missing printOnDocuments
90
+ **Cause**: Missing the required `printOnDocuments` field.
91
+ **Fix**: Always include `printOnDocuments: false` (or `true`) in POST body.
92
+ ```json
93
+ { "name": "PO Number", "type": "TEXT", "printOnDocuments": false }
94
+ ```
95
+
96
+ ### "Invalid request body" (400) — appliesTo field
97
+ **Cause**: Sending the `appliesTo` array field in the POST body.
98
+ **Fix**: Do NOT send `appliesTo` — it causes "Invalid request body". Only send: `name`, `type`, `printOnDocuments` (and `options` for DROPDOWN type).
99
+ ```json
100
+ // WRONG:
101
+ { "name": "PO Number", "type": "TEXT", "printOnDocuments": false, "appliesTo": ["INVOICE"] }
102
+
103
+ // CORRECT:
104
+ { "name": "PO Number", "type": "TEXT", "printOnDocuments": false }
105
+ ```
106
+ Valid `type` values: `"TEXT"`, `"DATE"`, `"DROPDOWN"` (UPPERCASE).
107
+
108
+ ---
109
+
110
+ ## Invoice / Bill Errors
111
+
112
+ ### "lineItems[0].accountResourceId is required if [saveAsDraft] is false" (422)
113
+ **Cause**: `saveAsDraft` defaults to `false`. If omitted or explicitly `false`, line items must have `accountResourceId`.
114
+ **Fix**: Either:
115
+ 1. Include `accountResourceId` on every line item (preferred — needs CoA resolved first)
116
+ 2. Use `saveAsDraft: true` to create a draft (user finalizes)
117
+
118
+ ### "contactResourceId is a required field" (422)
119
+ **Cause**: Missing contact reference.
120
+ **Fix**: Ensure the contacts step ran successfully and the template contact index maps to a created contact ID.
121
+
122
+ ### Cascading failures from CoA
123
+ **Symptom**: Invoices/bills fail because `accountResourceId` couldn't be resolved.
124
+ **Root cause**: CoA step failed → items step failed → invoices/bills fail (no account to assign).
125
+ **Fix**: Ensure CoA step succeeds first. Key CoA IDs by both `name` AND `code` (templates reference by code like `"4000"`).
126
+
127
+ ---
128
+
129
+ ## Payment Errors
130
+
131
+ ### Bill payments standalone endpoint — FIXED (PR #112)
132
+ **Was**: `POST /bills/{id}/payments` always returned 500 regardless of payload correctness.
133
+ **Root cause**: Nil pointer dereference in the API backend `mappings/bills.go` — the `TaxCurrency` nil check was outside the `TransactionFee != nil` guard. Any payment without transaction fees caused a panic.
134
+ **Fix**: Backend PR #112 moved the TaxCurrency check inside the nil guard, matching the working pattern in `invoices.go`.
135
+ **Status**: Standalone bill payments now work for basic payments. Embed-in-creation pattern remains a valid alternative.
136
+ **Note**: `TransactionFeeCollected` is NOT supported on bill payments (model field missing). Only invoice payments support collected fees.
137
+
138
+ ### Various field errors (422) — payments require 6 specific fields
139
+ **Cause**: Using wrong field names. Common mistakes:
140
+ - `amount` instead of `paymentAmount`
141
+ - `paymentDate` instead of `valueDate`
142
+ - `bankAccountResourceId` instead of `accountResourceId`
143
+ - Missing `transactionAmount`, `paymentMethod`, or `reference`
144
+
145
+ **Fix**: Payments require ALL 6 fields:
146
+ ```json
147
+ { "payments": [{
148
+ "paymentAmount": 100,
149
+ "transactionAmount": 100,
150
+ "accountResourceId": "bank-uuid",
151
+ "paymentMethod": "BANK_TRANSFER",
152
+ "reference": "PAY-001",
153
+ "valueDate": "2026-02-08"
154
+ }] }
155
+ ```
156
+
157
+ **Cross-currency**: `paymentAmount` = bank account currency (actual cash), `transactionAmount` = transaction document currency (invoice/bill/credit note — applied to balance). For same-currency, both equal. For FX (e.g., USD invoice, SGD bank at 1.35): `paymentAmount: 1350`, `transactionAmount: 1000`.
158
+
159
+ ### INSUFFICIENT_BALANCE_ON_SALE (422) — FX payment field swap
160
+ **Cause**: Swapping `paymentAmount` and `transactionAmount` in cross-currency payments. If `transactionAmount` exceeds the invoice balance, this error is thrown.
161
+ **Fix**: `transactionAmount` = transaction document currency amount (invoice/bill/credit note — must not exceed balance). `paymentAmount` = bank currency amount (the cash).
162
+ ```json
163
+ // WRONG — transactionAmount (1350) exceeds USD invoice balance (1000):
164
+ { "paymentAmount": 1000, "transactionAmount": 1350, ... }
165
+
166
+ // CORRECT — transactionAmount (1000) matches USD invoice, paymentAmount (1350) is SGD cash:
167
+ { "paymentAmount": 1350, "transactionAmount": 1000, ... }
168
+ ```
169
+
170
+ ### Missing array wrapper
171
+ **Cause**: Sending flat payment object instead of wrapped in `payments` array.
172
+ **Fix**: Always wrap: `{ "payments": [{ ... }] }` — even for a single payment.
173
+
174
+ ---
175
+
176
+ ## Currency Errors
177
+
178
+ ### 404 on rate endpoints — WRONG PATH (common mistake)
179
+ **Cause**: Using `/organization/currencies` (nested) instead of `/organization-currencies` (hyphenated) for rate endpoints.
180
+ **Endpoints that 404 (wrong paths)**:
181
+ - `/api/v1/organization/currencies/rates` → 404
182
+ - `/api/v1/organization/currencies/USD/rate` → 404
183
+ - `/api/v1/organization/currencies/{id}/rate` → 404
184
+ **Fix**: Rate endpoints use the **hyphenated** path `/organization-currencies` (NOT `/organization/currencies`):
185
+ ```
186
+ POST /api/v1/organization-currencies/:currencyCode/rates Set rate
187
+ GET /api/v1/organization-currencies/:currencyCode/rates List rates
188
+ GET /api/v1/organization-currencies/:currencyCode/rates/:id Get rate
189
+ PUT /api/v1/organization-currencies/:currencyCode/rates/:id Update rate
190
+ DELETE /api/v1/organization-currencies/:currencyCode/rates/:id Delete rate
191
+ ```
192
+ Enable currencies first via `POST /organization/currencies`, then set rates via `/organization-currencies/:code/rates`.
193
+
194
+ ### "Cannot set rate for organization base currency" (400)
195
+ **Cause**: Trying to POST/PUT a rate for the org's base currency (e.g., SGD for a Singapore org).
196
+ **Fix**: Only set rates for non-base currencies. GET also returns 400: `"Cannot lookup rate for organization base currency"`.
197
+
198
+ ### "rate must be greater than 0" (422)
199
+ **Cause**: Sending `rate: 0` or a negative rate.
200
+ **Fix**: Rate must be a positive number (> 0). Very small values like `0.0001` are accepted.
201
+
202
+ ### "rateApplicableFrom does not match the 2006-01-02 format" (422)
203
+ **Cause**: Using ISO datetime (e.g., `"2026-02-10T00:00:00Z"`) instead of date-only format.
204
+ **Fix**: Use `YYYY-MM-DD` format only: `"rateApplicableFrom": "2026-02-10"`.
205
+
206
+ ### "Invalid date range" / INVALID_DATE_RANGE (422)
207
+ **Cause**: `rateApplicableTo` is before `rateApplicableFrom`.
208
+ **Fix**: Ensure `rateApplicableTo` is after `rateApplicableFrom`, or omit `rateApplicableTo` entirely.
209
+
210
+ ### Rates appear inverted (wrong direction)
211
+ **Cause**: POSTing a sourceToFunctional rate (1 foreign = X base) as the `rate` field, which expects functionalToSource (1 base = X foreign).
212
+ **Symptom**: UI shows "1 SGD = 0.0088 JPY" instead of "1 SGD ≈ 111 JPY" — the reciprocal of what you intended.
213
+ **Fix**: Invert before POSTing: `rate = 1 / yourRate`. If your data says "1 JPY = 0.009 SGD", POST `rate: 111.11`.
214
+
215
+ ### Wrong body format for enabling
216
+ **Cause**: Using `{ currencyCode: "USD" }` instead of array format.
217
+ **Fix**: `{ "currencies": ["USD", "EUR"] }` — array of ISO code strings.
218
+
219
+ ---
220
+
221
+ ## FX (Foreign Currency) Errors
222
+
223
+ ### `currencyCode` string silently ignored (NO ERROR — major gotcha)
224
+ **Cause**: Using `currencyCode: "MYR"` (string) on invoice/bill creation for a foreign currency transaction.
225
+ **Behavior**: The API returns 201 (success!) but **silently ignores** the `currencyCode` field. The invoice is created in the org's base currency (e.g., SGD) with rate 1:1. No error is returned.
226
+ **Fix**: MUST use the `currency` OBJECT form:
227
+ ```json
228
+ // WRONG — silently ignored, invoice created in base currency (SGD):
229
+ { "contactResourceId": "uuid", "currencyCode": "MYR", "lineItems": [...] }
230
+
231
+ // WRONG — string causes "Invalid request body" (400):
232
+ { "contactResourceId": "uuid", "currency": "MYR", "lineItems": [...] }
233
+
234
+ // CORRECT — object form, platform auto-fetches ECB rate:
235
+ { "contactResourceId": "uuid", "currency": { "sourceCurrency": "MYR" }, "lineItems": [...] }
236
+
237
+ // CORRECT — object form with custom rate:
238
+ { "contactResourceId": "uuid", "currency": { "sourceCurrency": "MYR", "exchangeRate": 3.15 }, "lineItems": [...] }
239
+ ```
240
+
241
+ **Rate sources in response** (inspect `currencyExchange.rateSource`):
242
+ - `rateSource: "EXTERNAL"`, `providerName: "FRANKFURTER"` — auto-fetched from ECB
243
+ - `rateSource: "INTERNAL_TRANSACTION"`, `providerName: "CUSTOM"` — user-specified `exchangeRate`
244
+ - `rateSource: "INTERNAL_ORG"` — org-level rate (set via `/organization-currencies/:code/rates`)
245
+
246
+ ### "Invalid request body" (400) — `currency` as string
247
+ **Cause**: Using `currency: "USD"` (string) instead of object form.
248
+ **Fix**: Use object form `currency: { sourceCurrency: "USD" }` or `currency: { sourceCurrency: "USD", exchangeRate: 1.35 }`.
249
+
250
+ ---
251
+
252
+ ## Date Format Errors
253
+
254
+ ### "does not match 2006-01-02 format" (422) — wrong date format on bill payments
255
+ **Cause**: Sending ISO datetime (e.g., `"2026-02-08T00:00:00Z"`) or epoch milliseconds instead of `YYYY-MM-DD`.
256
+ **Fix**: All dates must be `YYYY-MM-DD` strings (e.g., `"2026-02-08"`).
257
+ ```json
258
+ // WRONG:
259
+ { "valueDate": "2026-02-08T00:00:00Z" } // ISO datetime rejected
260
+ { "valueDate": 1770508800000 } // epoch ms rejected
261
+
262
+ // CORRECT:
263
+ { "valueDate": "2026-02-08" } // YYYY-MM-DD string
264
+ ```
265
+
266
+ **Note**: The OAS may declare some date fields as `integer/int64` (e.g., cash journals), but `YYYY-MM-DD` strings work in practice. production clients sends all dates as `YYYY-MM-DD` via Python `date` type.
267
+
268
+ ---
269
+
270
+ ## CoA Code Mapping Errors
271
+
272
+ ### Account lookup fails because codes don't match template
273
+ **Cause**: Pre-existing accounts may have different codes than templates. For example:
274
+ - "Cost of Goods Sold" = code 310 in the API, but code 5000 in template
275
+ - "Accounts Receivable" = `code: null` in the API
276
+ **Fix**: Map template accounts to resource IDs via **name matching**, not code matching. Resource IDs are the universal identifier.
277
+ ```typescript
278
+ // Build map keyed by BOTH name and code:
279
+ ctx.coaIds[acct.name] = acct.resourceId;
280
+ if (acct.code) ctx.coaIds[acct.code] = acct.resourceId;
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Scheduler Errors
286
+
287
+ ### "bill.saveAsDraft is a required field" (422) / "invoice.saveAsDraft is a required field"
288
+ **Cause**: Transaction data sent flat instead of wrapped in type key.
289
+ **Wrong**:
290
+ ```json
291
+ { "repeat": "MONTHLY", "startDate": "...", "contactResourceId": "...", "saveAsDraft": false }
292
+ ```
293
+ **Fix**: Wrap in `invoice` or `bill` key:
294
+ ```json
295
+ { "repeat": "MONTHLY", "startDate": "...", "invoice": { "contactResourceId": "...", "saveAsDraft": false, ... } }
296
+ ```
297
+
298
+ ### INVALID_SALE_STATUS (422) / INVALID_PURCHASE_STATUS (422)
299
+ **Cause**: Scheduled invoice/bill has `saveAsDraft: true` on the wrapped document.
300
+ **Fix**: Scheduled transactions MUST use `saveAsDraft: false`. This means every line item needs `accountResourceId`.
301
+ ```json
302
+ // WRONG — causes INVALID_SALE_STATUS:
303
+ { "repeat": "MONTHLY", "invoice": { "saveAsDraft": true, ... } }
304
+
305
+ // CORRECT:
306
+ { "repeat": "MONTHLY", "invoice": { "saveAsDraft": false, "lineItems": [{ "accountResourceId": "uuid", ... }] } }
307
+ ```
308
+
309
+ ### Scheduler defaults to ONE_TIME
310
+ **Cause**: Using `frequency` or `interval` instead of `repeat` for the recurrence field.
311
+ **Fix**: The creation field is `repeat` (NOT `frequency` or `interval`). Both `frequency` and `interval` are silently ignored, defaulting to ONE_TIME.
312
+ ```json
313
+ // WRONG — creates ONE_TIME schedule:
314
+ { "frequency": "MONTHLY", ... }
315
+ { "interval": "MONTHLY", ... }
316
+
317
+ // CORRECT — creates MONTHLY schedule:
318
+ { "repeat": "MONTHLY", ... }
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Bank Record Errors
324
+
325
+ ### No JSON POST endpoint exists
326
+ There is no JSON POST endpoint for creating bank records. Use multipart import:
327
+
328
+ **Workaround**: Use multipart import instead:
329
+ ```
330
+ POST /api/v1/magic/importBankStatementFromAttachment
331
+ Content-Type: multipart/form-data
332
+
333
+ Fields:
334
+ - sourceFile: CSV/OFX bank statement file (NOT "file")
335
+ - accountResourceId: UUID of the bank account CoA entry (NOT "bankAccountResourceId")
336
+ - businessTransactionType: "BANK_STATEMENT"
337
+ - sourceType: "FILE" (valid values: URL, FILE)
338
+ ```
339
+
340
+ Production clients use this multipart endpoint exclusively. Multipart import is the more reliable method.
341
+
342
+ **Legacy guidance** (still valid for general bank record handling):
343
+ 1. Always use `Math.abs()` on amounts — amounts must be positive
344
+ 2. Use `type: "CREDIT"` or `type: "DEBIT"` to indicate direction
345
+ 3. Check that `bankAccountResourceId` is a valid CoA entry with `accountType: "Bank Accounts"`
346
+
347
+ ---
348
+
349
+ ## Report Errors
350
+
351
+ ### "endDate is a required field" (422) — trial balance
352
+ **Cause**: Missing `endDate` in trial balance request.
353
+ **Fix**: Always include both `startDate` and `endDate`:
354
+ ```json
355
+ { "startDate": "2025-11-10", "endDate": "2026-02-08" }
356
+ ```
357
+
358
+ ### "primarySnapshotDate is a required field" (422) — balance sheet
359
+ **Cause**: Using `endDate` instead of `primarySnapshotDate` for balance sheet.
360
+ **Fix**: Balance sheet uses `primarySnapshotDate`:
361
+ ```json
362
+ { "primarySnapshotDate": "2026-02-28" }
363
+ ```
364
+
365
+ ### "primarySnapshotDate / secondarySnapshotDate is a required field" (422) — P&L
366
+ **Cause**: Using `startDate`/`endDate` instead of snapshot dates for profit & loss.
367
+ **Fix**: P&L uses `primarySnapshotDate` and `secondarySnapshotDate`:
368
+ ```json
369
+ { "primarySnapshotDate": "2026-02-28", "secondarySnapshotDate": "2026-01-01" }
370
+ ```
371
+
372
+ ### "groupBy is a required field" (422) — general ledger
373
+ **Cause**: Missing required `groupBy` field.
374
+ **Fix**: Include `groupBy: "ACCOUNT"` along with date fields:
375
+ ```json
376
+ { "startDate": "2026-01-01", "endDate": "2026-02-28", "groupBy": "ACCOUNT" }
377
+ ```
378
+
379
+ ### "primaryStartDate / primaryEndDate is a required field" (422) — cashflow
380
+ **Cause**: Using `primarySnapshotDate` or `startDate`/`endDate` for cashflow report.
381
+ **Fix**: Cashflow uses `primaryStartDate`/`primaryEndDate`:
382
+ ```json
383
+ { "primaryStartDate": "2026-01-01", "primaryEndDate": "2026-02-28" }
384
+ ```
385
+
386
+ ### "reportDate is a required field" (422) — cash-balance
387
+ **Cause**: Using `startDate`/`endDate` for cash-balance report.
388
+ **Fix**: Cash-balance uses single `reportDate` field:
389
+ ```json
390
+ { "reportDate": "2026-02-28" }
391
+ ```
392
+
393
+ ### "endDate is a required field" (422) — ar-report / ap-report
394
+ **Cause**: Using `primarySnapshotDate` for AR/AP reports.
395
+ **Fix**: AR/AP reports use `endDate`:
396
+ ```json
397
+ { "endDate": "2026-02-28" }
398
+ ```
399
+
400
+ ### "startDate is a required field" (422) — ar-summary / ap-summary
401
+ **Cause**: Missing `startDate` for summary reports.
402
+ **Fix**: AR/AP summary reports use `startDate` + `endDate`:
403
+ ```json
404
+ { "startDate": "2026-01-01", "endDate": "2026-02-28" }
405
+ ```
406
+
407
+ ### "primarySnapshotStartDate / primarySnapshotEndDate is a required field" (422) — equity-movement
408
+ **Cause**: Using other date field names for equity movement report.
409
+ **Fix**: Equity movement uses `primarySnapshotStartDate`/`primarySnapshotEndDate`:
410
+ ```json
411
+ { "primarySnapshotStartDate": "2026-01-01", "primarySnapshotEndDate": "2026-02-28" }
412
+ ```
413
+
414
+ ### Data export field names differ from generate-reports
415
+ **Cause**: Assuming data exports use the same field names as generate-reports.
416
+ **Fix**: Data exports generally use simpler names: P&L export uses `startDate`/`endDate` (NOT `primarySnapshotDate`/`secondarySnapshotDate`). AR export uses `endDate`.
417
+
418
+ ---
419
+
420
+ ## Item Errors
421
+
422
+ ### "appliesToSale is required if [salePrice saleAccountResourceId saleTaxProfile] is present" (422)
423
+ **Cause**: Missing `appliesToSale: true` when sale-related fields are set.
424
+ **Fix**: When creating items with sale fields, MUST include `appliesToSale: true` AND `saleItemName`.
425
+ ```json
426
+ // WRONG — missing appliesToSale and saleItemName:
427
+ { "internalName": "Widget", "salePrice": 25.00, "saleAccountResourceId": "uuid" }
428
+
429
+ // CORRECT:
430
+ { "internalName": "Widget", "itemCode": "WDG-001", "appliesToSale": true, "saleItemName": "Widget", "salePrice": 25.00, "saleAccountResourceId": "uuid" }
431
+ ```
432
+ Same pattern for purchase side: `appliesToPurchase: true` + `purchaseItemName` required when purchase fields present.
433
+
434
+ ### "itemCode is a required field" (422)
435
+ **Cause**: Missing `itemCode` in item creation.
436
+ **Fix**: `itemCode` is always required. It's the unique SKU/code for the item.
437
+
438
+ ### POST /items/search — now available
439
+ `POST /items/search` is now available with the standard search filter syntax. Previously returned 404.
440
+
441
+ ### "CoA ref 'XXXX' not found"
442
+ **Cause**: Template references CoA account by code (e.g., `"4000"`) but the lookup map only has entries keyed by name.
443
+ **Fix**: When building CoA ID maps, key by BOTH `name` AND `code`:
444
+ ```typescript
445
+ ctx.coaIds[acct.name] = acct.resourceId;
446
+ if (acct.code) ctx.coaIds[acct.code] = acct.resourceId;
447
+ ```
448
+
449
+ ---
450
+
451
+ ## Inventory Item Errors
452
+
453
+ ### ITEM_UNIT_EMPTY_ERROR (422)
454
+ **Cause**: Missing `unit` field on inventory item creation.
455
+ **Fix**: Include `unit` (string, e.g., `"pcs"`, `"box"`, `"kg"`). The field is `unit` (NOT `itemUnit`, `unitName`, or `measurementUnit`).
456
+
457
+ ### "costingMethod must be one of [FIXED WAC]" (422)
458
+ **Cause**: Invalid costing method value.
459
+ **Fix**: Must be `"FIXED"` or `"WAC"` (NOT `"FIXED_COST"` or `"AVERAGE"`).
460
+
461
+ ### INVALID_ACCOUNT_TYPE_INVENTORY (422)
462
+ **Cause**: `purchaseAccountResourceId` (or another account field) points to a non-Inventory CoA account.
463
+ **Fix**: For inventory items, `purchaseAccountResourceId` MUST point to a CoA account with `accountType: "Inventory"` (NOT Direct Costs). Check CoA accounts via `GET /chart-of-accounts` and find one with `accountType: "Inventory"`.
464
+
465
+ ### INVALID_COST_PRICE (422)
466
+ **Cause**: Using `costingMethod: "FIXED"` — the FIXED method requires a valid cost price but the exact field name is not documented.
467
+ **Fix**: Use `costingMethod: "WAC"` (Weighted Average Cost) which works without specifying a cost price upfront.
468
+
469
+ ---
470
+
471
+ ## Cash Transfer Errors
472
+
473
+ ### "cashOut is a required field" / "cashIn is a required field" (422)
474
+ **Cause**: Using flat fields `fromAccountResourceId`/`toAccountResourceId`/`amount` instead of sub-objects.
475
+ **Fix**: Cash transfers use `cashOut`/`cashIn` sub-objects:
476
+ ```json
477
+ // WRONG:
478
+ { "fromAccountResourceId": "uuid", "toAccountResourceId": "uuid", "amount": 500 }
479
+
480
+ // CORRECT:
481
+ { "cashOut": { "accountResourceId": "uuid-from", "amount": 500 }, "cashIn": { "accountResourceId": "uuid-to", "amount": 500 } }
482
+ ```
483
+
484
+ ---
485
+
486
+ ## Credit Note Refund Errors
487
+
488
+ ### "refunds is a required field" (422)
489
+ **Cause**: Using `payments` wrapper instead of `refunds`.
490
+ **Fix**: CN refunds use `refunds` wrapper (NOT `payments`):
491
+ ```json
492
+ // WRONG:
493
+ { "payments": [{ "paymentAmount": 75, "paymentMethod": "BANK_TRANSFER", ... }] }
494
+
495
+ // CORRECT:
496
+ { "refunds": [{ "refundAmount": 75, "refundMethod": "BANK_TRANSFER", "transactionAmount": 75, "accountResourceId": "uuid-bank", "reference": "REF-001", "valueDate": "2026-02-09" }] }
497
+ ```
498
+
499
+ ### "refunds[0].refundAmount / refundMethod is a required field" (422)
500
+ **Cause**: Using `paymentAmount`/`paymentMethod` instead of `refundAmount`/`refundMethod`.
501
+ **Fix**: Use `refundAmount` and `refundMethod` (NOT `paymentAmount`/`paymentMethod`).
502
+
503
+ ---
504
+
505
+ ## Bookmark Errors
506
+
507
+ ### "items is a required field" (422)
508
+ **Cause**: Sending flat `name`/`url` instead of `items` array wrapper.
509
+ **Fix**: Bookmarks use `items` array:
510
+ ```json
511
+ // WRONG:
512
+ { "name": "My Bookmark", "url": "https://example.com" }
513
+
514
+ // CORRECT:
515
+ { "items": [{ "name": "My Bookmark", "value": "https://example.com", "categoryCode": "GENERAL_INFORMATION", "datatypeCode": "LINK" }] }
516
+ ```
517
+
518
+ ### "items[0].categoryCode must be one of [...]" (422)
519
+ **Cause**: Invalid category code value.
520
+ **Fix**: Must be one of: `AUDIT_AND_ASSURANCE`, `BANKING_AND_FINANCE`, `BUDGETS_AND_CONTROLS`, `EMPLOYEES_AND_PAYROLL`, `EXTERNAL_DOCUMENTS`, `GENERAL_INFORMATION`, `OWNERS_AND_DIRECTORS`, `TAXATION_AND_COMPLIANCE`, `WORKFLOWS_AND_PROCESSES`.
521
+
522
+ ### "items[0].datatypeCode must be one of [...]" (422)
523
+ **Cause**: Invalid datatype code.
524
+ **Fix**: Must be one of: `TEXT`, `NUMBER`, `BOOLEAN`, `DATE`, `LINK`.
525
+
526
+ ---
527
+
528
+ ## Scheduled Journal Errors
529
+
530
+ ### "schedulerEntries is a required field" (422)
531
+ **Cause**: Using nested `journal` wrapper (like scheduled invoices/bills) instead of flat structure.
532
+ **Fix**: Scheduled journals use FLAT structure with `schedulerEntries`:
533
+ ```json
534
+ // WRONG (nested like invoices/bills):
535
+ { "repeat": "MONTHLY", "journal": { "journalEntries": [...] } }
536
+
537
+ // CORRECT (flat with schedulerEntries):
538
+ { "reference": "JNL-001", "valueDate": "2026-03-01", "saveAsDraft": false, "schedulerEntries": [...], "repeat": "MONTHLY", "startDate": "2026-03-01", "endDate": "2026-12-01" }
539
+ ```
540
+
541
+ ---
542
+
543
+ ## Custom Field PUT Errors
544
+
545
+ ### 500 Internal Server Error on PUT (known bug)
546
+ **Cause**: Validation requires `appliesTo.invoices`, `appliesTo.bills`, `appliesTo.customerCredits`, `appliesTo.supplierCredits`, `appliesTo.payments` — but the endpoint 500s even with correct payload.
547
+ **Workaround**: Cannot update custom fields via API. Delete and recreate instead.
548
+
549
+ ---
550
+
551
+ ## Contact Group PUT Errors
552
+
553
+ ### 500 Internal Server Error on PUT (known bug)
554
+ **Cause**: `PUT /contact-groups/:id` returns 500 regardless of payload structure.
555
+ **Workaround**: Cannot update contact groups via API. Delete and recreate instead.
556
+
557
+ ---
558
+
559
+ ## Fixed Assets Errors
560
+
561
+ ### 422 Invalid date format
562
+ **Cause**: `depreciationStartDate` or `purchaseDate` not in `YYYY-MM-DD` format.
563
+ **Fix**: Use ISO date strings: `"purchaseDate": "2025-01-15"`, `"depreciationStartDate": "2025-02-01"`.
564
+
565
+ ---
566
+
567
+ ## Catalogs POST Errors
568
+
569
+ ### 500 Internal Server Error on POST (known bug)
570
+ **Cause**: `POST /catalogs` returns 500 after passing field validation.
571
+ **Correct field names** (discovered via 422 validation errors):
572
+ - `catalogName` (NOT `name`)
573
+ - `items` array with objects: `{ itemResourceId, itemName, price }` (NOT `itemResourceIds` flat array)
574
+ **Workaround**: Create catalogs through the Jaz UI.
575
+
576
+ ---
577
+
578
+ ## Deposits Errors
579
+
580
+ ### 404 — Endpoint does not exist
581
+ **Cause**: `POST /deposits` returns 404. Also tested: `/customer-deposits`, `/supplier-deposits`, `/cash-entries`, `/cash-in`, `/cash-out` — all 404.
582
+ **Note**: This endpoint is not implemented in the API. No workaround.
583
+
584
+ ---
585
+
586
+ ## Inventory Adjustments Errors
587
+
588
+ ### 404 — Endpoint does not exist
589
+ **Cause**: `POST /inventory/adjustments` returns 404. Also tested: `/inventory-adjustments`, `/inventory-items/:id/adjustments`, `/items/:id/inventory-adjustments` — all 404.
590
+ **Note**: This endpoint is not implemented in the API. Inventory items can be created via `POST /inventory-items` but stock adjustments cannot be made via API.
591
+
592
+ ---
593
+
594
+ ## Attachments Errors
595
+
596
+ ### "Invalid file type" (400)
597
+ **Cause**: Uploading `text/plain` files to `POST /:type/:id/attachments`.
598
+ **Fix**: API only accepts PDF and image types. Use `application/pdf` or `image/png`. The multipart field name must be `file` (NOT `sourceFile`).
599
+
600
+ ---
601
+
602
+ ## Journal Errors
603
+
604
+ ### "Invalid request body" (400) — currency field
605
+ **Cause**: Including `currency` at the top level of journal POST body.
606
+ **Fix**: Do NOT include `currency` in journal requests. The correct journal body only needs `saveAsDraft`, `reference`, `valueDate`, and `journalEntries`.
607
+ ```json
608
+ // WRONG:
609
+ { "saveAsDraft": false, "reference": "JV-001", "valueDate": "2026-02-08", "currency": "SGD", "journalEntries": [...] }
610
+
611
+ // CORRECT:
612
+ { "saveAsDraft": false, "reference": "JV-001", "valueDate": "2026-02-08", "journalEntries": [...] }
613
+ ```
614
+
615
+ ### Journal entry field format
616
+ **Cause**: Using `debit`/`credit` as separate number fields on journal entries.
617
+ **Fix**: Each entry uses `amount` (number) + `type` (`"DEBIT"` or `"CREDIT"`, UPPERCASE strings).
618
+ ```json
619
+ // WRONG:
620
+ { "accountResourceId": "uuid", "debit": 500, "credit": 0 }
621
+
622
+ // CORRECT:
623
+ { "accountResourceId": "uuid", "amount": 500, "type": "DEBIT" }
624
+ ```
625
+
626
+ ---
627
+
628
+ ## Cash Entry Errors
629
+
630
+ ### Missing saveAsDraft field (422)
631
+ **Cause**: Omitting `saveAsDraft` from `POST /cash-in-journals` or `POST /cash-out-journals`.
632
+ **Fix**: `saveAsDraft` is required on cash journal endpoints. Always include it:
633
+ ```json
634
+ { "saveAsDraft": false, "reference": "CI-001", "valueDate": "2026-02-08",
635
+ "accountResourceId": "uuid-bank", "journalEntries": [...] }
636
+ ```
637
+
638
+ ### Wrong structure — flat fields vs journalEntries
639
+ **Cause**: Using flat structure with `amount`, `bankAccountResourceId`, `description` fields.
640
+ **Fix**: Cash entries use `accountResourceId` at top level (the BANK account) + `journalEntries` array for offset entries.
641
+ ```json
642
+ // WRONG:
643
+ { "saveAsDraft": false, "reference": "CI-001", "valueDate": "2026-02-08",
644
+ "amount": 500, "bankAccountResourceId": "uuid", "accountResourceId": "uuid", "description": "Deposit" }
645
+
646
+ // CORRECT:
647
+ { "saveAsDraft": false, "reference": "CI-001", "valueDate": "2026-02-08",
648
+ "accountResourceId": "uuid-of-bank-account",
649
+ "journalEntries": [{ "accountResourceId": "uuid-of-revenue-account", "amount": 500, "type": "CREDIT" }] }
650
+ ```
651
+
652
+ ---
653
+
654
+ ## Credit Application Errors
655
+
656
+ ### Wrong structure — flat vs credits array
657
+ **Cause**: Sending a flat object `{ creditNoteResourceId, amount }` instead of wrapped in `credits` array.
658
+ **Fix**: Wrap in `credits` array and use `amountApplied` (NOT `amount`).
659
+ ```json
660
+ // WRONG:
661
+ { "creditNoteResourceId": "uuid", "amount": 225.00 }
662
+
663
+ // CORRECT:
664
+ { "credits": [{ "creditNoteResourceId": "uuid", "amountApplied": 225.00 }] }
665
+ ```
666
+
667
+ ---
668
+
669
+ ## Invoice GET Response Quirks
670
+
671
+ ### Line item account field asymmetry
672
+ **Symptom**: Code expecting `accountResourceId` on line items from GET response fails.
673
+ **Cause**: GET responses use `organizationAccountResourceId` for line item accounts, but POST uses `accountResourceId`.
674
+ **Fix**: When reading invoice data back, use `organizationAccountResourceId` to access the account. When creating, use `accountResourceId`.
675
+
676
+ ---
677
+
678
+ ## Withholding Tax Errors
679
+
680
+ ### WITHHOLDING_CODE_NOT_FOUND (422)
681
+
682
+ **Request**: POST /api/v1/bills with `withholdingTax` on line items
683
+ **Error**: `"WITHHOLDING_CODE_NOT_FOUND"` or similar withholding-related error
684
+ **Cause**: The organization does not have withholding tax enabled, or the code is invalid for this region.
685
+ **Fix**: Remove the `withholdingTax` field from ALL line items and retry. This is a common pattern for cross-region compatibility — Singapore orgs typically don't use withholding tax, but Philippines orgs do.
686
+
687
+ **Retry pattern** (from production):
688
+ 1. Submit bill/credit note with `withholdingTax` fields
689
+ 2. If error contains "WITHHOLDING" (case-insensitive), strip `withholdingTax` from all line items
690
+ 3. Retry the same request without withholding tax
691
+ 4. Log which org doesn't support withholding tax to avoid future retries
692
+
693
+ ---
694
+
695
+ ## Pagination Errors
696
+
697
+ ### `page`/`size` silently ignored (GOTCHA)
698
+ **Cause**: Sending `?page=0&size=100` or `?page=1&size=50` to any GET list endpoint.
699
+ **Behavior**: These params are silently ignored — API returns default results (limit=100, offset=0). No error is returned, making this bug hard to detect.
700
+ **Fix**: Use `?limit=100&offset=0` instead. Never use `page` or `size`.
701
+
702
+ ### "limit must be 1 or greater" (422)
703
+ **Cause**: Sending `?limit=0` on a GET endpoint.
704
+ **Fix**: Minimum limit is 1.
705
+
706
+ ### "limit must be 1000 or less" (422)
707
+ **Cause**: Sending `?limit=1001` or higher on a GET endpoint.
708
+ **Fix**: Maximum limit is 1000. To fetch all records, paginate with `limit=1000&offset=0`, then `limit=1000&offset=1000`, etc.
709
+
710
+ ### "offset must be 65536 or less" (422)
711
+ **Cause**: Offset exceeds maximum.
712
+ **Fix**: Maximum offset is 65536. For datasets larger than ~65K records, use POST /search with filters to narrow results.
713
+
714
+ ---
715
+
716
+ ## Search Errors
717
+
718
+ ### "sort is required if [offset] is present" (422)
719
+ **Cause**: Using `offset` in search without providing `sort`.
720
+ **Fix**: When paginating with `offset`, MUST include `sort` as an object:
721
+ ```json
722
+ // WRONG — missing sort or using top-level sortBy:
723
+ { "limit": 10, "offset": 0, "sortBy": ["valueDate"], "order": "DESC" }
724
+
725
+ // CORRECT — sort is an object with sortBy array:
726
+ { "limit": 10, "offset": 0, "sort": { "sortBy": ["valueDate"], "order": "DESC" } }
727
+ ```
728
+
729
+ ### "Invalid request body" (400) — wrong sort format
730
+ **Cause**: `sort` is a string instead of an object, or `sortBy` is a string instead of array.
731
+ **Fix**: `sort` must be `{ sortBy: ["field1"], order: "ASC"|"DESC" }`. `sortBy` must be an array.
732
+
733
+ ---
734
+
735
+ ## General / Transient Errors
736
+
737
+ ### 500 Internal Server Error (intermittent)
738
+ **Cause**: Server-side transient issue.
739
+ **Fix**: Retry once with 1-2 second delay. If persists, skip and log.
740
+
741
+ ### 429 Too Many Requests (rare)
742
+ **Cause**: Too many concurrent requests.
743
+ **Fix**: Limit to 5 concurrent requests. Use exponential backoff: 1s, 2s, 4s.
744
+
745
+ ### "resourceId must be a valid version 4 UUID" (422)
746
+ **Cause**: Passing a non-UUID string where a resource ID is expected.
747
+ **Fix**: Ensure all resource IDs are valid UUIDs (format: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`).
748
+
749
+ ---
750
+
751
+ *Last updated: 2026-02-10 — Currency rates: Fixed "404 on rate endpoints" (wrong path — use hyphenated `/organization-currencies`). Added rate validation errors. Fixed FX: `currencyCode` string is silently ignored, documented `currency` object form and rate sources. Verified via live API testing.*