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.
You are working with the Jaz REST API — the accounting platform backend. Also fully compatible with Juan Accounting (same API, same endpoints).
Before touching this skill's HTTP details, check what's actually available:
execute_tool with create_invoice, list_bills, etc.). Do not write direct HTTP. The MCP server handles auth, retries, and field shape for you.jaz-clio CLI: use the CLI commands (clio invoices list --json, etc.). Same code path, structured output.The rest of this skill — field names, gotchas, error catalog, dependency order, search filter syntax — applies regardless of invocation path. Read it for context, not for HTTP-call construction unless you're in the third bucket.
Core fundamentals (read first, every integration): Identifiers & Dates 1–3; Names & Fields 9–13; Transaction Creation 14–16; Chart of Accounts 17–22; Payments / Cross-Currency 4–8; Journals & Cash 23–26; Credit Notes & Refunds 27–28; Reports 36–37; Tax Profile Scoping 100; Transaction References 104; Draft Finalization Pipeline 81–88; Jaz Magic / PDF-JPG 57–63; Currency Rates 39, 49, 105; Withholding Tax 45, 98.
Integration depth (API clients, pipelines, batch jobs, MCP/CLI): Bulk Upsert (Items/Contacts/Rates); Background Jobs (filter resourceId not jobId); Export Records; Pagination (38); Search & Filter (50–56); Response Shape Gotchas (66–73); Cash Entry Response Shape (74–77); Entity Resolution (78–80); Bank Rules (89–90d); Fixed Assets (91–92c); Subscriptions & Scheduled (93–94); niche endpoints (95–102); Journals balance (103); Quick Fix (107, 111); TTB (108); Dynamic Strings (109–110); Sub-Resource Shapes (112); Nano-Classifier (113); Scheduler Asymmetry (114); Payment Record CRUD (115–117); Bulk Upserts transactions (118–122); Reconciliation write-side (123–127); Drafts lifecycle (128–135); Orders — Sale Quotes / Sale Orders / Purchase Requests / Purchase Orders (references/orders.md); Claims — records / lifecycle / bulk + conversion + payouts + Employees + Claim Settings (references/claims.md).
Base URL: https://api.getjaz.com
Auth: x-jk-api-key: <key> header on every request — key has jk- prefix (e.g., jk-a1b2c3...). NOT Authorization: Bearer or x-api-key.
Content-Type: application/json for all POST/PUT/PATCH (except multipart endpoints: createBusinessTransactionFromAttachment FILE mode, importBankStatementFromAttachment, and attachment uploads)
All paths are prefixed: /api/v1/ (e.g., https://api.getjaz.com/api/v1/invoices)
resourceId — never id. References use <resource>ResourceId suffix.valueDate — not issueDate, invoiceDate, date. This is an accounting term meaning "date of economic effect."YYYY-MM-DD strings — ISO datetime and epoch ms are rejected.paymentAmount = bank account currency (actual cash moved), transactionAmount = transaction document currency (invoice/bill/credit note — amount applied to balance). For same-currency, both are equal. For FX (e.g., USD invoice paid from SGD bank at 1.35): paymentAmount: 1350 (SGD), transactionAmount: 1000 (USD).valueDate — not paymentDate, not date.accountResourceId — not bankAccountResourceId.paymentAmount, transactionAmount, accountResourceId, paymentMethod, reference, valueDate.{ payments: [...] } — array recommended. Flat objects are now auto-wrapped by the API, but array format is preferred for clarity.name — not description.internalName, but name alias is accepted on POST. GET responses return both internalName and name.tagName, but name alias is accepted on POST. GET responses return both tagName and name.name, GET returns both customFieldName and name.reference — not referenceNumber.saveAsDraft has OPPOSITE defaults on the two surfaces. Raw REST: omitting it creates a FINALIZED transaction (false). MCP/CLI tools: creates for invoices, bills, both credit notes, journals, sale quotes, purchase requests/orders, and the order-conversions inject true — omitting it creates a DRAFT; pass saveAsDraft: false to post immediately. When the landing state matters, pass the field explicitly and read status off the create response rather than assuming either default. Cash entries and cash transfers are the exception on both surfaces: they have no draft state, so saveAsDraft: true is refused with a 422 and the tools do not offer the field at all.saveAsDraft: false (or omitted on the raw REST surface — see Rule 14), every lineItem MUST have accountResourceId.+65XXXXXXXX (SG), +63XXXXXXXXXX (PH). No spaces.accountType: "Bank Accounts". A convenience endpoint GET /bank-accounts exists but returns a flat array [{...}] — NOT the standard paginated { data, totalElements, totalPages } shape. Normalize before use.accounts — not chartOfAccounts.currency — not currencyCode. (Asymmetry — GET returns currencyCode.)classificationType — GET returns accountType. Same values. Both accept all 23 types: Bank Accounts, Cash, Current Asset, Non-current Asset, Fixed Asset, Inventory, Investment, Goodwill, Current Liability, Non-current Liability, Shareholders Equity, Operating Revenue, Other Revenue, Discontinued Income, Financing Income, Investing Income, Direct Costs, Operating Expense, Other Expense, Finance Cost, Investing Expense, Income Tax Expense, Discontinued Expense (nine of these are the IFRS 18 set added 2026-05, listed in rule 140). COGS is Direct Costs, never Operating Expense; "Cost of Goods Sold" is the default account NAME, not a type. Values are singular — a plural or invented value returns zero rows with no error, and search_accounts does not normalise (only create_account does). The set is GLOBAL (same for every org); confirm against the platform with list_account_classifications using limit: 100 — its default page is 20 and there are 23.POST /items/bulk-upsert) — max 500 per call. Provide resourceId per item to update (partial — only changed fields needed, server preserves existing values). Omit resourceId to create (defaults: status=ACTIVE, itemCategory=NON_INVENTORY). Response: { resourceId: null, resourceIds: [...] }. SYNC — returns resourceIds immediately.POST /contacts/bulk-upsert) — max 500 per call. Provide resourceId to update (partial), omit to create. billingName required for create. ASYNC — returns { jobId, status: "QUEUED", totalRecords }. Poll search_background_jobs with filter: {resourceId:{eq:jobId}} until status is SUCCESS, FAILED, or PARTIAL_SUCCESS. Unlike items, contacts bulk-upsert is asynchronous.POST /organization/currencies/rates/bulk-upsert) — max 500 per call. Requires rateDirection per rate (FUNCTIONAL_TO_SOURCE or SOURCE_TO_FUNCTIONAL). Auto-enables currencies not yet enabled in the org — no need to call add_currency first. Response: { resourceId: null, resourceIds: [...] }.jobId can be polled via search_background_jobs. This includes: contacts bulk-upsert (UPSERT_CONTACTS), items bulk-upsert (UPSERT_ITEMS), bank statement import (PROCESS_BANK_STATEMENT_FILES), and magic processing (MAGIC_TRANSACTION_*).resourceId, NOT jobId — filter: {jobId:{eq:...}} is silently ignored (returns ALL jobs). Must use filter: {resourceId:{eq:theJobId}}. The response field is named jobId but the filter path is resourceId.SUCCESS, FAILED, or PARTIAL_SUCCESS. Use processedCount, failedCount, totalRecords for progress. PARTIAL_SUCCESS means some records succeeded and some failed — check errorDetails array for per-record errors.startedAt filter does NOT work — use createdAt for date range filtering.errorDetails is [] (empty array) on success — not null.UPSERT_CONTACTS, UPSERT_ITEMS, PROCESS_BANK_STATEMENT_FILES, MAGIC_TRANSACTION_PURCHASE, MAGIC_TRANSACTION_SALE, MAGIC_TRANSACTION_SALE_CREDIT_NOTE.outputFormat: "XLSX" is always required — no other format is currently supported. Hardcode it.query + filter are mutually exclusive — the server returns INVALID_SEARCH_INPUT if both are provided. Pass query (structured search string, same syntax as dashboard) OR filter (raw JSON filter object), never both.INVOICE, BILL, CUSTOMER_CREDIT_NOTE, SUPPLIER_CREDIT_NOTE, SALE_PAYMENT, PURCHASE_PAYMENT, BATCH_PAYMENT, CONTACT, ITEM, CAPSULE, SCHEDULED_TRANSACTION, JOURNAL, BANK_RECORD, CASHFLOW_TRANSACTION, FIXED_ASSET, CHART_OF_ACCOUNT, TAX_PROFILE.fileUrl expires in ~5 minutes — it's a pre-signed S3 URL. Warn the user to download immediately.preview_export_records to confirm scope (count + sample rows) before calling export_records. The filterDescription field gives a human-readable summary like "2580 records | Status in: UNPAID".get_export_columns to discover available column paths and headers. Pass a columns array to select specific fields. Omit for default columns.previewRows keys are column headers — not field paths. E.g. {"Invoice Ref #": "INV-001", "Customer": "Acme"}. Use resolvedColumns to map headers back to paths.journalEntries with amount + type: "DEBIT"|"CREDIT" — NOT debit/credit number fields.currency object — same format as invoices/bills: "currency": { "sourceCurrency": "USD" } (auto-fetch platform rate) or "currency": { "sourceCurrency": "USD", "exchangeRate": 0.74 } (custom rate). Must be enabled for the org. Omit for base currency. Direction: exchangeRate is functionalToSource (1 org-base unit = N sourceCurrency) — usually the inverse of a quoted rate. Pass your figure as-is with rateDirection rather than inverting by hand; Rule 49. Three restrictions apply to foreign currency journals: (a) no controlled accounts — accounts with controlFlag (AR, AP) are off-limits (use invoices/bills instead), (b) no FX accounts — FX Unrealized Gain/Loss/Rounding are system-managed, (c) bank accounts must match — can only post to bank accounts in the same currency as the journal (e.g., USD journal → USD bank account only, not SGD bank account). All other non-controlled accounts (expenses, revenue, assets, liabilities) are available.currency object is the SAME everywhere — invoices, bills, credit notes, AND journals all use currency: { sourceCurrency: "USD", exchangeRate?: number, rateDirection?: "FUNCTIONAL_TO_SOURCE" | "SOURCE_TO_FUNCTIONAL" }. Direction: exchangeRate is functionalToSource (1 org-base unit = N sourceCurrency) — usually the inverse of a quoted rate. Pass your figure as-is with rateDirection rather than inverting by hand; Rule 49. Never use currencyCode: "USD" (silently ignored on invoices/bills) or currency: "USD" (string — causes 400 on invoices/bills).accountResourceId at top level for the BANK account + lines array for offsets.credits array with amountApplied — not flat.paymentAmount, transactionAmount, accountResourceId, paymentMethod, valueDate, reference. The API also accepts aliases refundAmount/refundMethod (see Rule 53) but prefer canonical paymentAmount/paymentMethod for consistency.POST /inventory-items, verified live 2026-09-02): required are itemCode, name, unit (e.g. "pcs" — a blank unit returns ITEM_UNIT_EMPTY_ERROR), costingMethod ("FIXED" or "WAC"), cogsResourceId and blockInsufficientDeductions (send false explicitly — omitting it 422s, it is not defaulted server-side). Send name: the endpoint declares no name property and marks internalName required, but the API populates internalName from the name you send. The two account links are TYPE-constrained and the errors name the type: cogsResourceId must be Direct Costs (INVALID_ACCOUNT_TYPE_DIRECT_COST), purchaseAccountResourceId must be Inventory (INVALID_ACCOUNT_TYPE_INVENTORY) — an inventory purchase debits the asset, and COGS is recognised on sale. The API reports purchaseAccountResourceId, saleAccountResourceId, appliesToSale and appliesToPurchase as "required if [cogsResourceId] is present" — but cogsResourceId is itself always required, so all four are unconditional too. appliesToSale and appliesToPurchase must both be true, not merely present: false returns APPLIES_TO_SALE_ERROR / APPLIES_TO_PURCHASE_ERROR ("must be true when cogs selected"), so an inventory-tracked item with COGS is necessarily both sale- and purchase-applicable. There is no inventoryAccountResourceId — it appears in no request schema and a create succeeds without it.DELETE /items/:id — not /inventory-items/:id.cashOut/cashIn sub-objects — NOT flat fromAccountResourceId/toAccountResourceId. Each: { accountResourceId, amount }.{ invoice: {...} } or { bill: {...} } — not flat. Recurrence field is repeat (NOT frequency/interval). saveAsDraft: false required. reference is required inside the invoice/bill wrapper — omitting it causes 422.schedulerEntries — not nested in journal wrapper. valueDate is required at the top level (alongside startDate, repeat, etc.).items array wrapper with name, value, categoryCode, datatypeCode.appliesTo WORKS and type does nothing (re-probed live 2026-09-02, reversing the previous entry). Send appliesTo as an OBJECT — {invoices, bills, customerCredits, supplierCredits, payments} sets the matching applyTo* to SHOW; omit it and the field appears on nothing. The old "do not send appliesTo" note came from an ARRAY example, which does 400. format is the ONLY control over the kind of field and the datatype is derived, not chosen: CUSTOM (default) yields datatypeCode: TEXT, any ALL_* value (ALL_CUSTOMERS/ALL_SUPPLIERS/ALL_CONTACTS/ALL_EMPLOYEES/ALL_USERS) yields LIST, a picklist of that population. There is no NUMBER, DATE or DROPDOWN custom field: type, fieldType, entityType, datatypeCode and options all return 200 and are silently dropped (an int on datatypeCode also returns 200, so the DTO does not declare it). printOnDocuments is required and not defaulted server-side.
35a. Custom field values on transactions: Set via customFields: [{ customFieldName: "PO Number", actualValue: "PO-123" }] on invoice/bill/customer-CN/supplier-CN/payment/item/fixed-asset create/update. NOT on journals, cash entries, or cash transfers. Read from GET responses in the same shape.
35b. Custom field search: POST /custom-fields/search with filter/sort/limit/offset. Filter by customFieldName (StringExpression), datatypeCode (StringExpression: TEXT, LIST, DATE — there is no DROPDOWN; see rule 35).
35c. Custom field GET: GET /custom-fields/:resourceId returns full definition including applyToSales, applyToPurchase, applyToCreditNote, applyToPayment, printOnDocuments, listOptions.35d. Tags are tags: string[] on ALL transaction create/update: invoices, bills, customer CNs, supplier CNs, journals, cash-in, cash-out, cash transfers. CLI uses --tag <name> (singular, wrapped to array). API accepts the array directly.
35e. ClassifierConfig on line items: classifierConfig: [{ resourceId: "<capsuleTypeId>", type: "invoice"|"bill", selectedClasses: [{ className: "Class A", resourceId: "<classId>" }], printable: true }]. Applies to line items on invoices, bills, credit notes, journal entries, and cash entry details. Create capsule types first via POST /capsule-types, then reference them in classifierConfig.
| Report | Required Fields |
|--------|----------------|
| Trial balance | startDate, endDate |
| Balance sheet | primarySnapshotDate |
| P&L | primarySnapshotDate, secondarySnapshotDate |
| General ledger | startDate, endDate, groupBy: "ACCOUNT" (also CONTACT, TRANSACTION, RELATIONSHIP) |
| Cashflow | primaryStartDate, primaryEndDate |
| Cash balance | reportDate |
| AR/AP report | endDate |
| AR/AP summary | startDate, endDate |
| Bank balance summary | primarySnapshotDate |
| Equity movement | primarySnapshotStartDate, primarySnapshotEndDate |
| Ledger highlights | (none — simple GET) |
GET /api/v1/ledger/highlights returns org-wide GL summary metadata: transaction counts by type, date range, active accounts/currencies, cross-currency flag, and dynamic FX types. No parameters. Response dates are epoch ms (see Rule 52).
37a. Data exports use simpler field names: P&L export uses startDate/endDate (NOT primarySnapshotDate). AR/AP export uses endDate.limit/offset pagination — NOT page/size. offset is a 0-indexed PAGE NUMBER, not a row-skip (offset=1 = second page of limit rows). Default limit=100, offset=0. Max limit=1000, max offset=65536. page/size params are silently ignored. Response shape: { totalPages, totalElements, truncated, data: [...] }. When truncated: true, a _meta: { fetchedRows, maxRows } field explains why (offset cap or --max-rows soft cap — default 10,000). Use --max-rows <n> to override. Always check truncated before assuming the full dataset was returned. Payload tier (view) — page-then-drill: search_* and the lean list_* tools (invoices, bills, contacts, items, journals, customer/supplier credit notes, sale/purchase orders) return a compact summary row by default (view:"lean" — id + reference/status/date/contact/amount). Search lean to FIND a record, then read it in full via its get_*; pass view:"full" only when you need whole rows up front (heavier — avoid for broad searches). Other collections always return full. CLI defaults to full; use --view lean./organization/currencies/:code/rates — enable currencies first via POST /organization/currencies, then set rates via POST /organization/currencies/:code/rates with body { "rate": 0.74, "rateApplicableFrom": "YYYY-MM-DD" } (see Rule 49 for direction). The older hyphenated /organization-currencies/... rate paths still resolve but are superseded: treat the nested form as the only supported path. The reference is migrating away from the hyphenated form, so do not rely on it being documented. Cannot set rates for org base currency. Full CRUD: POST (create), GET (list), GET/:id, PUT/:id, DELETE/:id.currency object — currencyCode: "USD" (string) is silently ignored (transaction created in base currency!). Use currency: { sourceCurrency: "USD" } to auto-fetch platform rate (ECB/FRANKFURTER), or currency: { sourceCurrency: "USD", exchangeRate: 0.74 } for a custom rate. Rate hierarchy: org rate → platform/ECB → transaction-level. Direction: exchangeRate is functionalToSource (1 org-base unit = N sourceCurrency) — usually the inverse of a quoted rate. Pass your figure as-is with rateDirection rather than inverting by hand; Rule 49.organizationAccountResourceId for line item accounts — POST uses accountResourceId. Request-side aliases resolve issueDate → valueDate, bankAccountResourceId → accountResourceId, etc.interval — POST uses repeat. (Response-side asymmetry remains.){ sort: { sortBy: ["valueDate"], order: "DESC" } }. Required when offset is present (even offset: 0).POST /magic/importBankStatementFromAttachment or JSON via POST /bank-records/:accountResourceId with { records: [{amount, transactionDate, description?, payerOrPayee?, reference?}] } (positive = cash-in, negative = cash-out, response: {data: {errors: []}}). Search: POST /bank-records/:accountResourceId/search — filter fields: valueDate (DateExpression), status (StringExpression: UNRECONCILED, RECONCILED, ARCHIVED, POSSIBLE_DUPLICATE), description, extContactName (payer/payee), extReference, netAmount (BigDecimalExpression), extAccountNumber. Sort by valueDate DESC default.WITHHOLDING_CODE_NOT_FOUND, strip field and retry./inventory-balances/:status, missing c.Bind). Contact groups PUT and custom fields PUT are NO LONGER on this list — both were re-probed live 2026-09Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer