AgentSkillsCN

api

Jaz/Juan REST API的完整参考——会计平台后端。在构建、修改、调试或扩展任何调用该API的代码时,包括API客户端、集成开发、数据种子导入、测试数据,或新接口开发时,均可使用此技能。其中收录了所有字段名称、响应结构、常见错误、易错场景,以及通过实际生产环境测试所发现的各类边缘案例。

SKILL.md
--- frontmatter
name: api
version: 3.4.1
description: Complete reference for the Jaz/Juan 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.
license: MIT
compatibility: Requires Jaz/Juan API key (x-jk-api-key header). Works with Claude Code, Claude Cowork, Claude.ai, and any agent that reads markdown.

Jaz API Skill

You are working with the Jaz/Juan REST API — the backend for Jaz (Singapore) and Juan (Philippines) accounting platforms.

When to Use This Skill

  • Writing or modifying any code that calls the Jaz API
  • Building API clients, integrations, or data pipelines
  • Debugging API errors (422, 400, 404, 500)
  • Adding support for new Jaz API endpoints
  • Reviewing code that constructs Jaz API request payloads

Quick Reference

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)

Critical Rules

Identifiers & Dates

  1. All IDs are resourceId — never id. References use <resource>ResourceId suffix.
  2. All transaction dates are valueDate — not issueDate, invoiceDate, date. This is an accounting term meaning "date of economic effect."
  3. All dates are YYYY-MM-DD strings — ISO datetime and epoch ms are rejected.

Payments (Cross-Currency Aware)

  1. Payment amounts have two fields: 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).
  2. Payment date is valueDate — not paymentDate, not date.
  3. Payment bank account is accountResourceId — not bankAccountResourceId.
  4. Payments require 6 fields: paymentAmount, transactionAmount, accountResourceId, paymentMethod, reference, valueDate.
  5. Payments wrapped in { payments: [...] } — array recommended. Flat objects are now auto-wrapped by the API, but array format is preferred for clarity.

Names & Fields

  1. Line item descriptions use name — not description.
  2. Item names: canonical field is internalName, but name alias is accepted on POST. GET responses return both internalName and name.
  3. Tag names: canonical field is tagName, but name alias is accepted on POST. GET responses return both tagName and name.
  4. Custom field names: POST uses name, GET returns both customFieldName and name.
  5. Invoice/bill number is reference — not referenceNumber.

Transaction Creation

  1. saveAsDraft defaults to false — omitting it creates a finalized transaction. Explicitly sending saveAsDraft: true creates a draft.
  2. If saveAsDraft: false (or omitted), every lineItem MUST have accountResourceId.
  3. Phones MUST be E.164+65XXXXXXXX (SG), +63XXXXXXXXXX (PH). No spaces.

Chart of Accounts

  1. Tax profiles pre-exist — NEVER create them. Only GET and map.
  2. Bank accounts are CoA entries with accountType: "Bank Accounts". No separate endpoint.
  3. CoA bulk-upsert wrapper is accounts — not chartOfAccounts.
  4. CoA POST uses currency — not currencyCode. (Asymmetry — GET returns currencyCode.)
  5. CoA POST uses classificationType — GET returns accountType. Same values.
  6. CoA code mapping: match by NAME, not code — pre-existing accounts may have different codes. Resource IDs are the universal identifier.

Journals & Cash

  1. Journals use journalEntries with amount + type: "DEBIT"|"CREDIT" — NOT debit/credit number fields.
  2. Journals support multi-currency via currency object — same format as invoices/bills: "currency": { "sourceCurrency": "USD" } (auto-fetch platform rate) or "currency": { "sourceCurrency": "USD", "exchangeRate": 1.35 } (custom rate). Must be enabled for the org. Omit for base currency. 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.
  3. currency object is the SAME everywhere — invoices, bills, credit notes, AND journals all use currency: { sourceCurrency: "USD", exchangeRate?: number }. Never use currencyCode: "USD" (silently ignored on invoices/bills) or currency: "USD" (string — causes 400 on invoices/bills).
  4. Cash entries use accountResourceId at top level for the BANK account + journalEntries array for offsets.

Credit Notes & Refunds

  1. Credit note application wraps in credits array with amountApplied — not flat.
  2. CN refunds use refunds wrapper with refundAmount + refundMethod — NOT payments/paymentAmount/paymentMethod.

Inventory Items

  1. Inventory items require: unit (e.g., "pcs"), costingMethod ("FIXED" or "WAC"), cogsResourceId, blockInsufficientDeductions, inventoryAccountResourceId. purchaseAccountResourceId MUST be Inventory-type CoA.
  2. Delete inventory items via DELETE /items/:id — not /inventory-items/:id.

Cash Transfers

  1. Cash transfers use cashOut/cashIn sub-objects — NOT flat fromAccountResourceId/toAccountResourceId. Each: { accountResourceId, amount }.

Schedulers

  1. Scheduled invoices/bills wrap in { invoice: {...} } or { bill: {...} } — not flat. Recurrence field is repeat (NOT frequency/interval). saveAsDraft: false required.
  2. Scheduled journals use FLAT structure with schedulerEntries — not nested in journal wrapper.

Bookmarks

  1. Bookmarks use items array wrapper with name, value, categoryCode, datatypeCode.

Custom Fields

  1. Do NOT send appliesTo on custom field POST — causes "Invalid request body". Only send name, type, printOnDocuments.

Reports

  1. Report field names differ by type — this is the most error-prone area:
ReportRequired Fields
Trial balancestartDate, endDate
Balance sheetprimarySnapshotDate
P&LprimarySnapshotDate, secondarySnapshotDate
General ledgerstartDate, endDate, groupBy: "ACCOUNT"
CashflowprimaryStartDate, primaryEndDate
Cash balancereportDate
AR/AP reportendDate
AR/AP summarystartDate, endDate
Bank balance summaryprimarySnapshotDate
Equity movementprimarySnapshotStartDate, primarySnapshotEndDate
  1. Data exports use simpler field names: P&L export uses startDate/endDate (NOT primarySnapshotDate). AR/AP export uses endDate.

Pagination

  1. All list/search endpoints use limit/offset pagination — NOT page/size. Default limit=100, offset=0. Max limit=1000, max offset=65536. page/size params are silently ignored. Response shape: { totalPages, totalElements, data: [...] }.

Other

  1. Currency rates use /organization-currencies/:code/rates — note the HYPHENATED path (NOT /organization/currencies). 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). Cannot set rates for org base currency. Full CRUD: POST (create), GET (list), GET/:id, PUT/:id, DELETE/:id.
  2. FX invoices/bills MUST use currency objectcurrencyCode: "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: 1.35 } for a custom rate. Rate hierarchy: org rate → platform/ECB → transaction-level.
  3. Invoice GET uses organizationAccountResourceId for line item accounts — POST uses accountResourceId. Request-side aliases resolve issueDatevalueDate, bankAccountResourceIdaccountResourceId, etc.
  4. Scheduler GET returns interval — POST uses repeat. (Response-side asymmetry remains.)
  5. Search sort is an object{ sort: { sortBy: ["valueDate"], order: "DESC" } }. Required when offset is present (even offset: 0).
  6. Bank records: two import methods — Multipart CSV/OFX via POST /magic/importBankStatementFromAttachment (fields: sourceFile, accountResourceId, businessTransactionType: "BANK_STATEMENT", sourceType: "FILE"). JSON via POST /bank-records/:accountResourceId with { records: [{description, netAmount, valueDate, ...}] }.
  7. Withholding tax on bills/supplier CNs only. Retry pattern: if WITHHOLDING_CODE_NOT_FOUND, strip field and retry.
  8. Known API bugs (500s): Contact groups PUT, custom fields PUT, capsules POST, catalogs POST, inventory balances GET — all return 500.
  9. Non-existent endpoints: POST /deposits and POST /inventory/adjustments return 404 — these endpoints are not implemented.
  10. Attachments require PDF/PNG: POST /:type/:id/attachments uses multipart file field but rejects text/plain. Use application/pdf or image/png.
  11. Currency rate direction: rate = functionalToSource (1 base = X foreign) — POST rate: 0.74 for a SGD org means 1 SGD = 0.74 USD. If your data stores rates as "1 USD = 1.35 SGD" (sourceToFunctional), you MUST invert: rate = 1 / 1.35 = 0.74. GET confirms both: rateFunctionalToSource (what you POSTed) and rateSourceToFunctional (the inverse).

Search & Filter

  1. Search endpoint universal pattern — All 28 POST /*/search endpoints share identical structure: { filter?, sort: { sortBy: ["field"], order: "ASC"|"DESC" }, limit: 1-1000, offset: 0-65536 }. Sort is REQUIRED when offset is present (even offset: 0). Default limit: 100. sortBy is always an array on all endpoints (no exceptions). See references/search-reference.md for per-endpoint filter/sort fields.
  2. Filter operator reference — String: eq, neq, contains, in (array, max 100), likeIn (array, max 100), reg (regex array, max 100), isNull (bool). Numeric: eq, gt, gte, lt, lte, in. Date (YYYY-MM-DD): eq, gt, gte, lt, lte, between (exactly 2 values). DateTime (RFC3339): same operators, converted to epoch ms internally. Boolean: eq. JSON: jsonIn, jsonNotIn. Logical: nest with and/or/not objects, or use andGroup/orGroup arrays (invoices, bills, journals, credit notes).
  3. Date format asymmetry (CRITICAL) — Request dates: YYYY-MM-DD strings (all create/update and DateExpression filters). Request datetimes: RFC3339 strings (DateTimeExpression filters for createdAt, updatedAt, approvedAt, submittedAt). ALL response dates: int64 epoch milliseconds — including valueDate, createdAt, updatedAt, approvedAt, submittedAt, matchDate. Convert: new Date(epochMs).toISOString().slice(0,10).
  4. Field aliases on create endpoints — Middleware transparently maps: issueDate/datevalueDate (invoices, bills, credit notes, journals). nametagName (tags) or internalName (items). paymentDatevalueDate, bankAccountResourceIdaccountResourceId (payments). paymentAmountrefundAmount, paymentMethodrefundMethod (credit note refunds). accountTypeclassificationType, currencyCodecurrency (CoA). Canonical names always work; aliases are convenience only.
  5. All search/list responses are flat — every search and list endpoint returns { totalElements, totalPages, data: [...] } directly (no outer data wrapper). Access the array via response.data, pagination via response.totalElements.
  6. Scheduled endpoints support date aliasestxnDateAliases middleware (mapping issueDate/datevalueDate) now applies to all scheduled create/update endpoints: POST/PUT /scheduled/invoices, POST/PUT /scheduled/bills, POST/PUT /scheduled/journals, POST/PUT /scheduled/subscriptions.
  7. Kebab-case URL aliasescapsuleTypes endpoints also accept kebab-case paths: /capsule-types (list, search, CRUD). moveTransactionCapsules also accepts /move-transaction-capsules. Both camelCase and kebab-case work identically.

Jaz Magic — Extraction & Autofill

  1. When the user starts from an attachment, always use Jaz Magic — if the input is a PDF, JPG, or any document image (invoice, bill, receipt), the correct path is POST /magic/createBusinessTransactionFromAttachment. Do NOT manually construct a POST /invoices or POST /bills payload from an attachment — Jaz Magic handles the entire extraction-and-autofill pipeline server-side: OCR, line item detection, contact matching, CoA auto-mapping via ML learning, and draft creation with all fields pre-filled. Only use POST /invoices or POST /bills when building transactions from structured data (JSON, CSV, database rows) where the fields are already known.
  2. Two upload modes with different content typessourceType: "FILE" requires multipart/form-data with sourceFile blob (JSON body fails with 400 "sourceFile is a required field"). sourceType: "URL" accepts application/json with sourceURL string. The OAS only documents URL mode — FILE mode (the common case) is undocumented.
  3. Three required fields: sourceFile (multipart blob — NOT file), businessTransactionType ("INVOICE" or "BILL" only — EXPENSE rejected), sourceType ("FILE" or "URL"). All three are validated server-side.
  4. Response maps transaction types: Request BILL → response businessTransactionType: "PURCHASE". Request INVOICE → response businessTransactionType: "SALE". S3 paths follow: /purchases/ vs /sales/.
  5. Extraction is asynchronous — the API response is immediate (file upload confirmation only). The actual Magic pipeline — OCR, line item extraction, contact matching, CoA learning, and autofill — runs asynchronously. The subscriptionFBPath in the response (e.g., magic_transactions/{orgId}/purchase/{fileId}) is a Firebase Realtime Database path for subscribing to extraction status updates.
  6. Accepts PDF and JPG/JPEG — both file types confirmed working. Handwritten documents are accepted at upload stage (extraction quality varies). fileType in response reflects actual format: "PDF", "JPEG".

Supporting Files

For detailed reference, read these files in this skill directory:

DX Overhaul (Implemented)

The backend DX overhaul is live. Key improvements now available:

  • Request-side field aliases: nametagName/internalName, issueDatevalueDate, bankAccountResourceIdaccountResourceId, and more. Both canonical and alias names are accepted.
  • Response-side aliases: Tags, items, and custom fields return name alongside canonical field names (tagName, internalName, customFieldName).
  • saveAsDraft defaults to false: Omitting it creates a finalized transaction. No longer required on POST.
  • POST /items/search available: Advanced search with filters now works for items.
  • NormalizeToArray: Flat payment/refund/credit objects are auto-wrapped into arrays. Array format is still recommended.
  • Nil-safe deletes: Delete endpoints return 404 (not 500) when resource not found.

Recommended Client Patterns

  • Starting from an attachment? → Use Jaz Magic (POST /magic/createBusinessTransactionFromAttachment). Never manually parse a PDF/JPG to construct POST /invoices or POST /bills — let the extraction & autofill pipeline handle it.
  • Starting from structured data? → Use POST /invoices or POST /bills directly with the known field values.
  • Serialization (Python): model_dump(mode="json", by_alias=True, exclude_unset=True, exclude_none=True)
  • Field names: All request bodies use camelCase
  • Date serialization: Python date type → YYYY-MM-DD strings
  • Bill payments: Embed in bill creation body (safest). Standalone POST /bills/{id}/payments also works.
  • Bank records: Use multipart POST /magic/importBankStatementFromAttachment
  • Scheduled bills: Wrap as { status, startDate, endDate, repeat, bill: {...} }
  • FX currency (invoices, bills, credit notes, AND journals): currency: { sourceCurrency: "USD" } (auto-fetches platform rate) or currency: { sourceCurrency: "USD", exchangeRate: 1.35 } (custom rate). Same object form on all transaction types. Never use currencyCode string — silently ignored.