# Lensing Public API v1 # Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accounting) Accounting covers the general ledger and its setup, financial reports, customer sales and billing, revenue recognition, vendors and purchasing, expenses and payroll, and fixed assets. ## Groups - [Accounting Period Status](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingAccountingPeriodStatuses.html) - [Accounting Periods](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingAccountingPeriods.html) - [Accounts](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingAccounts.html) - [Advance Payment Categories](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingAdvancePaymentCategories.html) - [Amortization Schedule Lines](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingAmortizationScheduleLines.html) - [Asset Management](https://docs.api.lensing.ai/reference/lensing/v1/groups/assetManagement.html) - [Books](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingBooks.html) - [Charts of Accounts](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingChartsOfAccounts.html) - [Credit Memos](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingCreditMemos.html) - [Customers](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingCustomers.html) - [Debit Memos](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingDebitMemos.html) - [Employee Advances](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingEmployeeAdvances.html) - [Expense Categories](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingExpenseCategories.html) - [Expense Reports](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingExpenseReports.html) - [Fiscal Calendars](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingFiscalCalendars.html) - [Fixed Assets](https://docs.api.lensing.ai/reference/lensing/v1/groups/fixedAssets.html) - [Intercompany Bills](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingIntercompanyBills.html) - [Invoice Frequency Templates](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingInvoiceFrequencyTemplates.html) - [Invoice Runs](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingInvoiceRuns.html) - [Invoices](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingInvoices.html) - [Journal Entries](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingJournalEntries.html) - [Outbound Payments](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingOutboundPayments.html) - [Payment Terms](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPaymentTerms.html) - [Payments](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPayments.html) - [Payroll Details](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPayrollDetails.html) - [Payroll Payments](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPayrollPayments.html) - [Payroll Templates](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPayrollTemplates.html) - [Performance Obligations](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPerformanceObligations.html) - [Price Books](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPriceBooks.html) - [Products](https://docs.api.lensing.ai/reference/lensing/v1/groups/accounting_products.html) - [Purchase Orders](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPurchaseOrders.html) - [Purchase Requests](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingPurchaseRequests.html) - [Refunds](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingRefunds.html) - [Reports](https://docs.api.lensing.ai/reference/lensing/v1/groups/accounting_reports.html) - [Revenue Contracts](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingRevenueContracts.html) - [Revenue Scheduling Templates](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingRevenueSchedulingTemplates.html) - [Sales Order Shipments](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingSalesOrderShipments.html) - [Sales Orders](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingSalesOrders.html) - [Sales Returns](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingSalesReturns.html) - [Standalone Payments](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingStandalonePayments.html) - [Subscriptions](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingSubscriptions.html) - [Tax Configurations](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingTaxConfigurations.html) - [Tax Documents](https://docs.api.lensing.ai/reference/lensing/v1/groups/taxDocuments.html) - [Usage](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingUsages.html) - [Vendor Bills](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingVendorBills.html) - [Vendor Credits](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingVendorCredits.html) - [Vendors](https://docs.api.lensing.ai/reference/lensing/v1/groups/accountingVendors.html) --- # Accounting Period Status Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingAccountingPeriodStatuses) An accounting period status is the open or closed state of one accounting period for one legal entity and accounting book. The same period can be open for one legal entity and closed for another, so each legal entity and accounting book has its own status for it. postable says whether journal entries can be posted to the general ledger in the period. It is true when the status is open or reopened. It doesn't reflect locks that apply only to some types of journal entry. ## Endpoints - [List accounting period statuses](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/accountingPeriodStatuses.html): `GET /accounting/period-status` - [Retrieve an accounting period status](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getAccountingPeriodStatus.html): `GET /accounting/period-status/{id}` - [Run a period-close task](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/executeAccountingPeriodTask.html): `POST /accounting/period-status/{id}/execute-task` --- # List accounting period statuses Operation ID: `accountingPeriodStatuses` Group: Accounting › Accounting Period Status [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/accountingPeriodStatuses) `GET /accounting/period-status` Read-only: Reads and returns; changes nothing. Lists accounting period statuses. Each item is one period for one legal entity and one accounting book. To read an item with its period-close checklist, use GET accounting/period-status/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountingPeriodId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `bookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `periodName` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fiscalYear` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fiscalQuarter` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `startDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `endDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `reopenedByCascade` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `postable` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, periodName, fiscalYear, fiscalQuarter, startDate, endDate, status, postable, lastModifiedDate - Selectable: id, accountingPeriodId, entityId, entityName, bookId, bookName, periodName, fiscalYear, fiscalQuarter, startDate, endDate, status, reopeningReason, reopenedByCascade, postable, postableReason, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of accountingPeriodStatusesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for this entity-book period status row. This identifies the (entity, book, period) combination, not the shared accounting period. | | `accountingPeriodId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the underlying accounting period, shared by every entity that uses this fiscal calendar. See id on GET accounting/accounting-periods/:id. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the legal entity this status belongs to. See id on GET base/entities. Absent only when the entity-book link is broken. | | `entityName` | string \| null | no | no | no | The name of the legal entity this status belongs to. | | `bookId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the accounting book this status belongs to. A period is opened and closed per book. See id on GET accounting/books/:id. | | `bookName` | string \| null | no | no | no | The name of the accounting book this status belongs to. | | `periodName` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The display name of the period as its fiscal calendar defines it, for example 'Jan 2026'. The format varies by calendar, so do not parse it. Match on dates instead. | | `fiscalYear` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The fiscal year label this period belongs to, for example 'FY2024'. Set by the calendar, so it need not match the calendar year of startDate. | | `fiscalQuarter` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The fiscal quarter label this period belongs to, for example 'Q1 2026'. | | `startDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The first day of the period, inclusive. Format YYYY-MM-DD. Absent only when the underlying period was deleted. | | `endDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The last day of the period, inclusive. Format YYYY-MM-DD. | | `status` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The status this entity and book have put the period in. Possible values: 'created' (generated, not yet opened), 'open', 'closed', 'reopened'. Only 'open' and 'reopened' accept postings. | | `reopeningReason` | string \| null | no | no | no | The free-text reason recorded when the period was reopened. Set only when status is 'reopened'. | | `reopenedByCascade` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this period was reopened as a side effect of reopening a later one, rather than directly. | | `postable` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the period currently accepts postings for this entity and book, true when its status is 'open' or 'reopened'. This does not account for journal-type-specific locks. | | `postableReason` | string \| null | no | no | no | Why the period does not accept postings, for example 'status is closed'. Empty when it is postable. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time this status row last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. It changes when the period is opened, closed or reopened for this entity and book. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountingPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "periodName": "", "fiscalYear": "", "fiscalQuarter": "", "startDate": "2026-01-15", "endDate": "2026-01-15", "status": "", "reopeningReason": "", "reopenedByCascade": true, "postable": true, "postableReason": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an accounting period status Operation ID: `getAccountingPeriodStatus` Group: Accounting › Accounting Period Status [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getAccountingPeriodStatus) `GET /accounting/period-status/{id}` Read-only: Reads and returns; changes nothing. Retrieves an accounting period status. The status is for one period, one legal entity and one accounting book. The response includes whether the period accepts new journal entries for that entity and book (postable), and the period-close checklist (closeTasks). ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the entity-book period status record you want to retrieve. This is the id of a record from GET accounting/period-status, not the shared accountingPeriodId. | ## Response `200` Success Under `data` (getAccountingPeriodStatusOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for this entity-book period status row. This identifies the (entity, book, period) combination, not the shared accounting period. | | `accountingPeriodId` | string | no | The identifier of the underlying accounting period, shared by every entity that uses this fiscal calendar. See id on GET accounting/accounting-periods/:id. | | `entityId` | string | no | The identifier of the legal entity this status belongs to. See id on GET base/entities. Absent only when the entity-book link is broken. | | `entityName` | string | no | The name of the legal entity this status belongs to. | | `bookId` | string | no | The identifier of the accounting book this status belongs to. A period is opened and closed per book. See id on GET accounting/books/:id. | | `bookName` | string | no | The name of the accounting book this status belongs to. | | `periodName` | string | no | The display name of the period as its fiscal calendar defines it, for example 'Jan 2026'. The format varies by calendar, so do not parse it. Match on dates instead. | | `fiscalYear` | string | no | The fiscal year label this period belongs to, for example 'FY2024'. Set by the calendar, so it need not match the calendar year of startDate. | | `fiscalQuarter` | string | no | The fiscal quarter label this period belongs to, for example 'Q1 2026'. | | `startDate` | date | no | The first day of the period, inclusive. Format YYYY-MM-DD. Absent only when the underlying period was deleted. | | `endDate` | date | no | The last day of the period, inclusive. Format YYYY-MM-DD. | | `status` | string | yes | The status this entity and book have put the period in. Possible values: 'created' (generated, not yet opened), 'open', 'closed', 'reopened'. Only 'open' and 'reopened' accept postings. | | `postable` | boolean | yes | Whether the period currently accepts postings for this entity and book, true when its status is 'open' or 'reopened'. This does not account for journal-type-specific locks. | | `postableReason` | string | yes | Why the period does not accept postings, for example 'status is closed'. Empty when it is postable. | | `reopeningReason` | string | no | The free-text reason recorded when the period was reopened. Set only when status is 'reopened'. | | `reopenedByCascade` | boolean | no | Whether this period was reopened as a side effect of reopening a later one, rather than directly. | | `lastModifiedDate` | date-time | yes | The date and time this status row last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. It changes when the period is opened, closed or reopened for this entity and book. | | `closeTasks` | array | no | The period-close checklist for this entity and book, one entry per task. An empty array when the row has no tasks yet. Check isExecutable before running a task with POST accounting/period-status/:id/execute-task. | | `closeTasks[].taskCode` | string | yes | The code identifying the task, and the value to pass as task to POST accounting/period-status/:id/execute-task. The forward tasks, for example 'open', 'close', 'reopen', 'lockAP', 'lockAR', 'lockRevenue', 'lockJournals', 'runRevaluation', 'calculateConsolidationRates'. The task that undoes one is in reverseTaskCode. | | `closeTasks[].reverseTaskCode` | string | no | The code of the task that undoes this one, for example 'unlockAP' for 'lockAP'. Empty when the task cannot be reversed. | | `closeTasks[].isExecuted` | boolean | yes | Whether this task has already run for this entity-book period. | | `closeTasks[].isExecutable` | boolean | yes | Whether this task can run now. It is false for a task already done and for one whose preconditions are unmet. notExecutableReason says which. | | `closeTasks[].notExecutableReason` | string | no | Why the task cannot run now. Present only when isExecutable is false and a reason is available. | | `closeTasks[].isReversible` | boolean | yes | Whether the task that undoes this one can run now. | | `closeTasks[].notReversibleReason` | string | no | Why the reversal cannot run now. Present only when isReversible is false and a reason is available. | | `closeTasks[].notApplicable` | boolean | no | Whether this task does not apply to this entity-book period at all, for example a consolidation task on an entity that does not consolidate. Such a task is neither executed nor executable and does not block the close. | | `closeTasks[].message` | string | no | A human-readable, translated description of the task's state: the applied message when it has run, the not-applied message when it has not, or the not-applicable message when it does not apply. | | `closeTasks[].lastModifiedDate` | date-time | no | The date and time this task's state last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Absent on a task that has never run. | | `closeTasks[].lastModifiedBy` | string | no | The identifier of the user who last changed this task's state. Absent on a task that has never run. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountingPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "periodName": "", "fiscalYear": "", "fiscalQuarter": "", "startDate": "2026-01-15", "endDate": "2026-01-15", "status": "", "postable": true, "postableReason": "", "reopeningReason": "", "reopenedByCascade": true, "lastModifiedDate": "2026-01-15T09:30:00.000Z", "closeTasks": [ { "taskCode": "", "reverseTaskCode": "", "isExecuted": true, "isExecutable": true, "notExecutableReason": "", "isReversible": true, "notReversibleReason": "", "notApplicable": true, "message": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lastModifiedBy": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Run a period-close task Operation ID: `executeAccountingPeriodTask` Group: Accounting › Accounting Period Status [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/executeAccountingPeriodTask) `POST /accounting/period-status/{id}/execute-task` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Runs a period-close task. A task runs on one accounting period status. For example, it opens, closes, or reopens the period, or locks or unlocks accounts payable (AP), accounts receivable (AR), journals, or revenue. Some tasks also change related periods. 'open' also opens earlier periods of the same legal entity and accounting book that aren't open yet. 'reopen' and the unlock tasks also reopen later closed periods, and the parent legal entity's period if it is closed. 'lockAR', 'unlockAR', 'lockAP', and 'unlockAP' also apply to the legal entity's other accounting books. Before you send the request, read closeTasks on GET accounting/period-status/:id. For a task in the list, check isExecutable. For 'reopen' or an unlock task, check isReversible on the task it undoes. The response is empty. Read the result with GET accounting/period-status/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier for the entity-book period status row to run the task against. This is the id of a record from GET accounting/period-status, not the shared accountingPeriodId. | ## Request body Required, `application/json`: executeAccountingPeriodTaskInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `task` | string | yes | The close task to run. Possible values: 'open', 'close', 'reopen', 'lockAR', 'unlockAR', 'lockAP', 'unlockAP', 'lockJournals', 'unlockJournals', 'lockRevenue', 'unlockRevenue', 'runRevaluation', 'calculateConsolidationRates'. These are the taskCode values on closeTasks. An unrecognized code fails with UNKNOWN_TASK. A recognized code that cannot run now fails with TASK_NOT_EXECUTABLE. | | `reopeningReason` | string | no | A free-text reason recorded against the period. Used only by the 'reopen' task, which stores it as the period's reopeningReason and shows it on both read routes. Other tasks ignore it. Supply it when reopening a closed period, since it is the only record of why the close was undone. | ```json { "task": "", "reopeningReason": "" } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `UNKNOWN_TASK` | 400 | | `TASK_NOT_EXECUTABLE` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, UNKNOWN_TASK, TASK_NOT_EXECUTABLE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Accounting Periods Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingAccountingPeriods) An accounting period is one period of a fiscal calendar for one legal entity and accounting book, with its own status. The same period can be open for one legal entity and closed for another, because each legal entity and accounting book has its own status for it. postable says whether journal entries can be posted to the general ledger in the period. It is true when the status is open or reopened. It doesn't reflect locks that apply only to some types of journal entry. To change a period's status, use POST accounting/period-status/:id/execute-task. ## Endpoints - [List accounting periods](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/accountingPeriods.html): `GET /accounting/accounting-periods` - [Retrieve an accounting period](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getAccountingPeriod.html): `GET /accounting/accounting-periods/{id}` --- # List accounting periods Operation ID: `accountingPeriods` Group: Accounting › Accounting Periods [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/accountingPeriods) `GET /accounting/accounting-periods` Read-only: Reads and returns; changes nothing. Lists accounting periods. Each item is one period for one legal entity and one accounting book. To read an item with its period-close checklist, use GET accounting/accounting-periods/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountingPeriodId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `bookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fiscalCalendarId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `periodName` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fiscalYear` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fiscalQuarter` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `startDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `endDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `reopenedByCascade` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `durationDays` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `hasTransactions` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `postable` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, periodName, fiscalYear, fiscalQuarter, startDate, endDate, status, durationDays, postable, lastModifiedDate - Selectable: id, accountingPeriodId, entityId, entityName, bookId, bookName, fiscalCalendarId, fiscalCalendarName, periodName, fiscalYear, fiscalQuarter, startDate, endDate, status, reopeningReason, reopenedByCascade, durationDays, hasTransactions, postable, postableReason, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of accountingPeriodsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for this entity-book accounting period row. This identifies the (entity, book, period) combination, not the shared accounting period. | | `accountingPeriodId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the underlying accounting period, shared by every entity that uses this fiscal calendar. See id on GET accounting/accounting-periods/:id. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the legal entity this status belongs to. See id on GET base/entities. Absent only when the entity-book link is broken. | | `entityName` | string \| null | no | no | no | The name of the legal entity this status belongs to. | | `bookId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the accounting book this status belongs to. A period is opened and closed per book. See id on GET accounting/books/:id. | | `bookName` | string \| null | no | no | no | The name of the accounting book this status belongs to. | | `fiscalCalendarId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the fiscal calendar this period belongs to. See id on GET accounting/fiscal-calendars/:id. Absent when the period was deleted from under its status row. | | `fiscalCalendarName` | string \| null | no | no | no | The name of the fiscal calendar this period belongs to. | | `periodName` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The display name of the period as its fiscal calendar defines it, for example 'Jan 2026'. The format varies by calendar, so do not parse it. Match on dates instead. | | `fiscalYear` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The fiscal year label this period belongs to, for example 'FY2024'. Set by the calendar, so it need not match the calendar year of startDate. | | `fiscalQuarter` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The fiscal quarter label this period belongs to, for example 'Q1 2026'. | | `startDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The first day of the period, inclusive. Format YYYY-MM-DD. Absent only when the underlying period was deleted. | | `endDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The last day of the period, inclusive. Format YYYY-MM-DD. | | `status` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The status this entity and book have put the period in. Possible values: 'created' (generated, not yet opened), 'open', 'closed', 'reopened'. Only 'open' and 'reopened' accept postings. | | `reopeningReason` | string \| null | no | no | no | The free-text reason recorded when the period was reopened. Set only when status is 'reopened'. | | `reopenedByCascade` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this period was reopened as a side effect of reopening a later one, rather than directly. | | `durationDays` | number \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The number of days the period spans, both bounds inclusive. Absent when startDate or endDate is. | | `hasTransactions` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether at least one journal entry line has been posted into the underlying period, by any entity. | | `postable` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the period currently accepts postings for this entity and book, true when its status is 'open' or 'reopened'. This does not account for journal-type-specific locks. | | `postableReason` | string \| null | no | no | no | Why the period does not accept postings, for example 'status is closed'. Empty when it is postable. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time this status row last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. It changes when the period is opened, closed or reopened for this entity and book. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountingPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "fiscalCalendarId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fiscalCalendarName": "", "periodName": "", "fiscalYear": "", "fiscalQuarter": "", "startDate": "2026-01-15", "endDate": "2026-01-15", "status": "", "reopeningReason": "", "reopenedByCascade": true, "durationDays": 0, "hasTransactions": true, "postable": true, "postableReason": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an accounting period Operation ID: `getAccountingPeriod` Group: Accounting › Accounting Periods [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getAccountingPeriod) `GET /accounting/accounting-periods/{id}` Read-only: Reads and returns; changes nothing. Retrieves an accounting period. The record is one period for one legal entity and one accounting book, so the same period has a separate record for each entity and book. The response includes whether the period accepts new journal entries for that entity and book (postable), and the period-close checklist (closeTasks). ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the entity-book accounting period record you want to retrieve. This is the id of a record from GET accounting/accounting-periods, not the shared accountingPeriodId. | ## Response `200` Success Under `data` (getAccountingPeriodOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for this entity-book accounting period row. This identifies the (entity, book, period) combination, not the shared accounting period. | | `accountingPeriodId` | string | no | The identifier of the underlying accounting period, shared by every entity that uses this fiscal calendar. See id on GET accounting/accounting-periods/:id. | | `entityId` | string | no | The identifier of the legal entity this status belongs to. See id on GET base/entities. Absent only when the entity-book link is broken. | | `entityName` | string | no | The name of the legal entity this status belongs to. | | `bookId` | string | no | The identifier of the accounting book this status belongs to. A period is opened and closed per book. See id on GET accounting/books/:id. | | `bookName` | string | no | The name of the accounting book this status belongs to. | | `fiscalCalendarId` | string | no | The identifier of the fiscal calendar this period belongs to. See id on GET accounting/fiscal-calendars/:id. Absent when the period was deleted from under its status row. | | `fiscalCalendarName` | string | no | The name of the fiscal calendar this period belongs to. | | `periodName` | string | no | The display name of the period as its fiscal calendar defines it, for example 'Jan 2026'. The format varies by calendar, so do not parse it. Match on dates instead. | | `fiscalYear` | string | no | The fiscal year label this period belongs to, for example 'FY2024'. Set by the calendar, so it need not match the calendar year of startDate. | | `fiscalQuarter` | string | no | The fiscal quarter label this period belongs to, for example 'Q1 2026'. | | `startDate` | date | no | The first day of the period, inclusive. Format YYYY-MM-DD. Absent only when the underlying period was deleted. | | `endDate` | date | no | The last day of the period, inclusive. Format YYYY-MM-DD. | | `durationDays` | number | no | The number of days the period spans, both bounds inclusive. Absent when startDate or endDate is. | | `status` | string | yes | The status this entity and book have put the period in. Possible values: 'created' (generated, not yet opened), 'open', 'closed', 'reopened'. Only 'open' and 'reopened' accept postings. | | `postable` | boolean | yes | Whether the period currently accepts postings for this entity and book, true when its status is 'open' or 'reopened'. This does not account for journal-type-specific locks. | | `postableReason` | string | yes | Why the period does not accept postings, for example 'status is closed'. Empty when it is postable. | | `reopeningReason` | string | no | The free-text reason recorded when the period was reopened. Set only when status is 'reopened'. | | `reopenedByCascade` | boolean | no | Whether this period was reopened as a side effect of reopening a later one, rather than directly. | | `hasTransactions` | boolean | no | Whether at least one journal entry line has been posted into the underlying period, by any entity. | | `lastModifiedDate` | date-time | yes | The date and time this status row last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. It changes when the period is opened, closed or reopened for this entity and book. | | `closeTasks` | array | no | The period-close checklist for this entity and book, one entry per task. An empty array when the row has no tasks yet. Check isExecutable before running a task with POST accounting/period-status/:id/execute-task. | | `closeTasks[].taskCode` | string | yes | The code identifying the task, and the value to pass as task to POST accounting/period-status/:id/execute-task. The forward tasks, for example 'open', 'close', 'reopen', 'lockAP', 'lockAR', 'lockRevenue', 'lockJournals', 'runRevaluation', 'calculateConsolidationRates'. The task that undoes one is in reverseTaskCode. | | `closeTasks[].reverseTaskCode` | string | no | The code of the task that undoes this one, for example 'unlockAP' for 'lockAP'. Empty when the task cannot be reversed. | | `closeTasks[].isExecuted` | boolean | yes | Whether this task has already run for this entity-book period. | | `closeTasks[].isExecutable` | boolean | yes | Whether this task can run now. It is false for a task already done and for one whose preconditions are unmet. notExecutableReason says which. | | `closeTasks[].notExecutableReason` | string | no | Why the task cannot run now. Present only when isExecutable is false and a reason is available. | | `closeTasks[].isReversible` | boolean | yes | Whether the task that undoes this one can run now. | | `closeTasks[].notReversibleReason` | string | no | Why the reversal cannot run now. Present only when isReversible is false and a reason is available. | | `closeTasks[].notApplicable` | boolean | no | Whether this task does not apply to this entity-book period at all, for example a consolidation task on an entity that does not consolidate. Such a task is neither executed nor executable and does not block the close. | | `closeTasks[].message` | string | no | A human-readable, translated description of the task's state: the applied message when it has run, the not-applied message when it has not, or the not-applicable message when it does not apply. | | `closeTasks[].lastModifiedDate` | date-time | no | The date and time this task's state last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Absent on a task that has never run. | | `closeTasks[].lastModifiedBy` | string | no | The identifier of the user who last changed this task's state. Absent on a task that has never run. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountingPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "fiscalCalendarId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fiscalCalendarName": "", "periodName": "", "fiscalYear": "", "fiscalQuarter": "", "startDate": "2026-01-15", "endDate": "2026-01-15", "durationDays": 0, "status": "", "postable": true, "postableReason": "", "reopeningReason": "", "reopenedByCascade": true, "hasTransactions": true, "lastModifiedDate": "2026-01-15T09:30:00.000Z", "closeTasks": [ { "taskCode": "", "reverseTaskCode": "", "isExecuted": true, "isExecutable": true, "notExecutableReason": "", "isReversible": true, "notReversibleReason": "", "notApplicable": true, "message": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lastModifiedBy": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Accounts Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingAccounts) Accounts are the general ledger accounts in a chart of accounts, each classified by type and subtype. ## Endpoints - [List accounts](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/accounts.html): `GET /accounting/accounts` - [Search accounts](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchAccount.html): `POST /accounting/accounts/search` - [Retrieve an account](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getAccount.html): `GET /accounting/accounts/{id}` --- # List accounts Operation ID: `accounts` Group: Accounting › Accounts [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/accounts) `GET /accounting/accounts` Read-only: Reads and returns; changes nothing. Lists accounts. The items don't include externalIdentifiers, which GET accounting/accounts/:id returns. To match on name or by external identifier, use POST accounting/accounts/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountSubType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `chartOfAccountsId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `parentAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `groupingAccount` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `revaluation` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `payOnBehalf` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `elimination` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `exchangeRateType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, accountNumber, accountType, accountSubType, accountStatus, chartOfAccountsId, groupingAccount, entityId, currency, lastModifiedDate - Selectable: id, accountNumber, accountName, accountType, accountSubType, accountStatus, description, chartOfAccountsId, parentAccountId, groupingAccount, entityId, currency, revaluation, revaluationMode, payOnBehalf, elimination, exchangeRateType, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of accountsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the account. | | `accountNumber` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The account number, unique within its chart of accounts. | | `accountName` | string | no | no | no | The display name of the account. | | `accountType` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The top-level classification of the account. Possible values: 'asset', 'liability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense'. | | `accountSubType` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | A finer classification within accountType. Possible values: 'bank', 'accountsReceivable', 'fixedAsset', 'cip', 'prepaidExpenses', 'deferredExpenses', 'otherCurrentAsset', 'otherAsset', 'unbilledReceivable', 'longTermAsset', 'accountsPayable', 'creditCard', 'deferredRevenue', 'otherCurrentLiability', 'longTermLiability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense', 'otherComprehensiveIncome'. Absent when none is assigned. | | `accountStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The lifecycle status of the account. Possible values: 'enabled' (available on transactions), 'disabled' (not available for new transactions), 'draft' (created but not yet reviewed), 'draftErrors' (a draft with validation errors to resolve before it can be enabled). | | `description` | string \| null | no | no | no | A free-form description of the account. Absent when none was set. | | `chartOfAccountsId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The identifier of the chart of accounts this account belongs to. An account belongs to exactly one. See id on GET accounting/chart-of-accounts/:id. | | `parentAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the parent account this account rolls up to. Absent for a top-level account. See id on GET accounting/accounts/:id. | | `groupingAccount` | boolean | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether this is a grouping (rollup) account. Balances from child accounts aggregate here, and journal entries cannot be posted to it directly. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The identifier of the legal entity this account is denominated for. Set only on bank and credit-card accounts. See id on GET base/entities. | | `currency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The account's currency. Format ISO 4217, for example 'USD'. Set only on accounts linked to a cash account, alongside entityId. | | `revaluation` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether balances on this account are revalued for exchange-rate changes. | | `revaluationMode` | string \| null | no | no | no | Which revaluation results this account recognizes. Possible values: 'recognizeAll' (both unrealized gains and unrealized losses), 'recognizeAllButUnrealizedGains' (only unrealized losses, leaving gains unposted as the imparity principle requires), 'none' (the account is not revalued). Absent when the account has no mode set. `revaluation` is true for every value other than 'none'. | | `payOnBehalf` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this account takes part in pay-on-behalf accounting. | | `elimination` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this account is eliminated during consolidation. | | `exchangeRateType` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Which rate translates this account's amounts on revaluation. Possible values: 'historical', 'average', 'current'. Absent on grouping accounts. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the account record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountNumber": "", "accountName": "", "accountType": "", "accountSubType": "", "accountStatus": "", "description": "", "chartOfAccountsId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "parentAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "groupingAccount": true, "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "currency": "", "revaluation": true, "revaluationMode": "", "payOnBehalf": true, "elimination": true, "exchangeRateType": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search accounts Operation ID: `searchAccount` Group: Accounting › Accounts [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchAccount) `POST /accounting/accounts/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches accounts. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchAccountInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. An account must match every filter you send. Omit to return all accounts. | | `filters.id` | string | no | Limits results to the account with this identifier. The match is exact. | | `filters.accountNumber` | string | no | Limits results to the account with this number. Pair it with chartOfAccountsId, since the number is unique only within a chart of accounts. | | `filters.accountName` | string | no | Limits results to accounts whose name contains this text. The match is case-insensitive. | | `filters.chartOfAccountsId` | string | no | Limits results to accounts in this chart of accounts, by its identifier. An identifier that matches none returns an empty page. | | `filters.accountType` | string | no | Limits results to accounts of this type. Possible values: 'asset', 'liability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense'. Any other value matches nothing. | | `filters.accountSubType` | string | no | Limits results to accounts of this sub-type. Possible values: 'bank', 'accountsReceivable', 'fixedAsset', 'cip', 'prepaidExpenses', 'deferredExpenses', 'otherCurrentAsset', 'otherAsset', 'unbilledReceivable', 'longTermAsset', 'accountsPayable', 'creditCard', 'deferredRevenue', 'otherCurrentLiability', 'longTermLiability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense', 'otherComprehensiveIncome'. Any other value matches nothing. | | `filters.accountStatus` | string | no | Limits results to accounts with this status. Possible values: 'enabled', 'disabled', 'draft', 'draftErrors'. Any other value matches nothing. | | `filters.entityId` | string | no | Limits results to accounts denominated for this legal entity, by its identifier. Only bank and credit-card accounts name an entity, so no other sub-type matches. An identifier that matches none returns an empty page. | | `filters.groupingAccount` | boolean | no | True returns only grouping accounts, false only posting accounts. Omit for both. | | `filters.externalIdentifier` | object | no | Limits results to accounts carrying this external identifier. Both fieldName and value are required. Several accounts may share a value, so this returns the items array, not a single record. | | `filters.externalIdentifier.fieldName` | string | yes | The configured key of the external identifier, a camelCase slug such as 'netsuiteAccountId'. It must be the fieldName of an existing configuration, or the request fails with UNKNOWN_EXTERNAL_IDENTIFIER. | | `filters.externalIdentifier.value` | string | yes | The identifier as the source system holds it. The match is exact and case-sensitive. | | `page` | object | no | Pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit on the first request. | | `page.size` | number | no | The number of accounts per page, between 1 and 200. Defaults to 50. A value outside that range fails with INVALID_PAGE_SIZE rather than being adjusted. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountNumber": "", "accountName": "", "chartOfAccountsId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountType": "", "accountSubType": "", "accountStatus": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "groupingAccount": true, "externalIdentifier": { "fieldName": "", "value": "" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchAccountOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The accounts that match, ordered by account number. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the account. | | `items[].accountNumber` | string | yes | The account number, unique within its chart of accounts. | | `items[].accountName` | string | yes | The display name of the account. | | `items[].accountType` | string | yes | The top-level classification of the account. Possible values: 'asset', 'liability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense'. | | `items[].accountSubType` | string | no | A finer classification within accountType. Possible values: 'bank', 'accountsReceivable', 'fixedAsset', 'cip', 'prepaidExpenses', 'deferredExpenses', 'otherCurrentAsset', 'otherAsset', 'unbilledReceivable', 'longTermAsset', 'accountsPayable', 'creditCard', 'deferredRevenue', 'otherCurrentLiability', 'longTermLiability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense', 'otherComprehensiveIncome'. Absent when none is assigned. | | `items[].accountStatus` | string | no | The lifecycle status of the account. Possible values: 'enabled' (available on transactions), 'disabled' (not available for new transactions), 'draft' (created but not yet reviewed), 'draftErrors' (a draft with validation errors to resolve before it can be enabled). | | `items[].description` | string | no | A free-form description of the account. Absent when none was set. | | `items[].chartOfAccountsId` | string | yes | The identifier of the chart of accounts this account belongs to. An account belongs to exactly one. See id on GET accounting/chart-of-accounts/:id. | | `items[].parentAccountId` | string | no | The identifier of the parent account this account rolls up to. Absent for a top-level account. See id on GET accounting/accounts/:id. | | `items[].groupingAccount` | boolean | yes | Whether this is a grouping (rollup) account. Balances from child accounts aggregate here, and journal entries cannot be posted to it directly. | | `items[].entityId` | string | no | The identifier of the legal entity this account is denominated for. Set only on bank and credit-card accounts. See id on GET base/entities. | | `items[].currency` | string | no | The account's currency. Format ISO 4217, for example 'USD'. Set only on accounts linked to a cash account, alongside entityId. | | `items[].revaluation` | boolean | no | Whether balances on this account are revalued for exchange-rate changes. | | `items[].payOnBehalf` | boolean | no | Whether this account takes part in pay-on-behalf accounting. | | `items[].elimination` | boolean | no | Whether this account is eliminated during consolidation. | | `items[].exchangeRateType` | string | no | Which rate translates this account's amounts on revaluation. Possible values: 'historical', 'average', 'current'. Absent on grouping accounts. | | `items[].lastModifiedDate` | date-time | yes | The date and time the account record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].externalIdentifiers` | array | no | External identifiers owned by other systems, at most one per fieldName. An empty array when the account has none. | | `items[].externalIdentifiers[].fieldName` | string | yes | The configured key of the external identifier, a camelCase slug such as 'netsuiteAccountId'. It is the fieldName of an existing account external-identifier configuration. | | `items[].externalIdentifiers[].value` | string | yes | The identifier as the source system holds it. | | `items[].externalIdentifiers[].externalUrl` | string | no | An absolute http(s) link to the object in the source system. Absent when none is set. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountNumber": "", "accountName": "", "accountType": "", "accountSubType": "", "accountStatus": "", "description": "", "chartOfAccountsId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "parentAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "groupingAccount": true, "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "currency": "", "revaluation": true, "payOnBehalf": true, "elimination": true, "exchangeRateType": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 | | `INVALID_PAGE_SIZE` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: UNKNOWN_EXTERNAL_IDENTIFIER, INVALID_PAGE_SIZE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an account Operation ID: `getAccount` Group: Accounting › Accounts [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getAccount) `GET /accounting/accounts/{id}` Read-only: Reads and returns; changes nothing. Retrieves an account. Use the account's id, not its accountNumber. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier for the account to return. | ## Response `200` Success Under `data` (getAccountOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the account. | | `accountNumber` | string | yes | The account number, unique within its chart of accounts. | | `accountName` | string | yes | The display name of the account. | | `accountType` | string | yes | The top-level classification of the account. Possible values: 'asset', 'liability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense'. | | `accountSubType` | string | no | A finer classification within accountType. Possible values: 'bank', 'accountsReceivable', 'fixedAsset', 'cip', 'prepaidExpenses', 'deferredExpenses', 'otherCurrentAsset', 'otherAsset', 'unbilledReceivable', 'longTermAsset', 'accountsPayable', 'creditCard', 'deferredRevenue', 'otherCurrentLiability', 'longTermLiability', 'equity', 'income', 'costOfGoodsSold', 'expense', 'otherIncome', 'otherExpense', 'otherComprehensiveIncome'. Absent when none is assigned. | | `accountStatus` | string | no | The lifecycle status of the account. Possible values: 'enabled' (available on transactions), 'disabled' (not available for new transactions), 'draft' (created but not yet reviewed), 'draftErrors' (a draft with validation errors to resolve before it can be enabled). | | `description` | string | no | A free-form description of the account. Absent when none was set. | | `chartOfAccountsId` | string | yes | The identifier of the chart of accounts this account belongs to. An account belongs to exactly one. See id on GET accounting/chart-of-accounts/:id. | | `parentAccountId` | string | no | The identifier of the parent account this account rolls up to. Absent for a top-level account. See id on GET accounting/accounts/:id. | | `groupingAccount` | boolean | yes | Whether this is a grouping (rollup) account. Balances from child accounts aggregate here, and journal entries cannot be posted to it directly. | | `entityId` | string | no | The identifier of the legal entity this account is denominated for. Set only on bank and credit-card accounts. See id on GET base/entities. | | `currency` | string | no | The account's currency. Format ISO 4217, for example 'USD'. Set only on accounts linked to a cash account, alongside entityId. | | `revaluation` | boolean | no | Whether balances on this account are revalued for exchange-rate changes. | | `payOnBehalf` | boolean | no | Whether this account takes part in pay-on-behalf accounting. | | `elimination` | boolean | no | Whether this account is eliminated during consolidation. | | `exchangeRateType` | string | no | Which rate translates this account's amounts on revaluation. Possible values: 'historical', 'average', 'current'. Absent on grouping accounts. | | `lastModifiedDate` | date-time | yes | The date and time the account record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `externalIdentifiers` | array | no | External identifiers owned by other systems, at most one per fieldName. An empty array when the account has none. | | `externalIdentifiers[].fieldName` | string | yes | The configured key of the external identifier, a camelCase slug such as 'netsuiteAccountId'. It is the fieldName of an existing account external-identifier configuration. | | `externalIdentifiers[].value` | string | yes | The identifier as the source system holds it. | | `externalIdentifiers[].externalUrl` | string | no | An absolute http(s) link to the object in the source system. Absent when none is set. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountNumber": "", "accountName": "", "accountType": "", "accountSubType": "", "accountStatus": "", "description": "", "chartOfAccountsId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "parentAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "groupingAccount": true, "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "currency": "", "revaluation": true, "payOnBehalf": true, "elimination": true, "exchangeRateType": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Advance Payment Categories Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingAdvancePaymentCategories) An advance payment category sets the accounts that employee advances use: the bank account an advance is paid from, and the offset account that records the advance until an expense report settles it. Each legal entity has at most one advance payment category, and employee advance requests refer to it. The category's bank and offset accounts are set for each chart of accounts, and no endpoint reads or sets them. ## Endpoints - [List advance payment categories](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/advancePaymentCategories.html): `GET /accounting/advance-payment-categories` - [Create an advance payment category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createAdvancePaymentCategory.html): `POST /accounting/advance-payment-categories` - [Search advance payment categories](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchAdvancePaymentCategories.html): `POST /accounting/advance-payment-categories/search` - [Retrieve an advance payment category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getAdvancePaymentCategory.html): `GET /accounting/advance-payment-categories/{id}` - [Replace an advance payment category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateAdvancePaymentCategory.html): `PATCH /accounting/advance-payment-categories/{id}` - [Delete an advance payment category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/deleteAdvancePaymentCategory.html): `DELETE /accounting/advance-payment-categories/{id}` --- # List advance payment categories Operation ID: `advancePaymentCategories` Group: Accounting › Advance Payment Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/advancePaymentCategories) `GET /accounting/advance-payment-categories` Read-only: Reads and returns; changes nothing. Lists advance payment categories. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `name` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `allowManualPayment` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, name, status, lastModifiedDate - Selectable: id, entityId, name, allowManualPayment, status, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of advancePaymentCategoriesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Stable, platform-assigned id (a UUID) of the advance payment category — the public identifier used by GET accounting/advance-payment-categories/:id. Filter e.g. filter[id][in]=,. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Id (a UUID) of the legal entity this category belongs to. An entity has at most one advance payment category, so filter[entityId][eq]= is the way to find 'the' category for an entity. | | `name` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Display name of the category. Filter target: filter[name][like]=Standard, or filter[name][eq]=Standard Advance. | | `allowManualPayment` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether an employee may be paid this advance manually (outside the standard payment run). Filter target: filter[allowManualPayment][eq]=true. | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Lifecycle status. Possible values: 'active', 'inactive'. Filter target: filter[status][eq]=active. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Instant the advance payment category last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). filter[lastModifiedDate][gte]=… plus sort=lastModifiedDate is a complete incremental-sync cursor. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "allowManualPayment": true, "status": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create an advance payment category Operation ID: `createAdvancePaymentCategory` Group: Accounting › Advance Payment Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createAdvancePaymentCategory) `POST /accounting/advance-payment-categories` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates an advance payment category for a legal entity. A legal entity can have only one category. The response is only the new category's id. The category's bank and offset accounts for each chart of accounts can't be set through this endpoint. Until they're set in the application, employee advances in this category can't be issued. Read the new record with GET accounting/advance-payment-categories/:id. To change a legal entity's existing category, use PATCH accounting/advance-payment-categories/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createAdvancePaymentCategoryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `entityId` | string | yes | Id (a UUID) of the legal entity this category belongs to. An entity has at most one advance payment category. Example: `"7c1a9e40-2b53-4d68-9f01-3a5b7c9d1e24"`. | | `name` | string | yes | Display name of the category. Required. Example: `"Standard Advance"`. | | `allowManualPayment` | boolean | no | Whether an employee may be paid this advance manually (outside the standard payment run). Omit to default to true. Example: `true`. | | `status` | string | no | Lifecycle status. Accepted values: 'active', 'inactive'. Omit to default to 'active'. Example: `"active"`. | ```json { "entityId": "7c1a9e40-2b53-4d68-9f01-3a5b7c9d1e24", "name": "Standard Advance", "allowManualPayment": true, "status": "active" } ``` ## Response `200` Success Under `data` (createAdvancePaymentCategoryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Id (a UUID) of the newly created advance payment category. This is the only field a write returns; read the record back with GET accounting/advance-payment-categories/{id} if you need its stored state. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ```json { "data": { "id": "b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `REFERENCE_NOT_FOUND` | 400 | | `CATEGORY_ALREADY_EXISTS` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: REFERENCE_NOT_FOUND, CATEGORY_ALREADY_EXISTS - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search advance payment categories Operation ID: `searchAdvancePaymentCategories` Group: Accounting › Advance Payment Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchAdvancePaymentCategories) `POST /accounting/advance-payment-categories/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches advance payment categories. The filters are legal entity, name, and status. Matching categories are returned newest first. A legal entity has at most one category, so filters.entityId returns at most one item. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchAdvancePaymentCategoriesInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | Search filters, nested as one object. All are optional and are ANDed; omit the whole object to match every category. Example: `{"status":"active"}`. | | `filters.entityId` | string | no | Restrict to the category belonging to this entity (a UUID). An entity has at most one category, so this matches at most one item. A uuid that matches no entity returns an empty page, not an error. | | `filters.name` | string | no | Exact match on the category's display name. | | `filters.status` | string | no | Restrict to categories in this lifecycle status: active or inactive. | | `page` | object | no | Pagination controls. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | Opaque forward cursor from a previous response's pagination.nextCursor; omit for the first page. | | `page.size` | number | no | Maximum number of categories to return. Defaults to 50, capped at 200. | ```json { "filters": { "status": "active" }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchAdvancePaymentCategoriesOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | Matching advance payment categories for this page, newest first. | | `items[].id` | string | yes | | | `items[].entityId` | string | yes | | | `items[].name` | string | yes | | | `items[].allowManualPayment` | boolean | no | | | `items[].status` | string | no | | | `items[].lastModifiedDate` | date-time | yes | | | `pagination` | object | yes | Pagination cursor for this page. | | `pagination.nextCursor` | string | no | Opaque cursor to pass as page.cursor for the next page; absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "allowManualPayment": true, "status": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an advance payment category Operation ID: `getAdvancePaymentCategory` Group: Accounting › Advance Payment Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getAdvancePaymentCategory) `GET /accounting/advance-payment-categories/{id}` Read-only: Reads and returns; changes nothing. Retrieves an advance payment category. The response gives the category's name, status, and whether manual payment is allowed. A legal entity has at most one category. To find it by legal entity, filter GET accounting/advance-payment-categories on entityId. The category's bank and offset accounts for each chart of accounts are not returned. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | Id of the advance payment category to fetch (a UUID); bound from the :id path segment. Example: `"9c1b7e40-3a6d-4f52-8f0b-1d2c7a5e93b1"`. | ## Response `200` Success Under `data` (getAdvancePaymentCategoryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Id of the advance payment category (a UUID) — the public identifier used by this route. | | `entityId` | string | yes | Id (a UUID) of the legal entity this category belongs to. An entity has at most one advance payment category. | | `name` | string | yes | Display name of the category. Renaming an existing category is done by calling upsert again with the same entityId and a new name — the underlying write is a rename in place, not a new record. | | `allowManualPayment` | boolean | no | Whether an employee may be paid this advance manually (outside the standard payment run). Defaults to true. | | `status` | string | no | Lifecycle status. Possible values: 'active', 'inactive'. Defaults to 'active'. | | `lastModifiedDate` | date-time | yes | Instant the advance payment category last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). Poll on this to sync incrementally rather than re-reading the whole collection. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "allowManualPayment": true, "status": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Replace an advance payment category Operation ID: `updateAdvancePaymentCategory` Group: Accounting › Advance Payment Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateAdvancePaymentCategory) `PATCH /accounting/advance-payment-categories/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Replaces an advance payment category. A field you leave out goes back to its default: allowManualPayment goes back to true and status goes back to 'active'. name is required. Each request deletes all of the category's bank and offset accounts, and this endpoint can't set them again. Until they're set again in the application, employee advances in this category can't be issued. The legal entity can't be changed. The response is empty. Read the updated record with GET accounting/advance-payment-categories/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the advance payment category to update; bound from the :id path segment. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Request body Required, `application/json`: updateAdvancePaymentCategoryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Display name of the category. Required — renaming is the primary purpose of an update call. Example: `"Standard Advance (renamed)"`. | | `allowManualPayment` | boolean | no | Whether an employee may be paid this advance manually (outside the standard payment run). Omit to default to true. Example: `true`. | | `status` | string | no | Lifecycle status. Accepted values: 'active', 'inactive'. Omit to default to 'active'. Example: `"active"`. | ```json { "name": "Standard Advance (renamed)", "allowManualPayment": true, "status": "active" } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `CATEGORY_NOT_FOUND` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: CATEGORY_NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Delete an advance payment category Operation ID: `deleteAdvancePaymentCategory` Group: Accounting › Advance Payment Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/deleteAdvancePaymentCategory) `DELETE /accounting/advance-payment-categories/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Deletes an advance payment category. The category's bank and offset accounts for each chart of accounts are deleted with it. A category that any employee advance request refers to, drafts included, can't be deleted. To retire it instead, set its status to 'inactive' with PATCH accounting/advance-payment-categories/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the advance payment category to delete; bound from the :id path segment. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `CATEGORY_NOT_FOUND` | 400 | | `CATEGORY_IN_USE` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: CATEGORY_NOT_FOUND, CATEGORY_IN_USE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Amortization Schedule Lines Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingAmortizationScheduleLines) An amortization schedule line is one period's amount of an amortization schedule. The amortization schedule has the method, the term, and the source document. There is no endpoint to change a line. An installment's journal entry is posted to the general ledger through the amortization schedule. outstanding is true when an installment's journal entry hasn't been posted and the installment isn't confirmed as zero. Filter on outstanding rather than working it out from isJournalEntryPosted and isConfirmedZero. isConfirmedZero can be unset, and if you treat unset as false you miss outstanding lines. To find out whether amortization blocks a period close, filter by legal entity, accounting book, and the period end date, not by the period id. An installment scheduled in an earlier period whose journal entry was never posted also blocks the close, and a scheduledPeriodId filter misses it. A line has no legal entity of its own. Its entityId comes from the source journal entry line of the schedule, so a line whose schedule or source document was deleted has no entityId. Those lines are still returned but don't match an entity filter, so check for lines with no entityId when you reconcile to zero. ## Endpoints - [List amortization schedule lines](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/amortizationScheduleLines.html): `GET /accounting/amortization-schedule-lines` --- # List amortization schedule lines Operation ID: `amortizationScheduleLines` Group: Accounting › Amortization Schedule Lines [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/amortizationScheduleLines) `GET /accounting/amortization-schedule-lines` Read-only: Reads and returns; changes nothing. Lists amortization schedule lines. Each line is one amortization schedule's amount for one accounting period. To see what is still outstanding before you close a period, filter on outstanding=true for one entityId and one bookId. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `amortizationScheduleHeaderId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `bookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `scheduledPeriodId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `scheduledPeriodStartDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `scheduledPeriodEndDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `transactionCurrency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `amortizationMethod` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `isJournalEntryPosted` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `isConfirmedZero` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `outstanding` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, scheduledPeriodStartDate, scheduledPeriodEndDate, amount, amortizationStartDate, amortizationEndDate, lastModifiedDate - Selectable: id, amortizationScheduleHeaderId, entityId, entityName, bookId, bookName, scheduledPeriodId, scheduledPeriodName, scheduledPeriodStartDate, scheduledPeriodEndDate, amount, transactionCurrency, amortizationMethod, amortizationStartDate, amortizationEndDate, isJournalEntryPosted, isConfirmedZero, outstanding, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of amortizationScheduleLinesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for this amortization schedule line. | | `amortizationScheduleHeaderId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the amortization schedule this line belongs to. Several lines, one per period, share it. There is no endpoint for the schedule itself. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The legal entity this schedule line belongs to. See id on GET base/entities. Absent when the schedule's source journal entry line is missing. Such a schedule line is still returned, but doesn't match an entityId filter. | | `entityName` | string \| null | no | no | no | The name of the legal entity this schedule line belongs to. | | `bookId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier of the accounting book this schedule posts into. See id on GET accounting/books/:id. | | `bookName` | string \| null | no | no | no | The name of the accounting book this schedule posts into. | | `scheduledPeriodId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The accounting period this schedule line is scheduled in. See accountingPeriodId on GET accounting/accounting-periods. To check whether a period close is blocked, filter on scheduledPeriodEndDate instead, because this filter misses earlier schedule lines that aren't posted. | | `scheduledPeriodName` | string \| null | no | no | no | The display name of the scheduled period as its fiscal calendar defines it, for example 'Aug 2026'. The format varies by calendar, so do not parse it. Match on dates instead. | | `scheduledPeriodStartDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The first day of the scheduled period, inclusive. Format YYYY-MM-DD. | | `scheduledPeriodEndDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The last day of the scheduled period, inclusive. Format YYYY-MM-DD. For a period-close check, filter on this date: it finds every schedule line due on or before the period being closed. | | `amount` | decimal \| null | no | no | yes | The amount of this schedule line, in the currency given by transactionCurrency. | | `transactionCurrency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The currency that denominates amount, taken from the schedule's source journal entry line. Format ISO 4217, for example 'USD'. Absent on a row whose header or source document was deleted. A query that does not filter by book can return rows in several currencies. | | `amortizationMethod` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | How the schedule spreads its total, from the header. Possible values: 'straightLine', 'straightLineProRateFirstLast', 'exactDays', 'pointInTime', 'usageBased'. | | `amortizationStartDate` | date \| null | no | no | yes | The first day of the amortization schedule. Format YYYY-MM-DD. This is the schedule's start, not this schedule line's period. | | `amortizationEndDate` | date \| null | no | no | yes | The last day the schedule amortizes over, from the header. Format YYYY-MM-DD. | | `isJournalEntryPosted` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this schedule line's journal entry has been posted. To find schedule lines still to post, filter on outstanding instead. | | `isConfirmedZero` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this schedule line was confirmed as zero, so no journal entry is needed. Absent means not confirmed. To find schedule lines still to post, filter on outstanding. | | `outstanding` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether this schedule line still needs a journal entry: true when its journal entry isn't posted and it isn't confirmed as zero. Filter on this for a period-close check. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time this schedule line last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. It tracks the line itself, so a change to the header or the joined entity, book or period does not advance it. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amortizationScheduleHeaderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "scheduledPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "scheduledPeriodName": "", "scheduledPeriodStartDate": "2026-01-15", "scheduledPeriodEndDate": "2026-01-15", "amount": "19.99", "transactionCurrency": "", "amortizationMethod": "", "amortizationStartDate": "2026-01-15", "amortizationEndDate": "2026-01-15", "isJournalEntryPosted": true, "isConfirmedZero": true, "outstanding": true, "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Asset Management Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/assetManagement) Asset management covers fixed assets and how they are depreciated. A fixed asset is the accounting record of an asset, such as a forklift, in one accounting book. Every fixed asset has a fixed asset type, such as machinery or vehicles. A depreciation policy sets how fixed assets of each type are depreciated. It sets the useful life, the default depreciation method and period length, and the accounts that depreciation and disposal journal entries post to. Saving a fixed asset creates its depreciation schedule. The schedule is the plan for depreciating the fixed asset over its useful life, with a line for each period. Recording depreciation for the lines creates the journal entries. You can record depreciation through the API. ## Groups - [Depreciation Policies](https://docs.api.lensing.ai/reference/lensing/v1/groups/invDepreciationPolicies.html) - [Fixed Asset Types](https://docs.api.lensing.ai/reference/lensing/v1/groups/invFixedAssetTypes.html) - [Fixed Assets](https://docs.api.lensing.ai/reference/lensing/v1/groups/invFixedAssets.html) --- # Depreciation Policies Group: Accounting › Asset Management [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/invDepreciationPolicies) A depreciation policy sets how fixed assets are depreciated in one accounting book. A policy covers one or more fixed asset types, such as machinery or vehicles. For each type, the policy has a depreciation rule. The rule sets the useful life for fixed assets of that type. The policy also sets a default depreciation method and period length. It sets the accounts that depreciation and disposal journal entries post to. These are the asset, accumulated depreciation, depreciation expense, gain, and loss accounts. Each book needs its own policies. A policy can be set up for several legal entities. For each fixed asset type, legal entity, and book, only one policy can be active. ## Endpoints - [List depreciation policies](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/depreciationPolicies.html): `GET /depreciation-policies` - [Search depreciation policies](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchDepreciationPolicies.html): `POST /depreciation-policies/search` - [Retrieve a depreciation policy](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getDepreciationPolicy.html): `GET /depreciation-policies/{id}` --- # List depreciation policies Operation ID: `depreciationPolicies` Group: Accounting › Asset Management › Depreciation Policies [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/depreciationPolicies) `GET /depreciation-policies` Read-only: Reads and returns; changes nothing. Lists depreciation policies. Each policy has the same fields as `GET depreciation-policies/:id`. To find the policies that use an account, filter the list on that account field, such as `depreciationExpenseAccountId`. To find policies by name or description, use `POST depreciation-policies/search`. The list includes policies that aren't set up for any legal entity. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationPolicyNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationMethod` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationConvention` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationInterval` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `bookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `assetAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accumulatedDepreciationAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationExpenseAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `gainAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lossAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `active` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, depreciationPolicyNumber, name, depreciationMethod, depreciationConvention, depreciationInterval, bookId, active, lastModifiedDate - Selectable: id, depreciationPolicyNumber, name, description, depreciationMethod, depreciationConvention, depreciationInterval, bookId, assetAccountId, accumulatedDepreciationAccountId, depreciationExpenseAccountId, gainAccountId, lossAccountId, active, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of depreciationPoliciesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the depreciation policy. Example: `"b1d4c8f2-5a37-4e91-8c62-9f0a3d7e14b5"`. | | `depreciationPolicyNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The depreciation policy number. Unique within the tenant. Example: 'DP-0007'. Example: `"DP-0007"`. | | `name` | string | no | no | yes | The name of the depreciation policy. Unique within the tenant. Example: `"Office Equipment — 5 Year Straight Line"`. | | `description` | string \| null | no | no | no | A description of the depreciation policy. Example: `"Standard straight-line write-down for office equipment over five years."`. | | `depreciationMethod` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The method used to spread depreciation over an asset's useful life. When a fixed asset of one of these types is set up, it takes this method. The method can then be changed on the fixed asset. Possible values: 'StraightLine' (equal amounts each period), 'Degressive' (declining balance at the fixed asset's annual `degressionRate`, switching to straight line when that gives a higher amount), 'Immediate' (the whole depreciable amount in the first period), 'Bypassed' (no depreciation schedule is created). How the method treats time is `depreciationConvention`. Example: `"StraightLine"`. | | `depreciationConvention` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How the method treats time: how the first and last periods are measured and how an amount is spread across periods. Possible values: 'FullMonth' (the in-service month counts as a full month; every period takes an equal share), 'MidMonth' (in service on or before the 15th: the full first month; after the 15th: depreciation starts the following month), 'MidYear' (year one and the year after the useful life each take half the annual amount), 'SixMonthRule' (the full annual amount in year one when in service more than six months of the fiscal year, otherwise half of it), 'ProrateFirstLast' (the partial first and last periods are prorated by days), 'ActualDays' (every period is weighted by its actual number of days). Present for 'StraightLine' (any value) and 'Degressive' (only 'FullMonth'); absent for 'Immediate' and 'Bypassed', which take none. Example: `"FullMonth"`. | | `depreciationInterval` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The length of each depreciation period. Possible values: 'Monthly', 'Quarterly', 'Yearly'. Example: `"Monthly"`. | | `bookId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The accounting book the depreciation policy applies to. A policy applies to one book only, so each book needs its own policies. See id on GET accounting/books/:id. Absent when the book no longer exists. Example: `"b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47"`. | | `assetAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The balance sheet account that holds the cost of fixed assets under this policy. Disposing of a fixed asset credits its acquisition value to this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"3a5f81c6-9d24-4b70-8e15-6c2b7f094d38"`. | | `accumulatedDepreciationAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The accumulated depreciation account, a contra-asset account. Recording depreciation credits this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"9e14d7b2-6c83-4f51-a027-8d3b5e199c61"`. | | `depreciationExpenseAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The depreciation expense account. Recording depreciation debits this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"5c2b8a41-7e96-4d03-b158-2f7a9c4e60d5"`. | | `gainAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The account for gains when an asset under this policy is sold for more than its net book value. It is used only on books that keep gains and losses in separate accounts, such as HGB books. On such a sale, the asset's net book value and the sale amount go to this account. If the fixed asset has a `revenueAccountId`, the sale amount goes there instead. On most books, gainAccountId and lossAccountId are the same account, set in Lensing as one gain or loss account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"8f0b3d27-4a19-4c62-9e73-1b5d8a2f7c04"`. | | `lossAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The account for losses when an asset under this policy is disposed of. The asset's net book value goes to this account. On a sale, the sale amount also goes here, unless the fixed asset has a `revenueAccountId`. On books that keep gains and losses in separate accounts, a sale for more than the net book value goes to `gainAccountId` instead. On most books, gainAccountId and lossAccountId are the same account, set in Lensing as one gain or loss account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"2d7e9a15-8b34-4f07-a5c1-6e0f3b9d472a"`. | | `active` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the depreciation policy is active. Only active policies are offered when a fixed asset type is chosen for a fixed asset. Depreciation already scheduled under an inactive policy is still recorded. Example: `true`. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the depreciation policy last changed, in UTC. Format: YYYY-MM-DDTHH:mm:ss.sssZ. Example: `"2026-08-14T09:12:33.000Z"`. | ```json { "data": [ { "id": "b1d4c8f2-5a37-4e91-8c62-9f0a3d7e14b5", "depreciationPolicyNumber": "DP-0007", "name": "Office Equipment — 5 Year Straight Line", "description": "Standard straight-line write-down for office equipment over five years.", "depreciationMethod": "StraightLine", "depreciationConvention": "FullMonth", "depreciationInterval": "Monthly", "bookId": "b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47", "assetAccountId": "3a5f81c6-9d24-4b70-8e15-6c2b7f094d38", "accumulatedDepreciationAccountId": "9e14d7b2-6c83-4f51-a027-8d3b5e199c61", "depreciationExpenseAccountId": "5c2b8a41-7e96-4d03-b158-2f7a9c4e60d5", "gainAccountId": "8f0b3d27-4a19-4c62-9e73-1b5d8a2f7c04", "lossAccountId": "2d7e9a15-8b34-4f07-a5c1-6e0f3b9d472a", "active": true, "lastModifiedDate": "2026-08-14T09:12:33.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search depreciation policies Operation ID: `searchDepreciationPolicies` Group: Accounting › Asset Management › Depreciation Policies [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchDepreciationPolicies) `POST /depreciation-policies/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches depreciation policies. Each policy returned has the same fields as `GET depreciation-policies/:id`. To filter by depreciation method, depreciation interval, account, active status, or `lastModifiedDate`, use `GET depreciation-policies`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchDepreciationPoliciesInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A policy must match every filter you send. Omit to return all depreciation policies. If you send an empty list for a filter, that filter is ignored. Example: `{"nameContains":"equipment","bookIds":["b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47"]}`. | | `filters.id` | string | no | Limits results to the depreciation policy with this id. The match is exact. | | `filters.depreciationPolicyNumber` | string | no | Limits results to the policy with this policy number. The match is exact. Example: 'DP-0007'. | | `filters.name` | string | no | Limits results to the policy with this name. The match is exact and case-sensitive. When sent, nameContains is ignored. | | `filters.nameContains` | string | no | Limits results to policies whose name contains this text. The match is case-insensitive, and % and _ match as ordinary characters. Ignored if the request also includes the name filter. | | `filters.descriptionContains` | string | no | Limits results to policies whose description contains this text. The match is case-insensitive, and % and _ match as ordinary characters. A policy with no description never matches. | | `filters.bookIds` | array | no | Limits results to policies that apply to any of these accounting books. The match is exact. See id on GET accounting/books/:id. | | `page` | object | no | The page of results to return. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | The cursor from the previous response's `pagination.nextCursor`. Omit on the first request. | | `page.size` | number | no | The number of depreciation policies per page, between 1 and 200. Defaults to 50. A value above 200 is treated as 200, and a value below 1 as 1, not rejected. | ```json { "filters": { "nameContains": "equipment", "bookIds": [ "b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47" ] }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchDepreciationPoliciesOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The depreciation policies on this page, most recently created first. An empty array when nothing matches. Example: `[{"id":"b1d4c8f2-5a37-4e91-8c62-9f0a3d7e14b5","depreciationPolicyNumber":"DP-0007","name":"Office Equipment — 5 Year Straight Line","description":"Standard straight-line write-down for office equipment over five years.","depreciationMethod":"StraightLine","depreciationConvention":"FullMonth","depreciationInterval":"Monthly","bookId":"b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47","assetAccountId":"3a5f81c6-9d24-4b70-8e15-6c2b7f094d38","accumulatedDepreciationAccountId":"9e14d7b2-6c83-4f51-a027-8d3b5e199c61","depreciationExpenseAccountId":"5c2b8a41-7e96-4d03-b158-2f7a9c4e60d5","gainAccountId":"8f0b3d27-4a19-4c62-9e73-1b5d8a2f7c04","lossAccountId":"2d7e9a15-8b34-4f07-a5c1-6e0f3b9d472a","active":true,"lastModifiedDate":"2026-08-14T09:12:33.000Z"}]`. | | `items[].id` | string | yes | The unique identifier for the depreciation policy. | | `items[].depreciationPolicyNumber` | string | no | The depreciation policy number. Unique within the tenant. Example: 'DP-0007'. | | `items[].name` | string | yes | The name of the depreciation policy. Unique within the tenant. | | `items[].description` | string | no | A description of the depreciation policy. | | `items[].depreciationMethod` | string | yes | The method used to spread depreciation over an asset's useful life. When a fixed asset of one of these types is set up, it takes this method. The method can then be changed on the fixed asset. Possible values: 'StraightLine' (equal amounts each period), 'Degressive' (declining balance at the fixed asset's annual `degressionRate`, switching to straight line when that gives a higher amount), 'Immediate' (the whole depreciable amount in the first period), 'Bypassed' (no depreciation schedule is created). How the method treats time is `depreciationConvention`. | | `items[].depreciationConvention` | string | no | How the method treats time: how the first and last periods are measured and how an amount is spread across periods. Possible values: 'FullMonth' (the in-service month counts as a full month; every period takes an equal share), 'MidMonth' (in service on or before the 15th: the full first month; after the 15th: depreciation starts the following month), 'MidYear' (year one and the year after the useful life each take half the annual amount), 'SixMonthRule' (the full annual amount in year one when in service more than six months of the fiscal year, otherwise half of it), 'ProrateFirstLast' (the partial first and last periods are prorated by days), 'ActualDays' (every period is weighted by its actual number of days). Present for 'StraightLine' (any value) and 'Degressive' (only 'FullMonth'); absent for 'Immediate' and 'Bypassed', which take none. | | `items[].depreciationInterval` | string | yes | The length of each depreciation period. Possible values: 'Monthly', 'Quarterly', 'Yearly'. | | `items[].bookId` | string | no | The accounting book the depreciation policy applies to. A policy applies to one book only, so each book needs its own policies. See id on GET accounting/books/:id. Absent when the book no longer exists. | | `items[].assetAccountId` | string | no | The balance sheet account that holds the cost of fixed assets under this policy. Disposing of a fixed asset credits its acquisition value to this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. | | `items[].accumulatedDepreciationAccountId` | string | no | The accumulated depreciation account, a contra-asset account. Recording depreciation credits this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. | | `items[].depreciationExpenseAccountId` | string | no | The depreciation expense account. Recording depreciation debits this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. | | `items[].gainAccountId` | string | no | The account for gains when an asset under this policy is sold for more than its net book value. It is used only on books that keep gains and losses in separate accounts, such as HGB books. On such a sale, the asset's net book value and the sale amount go to this account. If the fixed asset has a `revenueAccountId`, the sale amount goes there instead. On most books, gainAccountId and lossAccountId are the same account, set in Lensing as one gain or loss account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. | | `items[].lossAccountId` | string | no | The account for losses when an asset under this policy is disposed of. The asset's net book value goes to this account. On a sale, the sale amount also goes here, unless the fixed asset has a `revenueAccountId`. On books that keep gains and losses in separate accounts, a sale for more than the net book value goes to `gainAccountId` instead. On most books, gainAccountId and lossAccountId are the same account, set in Lensing as one gain or loss account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. | | `items[].active` | boolean | no | Whether the depreciation policy is active. Only active policies are offered when a fixed asset type is chosen for a fixed asset. Depreciation already scheduled under an inactive policy is still recorded. | | `items[].lastModifiedDate` | date-time | yes | The date and time the depreciation policy last changed, in UTC. Format: YYYY-MM-DDTHH:mm:ss.sssZ. | | `pagination` | object | yes | Whether more depreciation policies match, and the cursor for the next page. Example: `{"hasMore":false}`. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as `page.cursor`. Absent on the last page. | | `pagination.hasMore` | boolean | yes | Whether more depreciation policies match after this page. | ```json { "data": { "items": [ { "id": "b1d4c8f2-5a37-4e91-8c62-9f0a3d7e14b5", "depreciationPolicyNumber": "DP-0007", "name": "Office Equipment — 5 Year Straight Line", "description": "Standard straight-line write-down for office equipment over five years.", "depreciationMethod": "StraightLine", "depreciationConvention": "FullMonth", "depreciationInterval": "Monthly", "bookId": "b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47", "assetAccountId": "3a5f81c6-9d24-4b70-8e15-6c2b7f094d38", "accumulatedDepreciationAccountId": "9e14d7b2-6c83-4f51-a027-8d3b5e199c61", "depreciationExpenseAccountId": "5c2b8a41-7e96-4d03-b158-2f7a9c4e60d5", "gainAccountId": "8f0b3d27-4a19-4c62-9e73-1b5d8a2f7c04", "lossAccountId": "2d7e9a15-8b34-4f07-a5c1-6e0f3b9d472a", "active": true, "lastModifiedDate": "2026-08-14T09:12:33.000Z" } ], "pagination": { "hasMore": false } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `INVALID_CURSOR` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_CURSOR - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a depreciation policy Operation ID: `getDepreciationPolicy` Group: Accounting › Asset Management › Depreciation Policies [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getDepreciationPolicy) `GET /depreciation-policies/{id}` Read-only: Reads and returns; changes nothing. Retrieves a depreciation policy. To find a policy by its number or name, use `POST depreciation-policies/search`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the depreciation policy you want to retrieve. | ## Response `200` Success Under `data` (getDepreciationPolicyOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the depreciation policy. Example: `"b1d4c8f2-5a37-4e91-8c62-9f0a3d7e14b5"`. | | `depreciationPolicyNumber` | string | no | The depreciation policy number. Unique within the tenant. Example: 'DP-0007'. Example: `"DP-0007"`. | | `name` | string | yes | The name of the depreciation policy. Unique within the tenant. Example: `"Office Equipment — 5 Year Straight Line"`. | | `description` | string | no | A description of the depreciation policy. Example: `"Standard straight-line write-down for office equipment over five years."`. | | `depreciationMethod` | string | yes | The method used to spread depreciation over an asset's useful life. When a fixed asset of one of these types is set up, it takes this method. The method can then be changed on the fixed asset. Possible values: 'StraightLine' (equal amounts each period), 'Degressive' (declining balance at the fixed asset's annual `degressionRate`, switching to straight line when that gives a higher amount), 'Immediate' (the whole depreciable amount in the first period), 'Bypassed' (no depreciation schedule is created). How the method treats time is `depreciationConvention`. Example: `"StraightLine"`. | | `depreciationConvention` | string | no | How the method treats time: how the first and last periods are measured and how an amount is spread across periods. Possible values: 'FullMonth' (the in-service month counts as a full month; every period takes an equal share), 'MidMonth' (in service on or before the 15th: the full first month; after the 15th: depreciation starts the following month), 'MidYear' (year one and the year after the useful life each take half the annual amount), 'SixMonthRule' (the full annual amount in year one when in service more than six months of the fiscal year, otherwise half of it), 'ProrateFirstLast' (the partial first and last periods are prorated by days), 'ActualDays' (every period is weighted by its actual number of days). Present for 'StraightLine' (any value) and 'Degressive' (only 'FullMonth'); absent for 'Immediate' and 'Bypassed', which take none. Example: `"FullMonth"`. | | `depreciationInterval` | string | yes | The length of each depreciation period. Possible values: 'Monthly', 'Quarterly', 'Yearly'. Example: `"Monthly"`. | | `bookId` | string | no | The accounting book the depreciation policy applies to. A policy applies to one book only, so each book needs its own policies. See id on GET accounting/books/:id. Absent when the book no longer exists. Example: `"b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47"`. | | `assetAccountId` | string | no | The balance sheet account that holds the cost of fixed assets under this policy. Disposing of a fixed asset credits its acquisition value to this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"3a5f81c6-9d24-4b70-8e15-6c2b7f094d38"`. | | `accumulatedDepreciationAccountId` | string | no | The accumulated depreciation account, a contra-asset account. Recording depreciation credits this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"9e14d7b2-6c83-4f51-a027-8d3b5e199c61"`. | | `depreciationExpenseAccountId` | string | no | The depreciation expense account. Recording depreciation debits this account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"5c2b8a41-7e96-4d03-b158-2f7a9c4e60d5"`. | | `gainAccountId` | string | no | The account for gains when an asset under this policy is sold for more than its net book value. It is used only on books that keep gains and losses in separate accounts, such as HGB books. On such a sale, the asset's net book value and the sale amount go to this account. If the fixed asset has a `revenueAccountId`, the sale amount goes there instead. On most books, gainAccountId and lossAccountId are the same account, set in Lensing as one gain or loss account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"8f0b3d27-4a19-4c62-9e73-1b5d8a2f7c04"`. | | `lossAccountId` | string | no | The account for losses when an asset under this policy is disposed of. The asset's net book value goes to this account. On a sale, the sale amount also goes here, unless the fixed asset has a `revenueAccountId`. On books that keep gains and losses in separate accounts, a sale for more than the net book value goes to `gainAccountId` instead. On most books, gainAccountId and lossAccountId are the same account, set in Lensing as one gain or loss account. See id on GET accounting/accounts/:id. Absent when the account no longer exists. Example: `"2d7e9a15-8b34-4f07-a5c1-6e0f3b9d472a"`. | | `active` | boolean | no | Whether the depreciation policy is active. Only active policies are offered when a fixed asset type is chosen for a fixed asset. Depreciation already scheduled under an inactive policy is still recorded. Example: `true`. | | `lastModifiedDate` | date-time | yes | The date and time the depreciation policy last changed, in UTC. Format: YYYY-MM-DDTHH:mm:ss.sssZ. Example: `"2026-08-14T09:12:33.000Z"`. | ```json { "data": { "id": "b1d4c8f2-5a37-4e91-8c62-9f0a3d7e14b5", "depreciationPolicyNumber": "DP-0007", "name": "Office Equipment — 5 Year Straight Line", "description": "Standard straight-line write-down for office equipment over five years.", "depreciationMethod": "StraightLine", "depreciationConvention": "FullMonth", "depreciationInterval": "Monthly", "bookId": "b7c19e04-3f52-4a86-9d13-5e8f2a6c0b47", "assetAccountId": "3a5f81c6-9d24-4b70-8e15-6c2b7f094d38", "accumulatedDepreciationAccountId": "9e14d7b2-6c83-4f51-a027-8d3b5e199c61", "depreciationExpenseAccountId": "5c2b8a41-7e96-4d03-b158-2f7a9c4e60d5", "gainAccountId": "8f0b3d27-4a19-4c62-9e73-1b5d8a2f7c04", "lossAccountId": "2d7e9a15-8b34-4f07-a5c1-6e0f3b9d472a", "active": true, "lastModifiedDate": "2026-08-14T09:12:33.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Fixed Asset Types Group: Accounting › Asset Management [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/invFixedAssetTypes) Fixed asset types: the master data a fixed asset is created from, carrying the depreciation defaults it inherits — read one, or walk the collection with structured filters. A fixed asset type is master data: the template a fixed asset is created from. It carries the depreciation defaults — above all `defaultUsefulLife` — that a new asset inherits, and it is what `typeId` on a fixed asset points at. **Two read routes, one item schema.** `GET fixed-asset-types/{id}` reads one type and `GET fixed-asset-types` walks the collection with filters in the query string. Both return the same fields, so a caller can move between them without remapping anything. There is no `POST .../search` on this resource: every field it publishes is a URL-safe scalar, so the collection route can already express every query, and adding a search body would duplicate the contract in two places. **`name` is filterable here, unlike on most resources.** An asset type name is a classification label chosen by whoever configures the module — 'Machinery', 'Vehicles' — not a person, a counterparty or physical property, so it is not identifying data that has to stay out of a query string. It is exact-match only; there is no free-text search on this resource. **The hierarchy is one level as published here.** `groupId` and `groupName` name the FixedAssetTypeGroup a type belongs to. That group may itself have a parent, and this API does not walk it — a caller needing the full tree reads the groups themselves. **`defaultUsefulLife` is a count of periods, not an amount and not a duration.** It is read together with the depreciation interval on the asset that inherits it; the number alone does not say whether it means months or years. **Inactive types are returned by both routes.** `active` is on every response and filterable on the collection: a type is deactivated rather than deleted once assets reference it, and an integration reconciling historical assets still has to resolve the type they point at. Filter `active` when populating a picker. **Incremental sync** is `filter[lastModifiedDate][gte]=…&sort=lastModifiedDate` on the collection. It tracks the FixedAssetType row only: renaming the group a type belongs to does not move it. Every request carries an `Api-Version` header — `v1` — alongside the bearer token. It is required, with no default: a missing or empty header, or one naming a version this company does not have, is answered with 400; a route that exists but not in the requested version is answered with 404. One thing to know before writing error handling: a business failure such as `NOT_FOUND` arrives under HTTP 400, not 404, because a route cannot yet declare a status per diagnostic code. Branch on the `code` in the error body. ## Endpoints - [Fixed Asset Types](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/fixedAssetTypes.html): `GET /fixed-asset-types` - [Get Fixed Asset Type](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getFixedAssetType.html): `GET /fixed-asset-types/{id}` --- # Fixed Asset Types Operation ID: `fixedAssetTypes` Group: Accounting › Asset Management › Fixed Asset Types [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/fixedAssetTypes) `GET /fixed-asset-types` Read-only: Reads and returns; changes nothing. Fixed asset types — the master data a fixed asset is created from, carrying the depreciation defaults it inherits. Both active and inactive types are returned; filter active to populate a picker. [nav: @uicode:fixedAssetType?id={id}] The fixed asset type collection, with structured filtering, ordering, sparse field selection and cursor pagination in the query string. ``` GET fixed-asset-types?filter[active][eq]=true&filter[groupId][eq]=&sort=name ``` A response is `{ "data": [ … ], "pagination": { "hasMore": …, "nextCursor": … } }`. Page forward by passing the previous `nextCursor` as `page[cursor]`. Page size defaults to 25 and is capped at 100; a larger `page[size]` is clamped to 100 rather than rejected. **`name` is filterable and orderable here, unlike on most resources.** An asset type name is a classification label chosen by whoever configures the module — 'Machinery', 'Vehicles' — not a person, a counterparty, or physical property, so it is not identifying data that has to stay out of a query string. The filter is exact match; there is no free-text search on this resource. **Inactive types are returned.** A type is deactivated rather than deleted once assets reference it, so the collection carries both and `active` is filterable: filter `active` when populating a picker, and leave it off when resolving the type a historical asset points at. **Incremental sync.** `lastModifiedDate` is both filterable and orderable, which is what makes `filter[lastModifiedDate][gte]=…&sort=lastModifiedDate` a complete sync cursor — either half alone is not. It tracks the FixedAssetType row only, so renaming the group a type belongs to does not move it. **`defaultUsefulLife` is a count of periods, not an amount.** No field on this resource is money, so nothing here carries a currency. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `name` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `active` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `defaultUsefulLife` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `groupId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, name, defaultUsefulLife, lastModifiedDate - Selectable: id, name, active, defaultUsefulLife, groupId, groupName, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of fixedAssetTypesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Platform id (a UUID) of the fixed asset type — the public identifier of this record. Example: `"7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a"`. | | `name` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Display name of the fixed asset type, e.g. 'Machinery'. Filterable and orderable by exact match — a type name is a configuration label, not identifying data, so unlike most name fields it is admitted to the query string. There is no free-text matching on it. Example: `"Machinery"`. | | `active` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether the type is usable for business users. A type is deactivated rather than deleted once assets reference it, so this collection carries both; filter it to populate a picker. Absent on legacy rows that predate the column, which should be read as active. Example: `true`. | | `defaultUsefulLife` | number \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Number of depreciation periods a new asset of this type inherits as its useful life. A whole COUNT of periods, not an amount and not a duration: the depreciation interval that gives it units (monthly, quarterly, yearly) lives on the asset, so this number alone does not say whether it means months or years. Absent when the type sets no default. Example: `60`. | | `groupId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Platform id (a UUID) of the FixedAssetTypeGroup this type belongs to. Absent on an ungrouped type. That group may itself have a parent group, which this API does not walk. Example: `"2e8c14a9-6b73-4f52-9d01-8a3f7e0c5b26"`. | | `groupName` | string \| null | no | no | no | Display name of the group named by groupId, carried alongside it so the common grouping needs no second call. Returned but NOT filterable or orderable — filter groupId instead, so one question has one answer. Absent whenever groupId is. Example: `"Plant and Equipment"`. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Instant the fixed asset type record last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). Filterable and orderable together, which is what makes filter[lastModifiedDate][gte] plus sort=lastModifiedDate a complete incremental-sync cursor. NOTE it tracks the FixedAssetType row only: renaming the group a type belongs to does not move it. Example: `"2026-08-18T09:14:22.517Z"`. | ```json { "data": [ { "id": "7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a", "name": "Machinery", "active": true, "defaultUsefulLife": 60, "groupId": "2e8c14a9-6b73-4f52-9d01-8a3f7e0c5b26", "groupName": "Plant and Equipment", "lastModifiedDate": "2026-08-18T09:14:22.517Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Get Fixed Asset Type Operation ID: `getFixedAssetType` Group: Accounting › Asset Management › Fixed Asset Types [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getFixedAssetType) `GET /fixed-asset-types/{id}` Read-only: Reads and returns; changes nothing. Fetch a fixed asset type by id, with its default useful life and the group it belongs to Reads one fixed asset type by its id, returning the whole record: the display name, whether it is still usable, the default useful life a new asset inherits, and the group it belongs to. **Look-ups by any other key are filters, not routes.** The type name identifies a type just as well for a human, and it is not this path parameter — which is the platform UUID. Filter `name` on `GET fixed-asset-types` instead; it returns this same item schema. **An inactive type IS returned here.** A type is deactivated rather than deleted once assets reference it, and an integration resolving the type a historical asset points at still needs to read it. Check `active`. **`defaultUsefulLife` is a count of periods, not a duration.** The depreciation interval that gives it units lives on the asset that inherited it, so the number alone does not say whether it means months or years. A missing id answers `NOT_FOUND` rather than an empty payload — under HTTP 400, so branch on the `code`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | Platform id (a UUID) of the fixed asset type; bound from the :id path segment. A value that is not UUID-shaped cannot match any record and is answered with NOT_FOUND, the same as a well-formed id that matches nothing. | ## Response `200` Success Under `data` (getFixedAssetTypeOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Platform id (a UUID) of the fixed asset type — the public identifier of this record. Example: `"7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a"`. | | `name` | string | yes | Display name of the fixed asset type, e.g. 'Machinery'. Unique per tenant is NOT guaranteed by this contract; treat id as the identity. Example: `"Machinery"`. | | `active` | boolean | no | Whether the type is usable for business users. A type is deactivated rather than deleted once assets reference it, so an inactive type is still returned here and by the collection. Absent on legacy rows that predate the column, which should be read as active. Example: `true`. | | `defaultUsefulLife` | number | no | Number of depreciation periods a new asset of this type inherits as its useful life. A whole COUNT of periods, not an amount and not a duration: the depreciation interval that gives it units (monthly, quarterly, yearly) lives on the asset, so this number alone does not say whether it means months or years. Absent when the type sets no default. Example: `60`. | | `groupId` | string | no | Platform id (a UUID) of the FixedAssetTypeGroup this type belongs to. Absent on an ungrouped type. That group may itself have a parent group, which this API does not walk. Example: `"2e8c14a9-6b73-4f52-9d01-8a3f7e0c5b26"`. | | `groupName` | string | no | Display name of the group named by groupId, carried alongside it so the common grouping needs no second call. Absent whenever groupId is. Example: `"Plant and Equipment"`. | | `lastModifiedDate` | date-time | yes | Instant the fixed asset type record last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). NOTE it tracks the FixedAssetType row only: renaming the group a type belongs to does not move it. Example: `"2026-08-18T09:14:22.517Z"`. | ```json { "data": { "id": "7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a", "name": "Machinery", "active": true, "defaultUsefulLife": 60, "groupId": "2e8c14a9-6b73-4f52-9d01-8a3f7e0c5b26", "groupName": "Plant and Equipment", "lastModifiedDate": "2026-08-18T09:14:22.517Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Fixed Assets Group: Accounting › Asset Management [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/invFixedAssets) Fixed assets are the accounting records of the assets a legal entity capitalizes and depreciates. Some fields describe the asset itself rather than its accounting. They are `name`, `assetStatus`, `assetNumber`, `serialNumber`, `assetTag`, `acquisitionDate`, `departmentId`, and `businessUnitId`. The legal entity that owns the asset, `entityId`, is stored with it too. Imported fixed assets can already be partly depreciated, for example when assets are moved from another system. For these, `depreciationStartValueAmount` is the value that depreciation continues from, and `initialDepreciatedAmount` is the depreciation from before the import. A company can keep more than one set of books, for example one for local accounting rules and one for group reporting. Each fixed asset belongs to one book, because each book can depreciate the same asset differently. So an asset recorded in both books has two fixed asset records, one in each. For example, a forklift might be depreciated over eight years in the local book and over five years in the group book. Its two fixed assets have different depreciation, but the same `companyAssetId`, name, serial number, and the other fields that describe the asset itself. To find all the fixed assets for one asset, filter `GET fixed-assets` on `companyAssetId`. In Lensing, the fixed assets list shows one book at a time. The API returns the fixed assets of every book together, unless you filter on `bookId`. ## Endpoints - [List fixed assets](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/listFixedAssets.html): `GET /fixed-assets` - [Search fixed assets](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchFixedAssets.html): `POST /fixed-assets/search` - [Retrieve a fixed asset](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getFixedAsset.html): `GET /fixed-assets/{id}` --- # List fixed assets Operation ID: `listFixedAssets` Group: Accounting › Asset Management › Fixed Assets [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/listFixedAssets) `GET /fixed-assets` Read-only: Reads and returns; changes nothing. Lists fixed assets. Voided assets are not included, even if you filter `fixedAssetStatus` on 'voided'. Amounts are in each asset's own `currency` and are not converted. The exception is `soldAmountAmount`, which is in `soldAmountCurrency`. An asset recorded in more than one book has one fixed asset in each. Filter `bookId` before you add up amounts. A page has 25 fixed assets by default and at most 100. A larger `page[size]` is treated as 100. To include voided assets, use `POST fixed-assets/search`. To read a voided asset by its id, use `GET fixed-assets/:id`. To filter by `name`, `assetTag`, `assetNumber`, or `serialNumber`, use `POST fixed-assets/search`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fixedAssetNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fixedAssetStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `companyAssetId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `assetStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `acquisitionDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `departmentId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `businessUnitId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `typeId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `bookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `inServiceDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `depreciationMethod` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationInterval` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `depreciationStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `disposalDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, fixedAssetNumber, inServiceDate, acquisitionDate, acquisitionValueAmount, remainingValue, depreciatedAmount, disposalDate, lastModifiedDate - Selectable: id, fixedAssetNumber, fixedAssetStatus, companyAssetId, name, entityId, assetStatus, assetNumber, serialNumber, assetTag, acquisitionDate, departmentId, businessUnitId, typeId, typeName, bookId, currency, inServiceDate, depreciationMethod, depreciationInterval, depreciationStatus, degressionRate, acquisitionValueAmount, acquisitionValueCurrency, salvageValueAmount, salvageValueCurrency, depreciationStartValueAmount, depreciableAmount, depreciatedAmount, initialDepreciatedAmount, remainingValue, disposalDate, disposalAmount, disposalEntryId, disposalEntryNumber, soldAmountAmount, soldAmountCurrency, saleEntryId, saleEntryNumber, saleReceivingAccountId, revenueAccountId, saleDocumentId, saleDocumentNumber, vatPercentageId, valueAllocationId, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of listFixedAssetsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the fixed asset. Example: `"9d5b1f42-7c30-4e88-b1a6-2f0c7e34d915"`. | | `fixedAssetNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The fixed asset number. Unique within the tenant. Each book's fixed asset has its own number, unlike assetNumber, which is the same in every book. Example: 'FA-1'. Example: `"FA-000142"`. | | `fixedAssetStatus` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The status of the fixed asset. Possible values: 'active', 'voided'. Example: `"active"`. | | `companyAssetId` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The asset this fixed asset records. Fixed assets of the same asset in different books have the same companyAssetId. Example: `"3a7e5c81-9b24-4d0f-8e63-1c95af207b48"`. | | `name` | string \| null | no | no | no | The name of the asset. Example: `"Forklift - Warehouse 2"`. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The legal entity that owns the asset. See id on GET base/entities. Example: `"c41d8e07-5f62-4a93-b7d1-8e0364f5a2c9"`. | | `assetStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The physical status of the asset. Possible values: 'available', 'broken', 'inRepair', 'inUse', 'retired', 'disposed'. Disposing of an asset sets assetStatus to 'disposed' and leaves fixedAssetStatus as 'active'. Example: `"inUse"`. | | `assetNumber` | string \| null | no | no | no | The asset number. It is unique within the tenant, and all fixed assets of the same asset have the same number. Example: `"CA-0881"`. | | `serialNumber` | string \| null | no | no | no | The serial number of the asset. It is free text and does not have to be unique. Example: `"SN-77120934"`. | | `assetTag` | string \| null | no | no | no | The asset tag of the asset, such as the code on a label attached to it. It is free text. Example: `"WH2-FORK-03"`. | | `acquisitionDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the asset was acquired. Format: YYYY-MM-DD. Example: `"2024-03-11"`. | | `departmentId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The department the asset is assigned to. Absent when the asset has no department. See id on GET people/departments/:id. | | `businessUnitId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The business unit the asset is assigned to. Absent when the asset has no business unit. | | `typeId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The type of the fixed asset. Example: `"7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a"`. | | `typeName` | string \| null | no | no | no | The name of the fixed asset type. Example: 'Machinery'. Example: `"Machinery"`. | | `bookId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The accounting book the fixed asset is recorded in. See id on GET accounting/books/:id. Example: `"e0c37a94-2d15-4b86-9f42-5a7c81de03b6"`. | | `currency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The currency of the fixed asset. Format: ISO 4217, for example 'EUR'. Example: `"EUR"`. | | `inServiceDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the asset was placed in service. The depreciation schedule is calculated from this date. Format: YYYY-MM-DD. Example: `"2024-04-01"`. | | `depreciationMethod` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The depreciation method of the fixed asset. Possible values: 'StraightLine', 'StraightLineProRateFirstLast', 'Degressive', 'Immediate', 'Bypassed', 'StraightLineHalfMonth', 'ExactDays'. Example: `"StraightLine"`. | | `depreciationInterval` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The interval at which depreciation is recorded for the fixed asset. Possible values: 'Monthly', 'Quarterly', 'Yearly'. Example: `"Monthly"`. | | `depreciationStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The depreciation status of the fixed asset. Possible values: 'NotStarted', 'Ongoing', 'Completed', 'Disposed', 'Voided'. A fixed asset with no depreciation schedule also has the value 'Completed', so check depreciatedAmount before you treat a fixed asset as fully depreciated. Example: `"Ongoing"`. | | `degressionRate` | decimal \| null | no | no | no | The yearly rate of declining-balance depreciation, as a decimal fraction. For example, 0.2 is 20%. Used only when depreciationMethod is 'Degressive'. | | `acquisitionValueAmount` | decimal \| null | no | no | yes | The value the fixed asset was capitalized at, in the `acquisitionValueCurrency`. Example: `"42000"`. | | `acquisitionValueCurrency` | string \| null | no | no | no | The currency of acquisitionValueAmount. Format: ISO 4217, for example 'EUR'. | | `salvageValueAmount` | decimal \| null | no | no | no | The value the asset is expected to have at the end of its useful life (salvage value, also called residual value), in the `salvageValueCurrency`. Depreciation reduces the asset's net book value to this amount, not to zero. | | `salvageValueCurrency` | string \| null | no | no | no | The currency of salvageValueAmount. Format: ISO 4217, for example 'EUR'. | | `depreciationStartValueAmount` | decimal \| null | no | no | no | The value the fixed asset's depreciation started from, in the `currency`. Present only for imported fixed assets. | | `depreciableAmount` | decimal \| null | no | no | no | The amount that is depreciated over the useful life of the fixed asset, in the `currency`. Formula: depreciationStartValueAmount - salvageValueAmount, or when depreciationStartValueAmount is absent, acquisitionValueAmount - salvageValueAmount. Example: `"40000"`. | | `depreciatedAmount` | decimal \| null | no | no | yes | The total depreciation of the fixed asset, in the `currency`. Formula: initialDepreciatedAmount + the depreciation recorded on the depreciation schedule. Example: `"13333.33"`. | | `initialDepreciatedAmount` | decimal \| null | no | no | no | The depreciation the fixed asset had before it was imported, in the `currency`. Formula: acquisitionValueAmount - depreciationStartValueAmount, or 0 when depreciationStartValueAmount is absent. | | `remainingValue` | decimal \| null | no | no | yes | The net book value of the fixed asset, in the `currency`. Formula: acquisitionValueAmount - depreciatedAmount. It is 0 when fixedAssetStatus is 'voided' or assetStatus is 'retired' or 'disposed'. Example: `"28666.67"`. | | `disposalDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the asset was disposed of. Format: YYYY-MM-DD. Present only when the asset has been disposed of. | | `disposalAmount` | decimal \| null | no | no | no | The value written off when the asset was disposed of, in the `currency`. Present only when the asset has been disposed of. | | `disposalEntryId` | string \| null | no | no | no | The journal entry that records the disposal. Present only when the asset has been disposed of. See id on GET accounting/journal-entries/:id. | | `disposalEntryNumber` | string \| null | no | no | no | The number of the journal entry that records the disposal. Present only when the asset has been disposed of. Example: 'JE-4412'. | | `soldAmountAmount` | decimal \| null | no | no | no | The amount the asset was sold for, in the `soldAmountCurrency`. Present only when the asset was sold. | | `soldAmountCurrency` | string \| null | no | no | no | The currency of soldAmountAmount. Format: ISO 4217, for example 'EUR'. It can differ from currency. | | `saleEntryId` | string \| null | no | no | no | Platform id (a UUID) of the journal entry recording the sale. Absent when the asset wasn't sold. | | `saleEntryNumber` | string \| null | no | no | no | Human-readable number of the sale journal entry, carried alongside saleEntryId. | | `saleReceivingAccountId` | string \| null | no | no | no | The account the sale proceeds were received into. Present only when the asset was sold. See id on GET accounting/accounts/:id. | | `revenueAccountId` | string \| null | no | no | no | The revenue account for the sale of the asset. Present only when the asset was sold and a revenue account was chosen for the sale. See id on GET accounting/accounts/:id. | | `saleDocumentId` | string \| null | no | no | no | The invoice for the sale of the asset, created as a manual taxable document. Present only when the asset was sold. See id on GET tax-documents/manual-taxable-documents/:id. | | `saleDocumentNumber` | string \| null | no | no | no | The number of the invoice for the sale of the asset. Present only when the asset was sold. See taxDocumentNumber on GET tax-documents/manual-taxable-documents/:id. | | `vatPercentageId` | string \| null | no | no | no | The tax code applied to the sale of the asset. Present only when the asset was sold and a tax code was applied to the sale. See lines.id on GET accounting/tax-configurations/:id. | | `valueAllocationId` | string \| null | no | no | no | The value accounting tags set on the fixed asset. Absent when the fixed asset has no tags. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Instant the fixed asset record last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). NOTE it tracks the FixedAsset row only: renaming or reassigning the underlying CompanyAsset does not move it. Example: `"2026-08-18T09:14:22.517Z"`. | ```json { "data": [ { "id": "9d5b1f42-7c30-4e88-b1a6-2f0c7e34d915", "fixedAssetNumber": "FA-000142", "fixedAssetStatus": "active", "companyAssetId": "3a7e5c81-9b24-4d0f-8e63-1c95af207b48", "name": "Forklift - Warehouse 2", "entityId": "c41d8e07-5f62-4a93-b7d1-8e0364f5a2c9", "assetStatus": "inUse", "assetNumber": "CA-0881", "serialNumber": "SN-77120934", "assetTag": "WH2-FORK-03", "acquisitionDate": "2024-03-11", "departmentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "businessUnitId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "typeId": "7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a", "typeName": "Machinery", "bookId": "e0c37a94-2d15-4b86-9f42-5a7c81de03b6", "currency": "EUR", "inServiceDate": "2024-04-01", "depreciationMethod": "StraightLine", "depreciationInterval": "Monthly", "depreciationStatus": "Ongoing", "degressionRate": "19.99", "acquisitionValueAmount": "42000", "acquisitionValueCurrency": "", "salvageValueAmount": "19.99", "salvageValueCurrency": "", "depreciationStartValueAmount": "19.99", "depreciableAmount": "40000", "depreciatedAmount": "13333.33", "initialDepreciatedAmount": "19.99", "remainingValue": "28666.67", "disposalDate": "2026-01-15", "disposalAmount": "19.99", "disposalEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "disposalEntryNumber": "", "soldAmountAmount": "19.99", "soldAmountCurrency": "", "saleEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "saleEntryNumber": "", "saleReceivingAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "revenueAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "saleDocumentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "saleDocumentNumber": "", "vatPercentageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "valueAllocationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-08-18T09:14:22.517Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search fixed assets Operation ID: `searchFixedAssets` Group: Accounting › Asset Management › Fixed Assets [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchFixedAssets) `POST /fixed-assets/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches fixed assets, returning the same fields as `GET fixed-assets/:id` and `GET fixed-assets`. `name`, `assetTag`, `assetNumber`, and `serialNumber` can be filtered only on this endpoint. `name` and `assetTag` match any part of the value, and the match is case-insensitive. `assetNumber` and `serialNumber` must match exactly. Voided assets are included. To leave them out, set `fixedAssetStatuses` to ['active']. To find assets that were never disposed, set `disposed` to false. A `disposalDate` range can't find them, because an asset with no disposal date falls outside every range. If you send `disposed: false` together with a `disposalDate` range, no fixed assets are returned. The `acquisitionValueAmount` range compares only the number, not the currency. Amounts are not converted, so a range of 1,000 to 5,000 matches an asset worth 2,000 USD and one worth 2,000 JPY. To match amounts in one currency only, also filter on `currencies`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchFixedAssetsInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A fixed asset must match every filter you send. Omit to return all fixed assets. If you send an empty list for a filter, that filter is ignored. Example: `{"serialNumber":"SN-77120934"}`. | | `filters.ids` | array | no | Limits results to the fixed assets with these ids. The match is exact. | | `filters.fixedAssetNumber` | string | no | Limits results to the fixed asset with this fixed asset number. The match is exact and case-sensitive. Example: 'FA-1'. | | `filters.assetNumber` | string | no | Limits results to the fixed assets with this asset number. The match is exact and case-sensitive. All the fixed assets of the asset are returned, one for each book it is recorded in. | | `filters.serialNumber` | string | no | Limits results to fixed assets whose asset has this serial number. The match is exact and case-sensitive. | | `filters.name` | string | no | Limits results to fixed assets whose name contains this text. The match is case-insensitive, and % and _ match as ordinary characters. Spaces at the start and end are ignored. If the text is empty or only spaces, this filter is ignored. | | `filters.assetTag` | string | no | Limits results to fixed assets whose asset tag contains this text. The match is case-insensitive, and % and _ match as ordinary characters. Spaces at the start and end are ignored. If the text is empty or only spaces, this filter is ignored. | | `filters.entityIds` | array | no | Limits results to fixed assets whose asset belongs to any of these legal entities. The match is exact. See id on GET base/entities. | | `filters.bookIds` | array | no | Limits results to fixed assets in any of these accounting books. The match is exact. An asset recorded in more than one book has one fixed asset in each, so send one book before you add up amounts. See id on GET accounting/books/:id. | | `filters.typeIds` | array | no | Limits results to fixed assets of any of these types. The match is exact. | | `filters.departmentIds` | array | no | Limits results to fixed assets whose asset is assigned to any of these departments. The match is exact. See id on GET people/departments/:id. | | `filters.businessUnitIds` | array | no | Limits results to fixed assets whose asset is assigned to any of these business units. The match is exact. | | `filters.fixedAssetStatuses` | array | no | Limits results to fixed assets with any of these statuses. Accepted values: 'active', 'voided'. Send ['active'] to leave out voided fixed assets. | | `filters.depreciationStatuses` | array | no | Limits results to fixed assets with any of these depreciation statuses. Accepted values: 'NotStarted', 'Ongoing', 'Completed', 'Disposed', 'Voided'. 'Completed' also matches fixed assets that have no depreciation schedule. | | `filters.depreciationMethods` | array | no | Limits results to fixed assets with any of these depreciation methods. Accepted values: 'StraightLine', 'StraightLineProRateFirstLast', 'Degressive', 'Immediate', 'Bypassed', 'StraightLineHalfMonth', 'ExactDays'. | | `filters.depreciationIntervals` | array | no | Limits results to fixed assets with any of these depreciation intervals. Accepted values: 'Monthly', 'Quarterly', 'Yearly'. | | `filters.assetStatuses` | array | no | Limits results to fixed assets whose asset has any of these physical statuses. Accepted values: 'available', 'broken', 'inRepair', 'inUse', 'retired', 'disposed'. | | `filters.currencies` | array | no | Limits results to fixed assets in any of these currencies. Format: ISO 4217, for example 'EUR'. The match is exact and case-sensitive. | | `filters.inServiceDate` | object | no | The range of in-service dates to match. Send start, end, or both. | | `filters.inServiceDate.start` | date | no | Limits results to fixed assets placed in service on or after this date. Format: YYYY-MM-DD. | | `filters.inServiceDate.end` | date | no | Limits results to fixed assets placed in service on or before this date. Format: YYYY-MM-DD. | | `filters.acquisitionDate` | object | no | The range of acquisition dates to match. Send start, end, or both. | | `filters.acquisitionDate.start` | date | no | Limits results to fixed assets acquired on or after this date. Format: YYYY-MM-DD. | | `filters.acquisitionDate.end` | date | no | Limits results to fixed assets acquired on or before this date. Format: YYYY-MM-DD. | | `filters.disposalDate` | object | no | The range of disposal dates to match. Send start, end, or both. | | `filters.disposalDate.start` | date | no | Limits results to fixed assets disposed of on or after this date. Format: YYYY-MM-DD. | | `filters.disposalDate.end` | date | no | Limits results to fixed assets disposed of on or before this date. Format: YYYY-MM-DD. | | `filters.disposed` | boolean | no | When true, returns only fixed assets that have been disposed of. When false, returns only fixed assets that have not been disposed of. Omit to return both. | | `filters.acquisitionValueAmount` | object | no | The range of acquisition values to match. Each fixed asset's value is compared in its own `acquisitionValueCurrency`, without conversion. Send min, max, or both. | | `filters.acquisitionValueAmount.min` | decimal | no | Limits results to fixed assets with an acquisitionValueAmount of this value or more. | | `filters.acquisitionValueAmount.max` | decimal | no | Limits results to fixed assets with an acquisitionValueAmount of this value or less. | | `filters.lastModifiedDate` | object | no | The range of last-change times to match, in UTC. Send start, end, or both. | | `filters.lastModifiedDate.start` | string | no | Limits results to fixed assets that last changed at or after this time. Format: YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.lastModifiedDate.end` | string | no | Limits results to fixed assets that last changed at or before this time. Format: YYYY-MM-DDTHH:mm:ss.sssZ. | | `page` | object | no | The page of results to return. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's `pagination.nextCursor`. Omit on the first request. | | `page.size` | number | no | The number of fixed assets per page, between 1 and 200. Defaults to 50. A value above 200 is treated as 200, and a value below 1 as 1, not rejected. | ```json { "filters": { "serialNumber": "SN-77120934" }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchFixedAssetsOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The fixed assets on this page, most recently created first. An empty array when nothing matches. Example: `[{"id":"9d5b1f42-7c30-4e88-b1a6-2f0c7e34d915","fixedAssetNumber":"FA-000142","fixedAssetStatus":"active","companyAssetId":"3a7e5c81-9b24-4d0f-8e63-1c95af207b48","name":"Forklift - Warehouse 2","entityId":"c41d8e07-5f62-4a93-b7d1-8e0364f5a2c9","assetStatus":"inUse","assetNumber":"CA-0881","serialNumber":"SN-77120934","assetTag":"WH2-FORK-03","typeId":"7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a","typeName":"Machinery","currency":"EUR","inServiceDate":"2024-04-01","depreciationMethod":"StraightLine","depreciationInterval":"Monthly","depreciationStatus":"Ongoing","acquisitionValueAmount":"42000","depreciatedAmount":"13333.33","remainingValue":"28666.67","lastModifiedDate":"2026-08-18T09:14:22.517Z"}]`. | | `items[].id` | string | yes | The unique identifier for the fixed asset. | | `items[].fixedAssetNumber` | string | no | The fixed asset number. Unique within the tenant. Each book's fixed asset has its own number, unlike assetNumber, which is the same in every book. Example: 'FA-1'. | | `items[].fixedAssetStatus` | string | yes | The status of the fixed asset. Possible values: 'active', 'voided'. Voided fixed assets are returned here, unlike on `GET fixed-assets`. | | `items[].companyAssetId` | string | yes | The asset this fixed asset records. Fixed assets of the same asset in different books have the same companyAssetId. | | `items[].name` | string | no | The name of the asset. | | `items[].entityId` | string | no | The legal entity that owns the asset. See id on GET base/entities. | | `items[].assetStatus` | string | no | The physical status of the asset. Possible values: 'available', 'broken', 'inRepair', 'inUse', 'retired', 'disposed'. Disposing of an asset sets assetStatus to 'disposed' and leaves fixedAssetStatus as 'active'. | | `items[].assetNumber` | string | no | The asset number. It is unique within the tenant, and all fixed assets of the same asset have the same number. | | `items[].serialNumber` | string | no | The serial number of the asset. It is free text and does not have to be unique. | | `items[].assetTag` | string | no | The asset tag of the asset, such as the code on a label attached to it. It is free text. | | `items[].acquisitionDate` | date | no | The date the asset was acquired. Format: YYYY-MM-DD. | | `items[].departmentId` | string | no | The department the asset is assigned to. Absent when the asset has no department. See id on GET people/departments/:id. | | `items[].businessUnitId` | string | no | The business unit the asset is assigned to. Absent when the asset has no business unit. | | `items[].typeId` | string | no | The type of the fixed asset. | | `items[].typeName` | string | no | The name of the fixed asset type. Example: 'Machinery'. | | `items[].bookId` | string | no | The accounting book the fixed asset is recorded in. See id on GET accounting/books/:id. | | `items[].currency` | string | no | The currency of the fixed asset. Format: ISO 4217, for example 'EUR'. | | `items[].inServiceDate` | date | no | The date the asset was placed in service. The depreciation schedule is calculated from this date. Format: YYYY-MM-DD. | | `items[].depreciationMethod` | string | no | The depreciation method of the fixed asset. Possible values: 'StraightLine', 'StraightLineProRateFirstLast', 'Degressive', 'Immediate', 'Bypassed', 'StraightLineHalfMonth', 'ExactDays'. | | `items[].depreciationInterval` | string | no | The interval at which depreciation is recorded for the fixed asset. Possible values: 'Monthly', 'Quarterly', 'Yearly'. | | `items[].depreciationStatus` | string | no | The depreciation status of the fixed asset. Possible values: 'NotStarted', 'Ongoing', 'Completed', 'Disposed', 'Voided'. A fixed asset with no depreciation schedule also has the value 'Completed', so check depreciatedAmount before you treat a fixed asset as fully depreciated. | | `items[].degressionRate` | decimal | no | The yearly rate of declining-balance depreciation, as a decimal fraction. For example, 0.2 is 20%. Used only when depreciationMethod is 'Degressive'. | | `items[].acquisitionValueAmount` | decimal | no | The value the fixed asset was capitalized at, in the `acquisitionValueCurrency`. | | `items[].acquisitionValueCurrency` | string | no | The currency of acquisitionValueAmount. Format: ISO 4217, for example 'EUR'. | | `items[].salvageValueAmount` | decimal | no | The value the asset is expected to have at the end of its useful life (salvage value, also called residual value), in the `salvageValueCurrency`. Depreciation reduces the asset's net book value to this amount, not to zero. | | `items[].salvageValueCurrency` | string | no | The currency of salvageValueAmount. Format: ISO 4217, for example 'EUR'. | | `items[].depreciationStartValueAmount` | decimal | no | The value the fixed asset's depreciation started from, in the `currency`. Present only for imported fixed assets. | | `items[].depreciableAmount` | decimal | no | The amount that is depreciated over the useful life of the fixed asset, in the `currency`. Formula: depreciationStartValueAmount - salvageValueAmount, or when depreciationStartValueAmount is absent, acquisitionValueAmount - salvageValueAmount. | | `items[].depreciatedAmount` | decimal | no | The total depreciation of the fixed asset, in the `currency`. Formula: initialDepreciatedAmount + the depreciation recorded on the depreciation schedule. | | `items[].initialDepreciatedAmount` | decimal | no | The depreciation the fixed asset had before it was imported, in the `currency`. Formula: acquisitionValueAmount - depreciationStartValueAmount, or 0 when depreciationStartValueAmount is absent. | | `items[].remainingValue` | decimal | no | The net book value of the fixed asset, in the `currency`. Formula: acquisitionValueAmount - depreciatedAmount. It is 0 when fixedAssetStatus is 'voided' or assetStatus is 'retired' or 'disposed'. | | `items[].disposalDate` | date | no | The date the asset was disposed of. Format: YYYY-MM-DD. Present only when the asset has been disposed of. | | `items[].disposalAmount` | decimal | no | The value written off when the asset was disposed of, in the `currency`. Present only when the asset has been disposed of. | | `items[].disposalEntryId` | string | no | The journal entry that records the disposal. Present only when the asset has been disposed of. See id on GET accounting/journal-entries/:id. | | `items[].disposalEntryNumber` | string | no | The number of the journal entry that records the disposal. Present only when the asset has been disposed of. Example: 'JE-4412'. | | `items[].soldAmountAmount` | decimal | no | The amount the asset was sold for, in the `soldAmountCurrency`. Present only when the asset was sold. | | `items[].soldAmountCurrency` | string | no | The currency of soldAmountAmount. Format: ISO 4217, for example 'EUR'. It can differ from currency. | | `items[].saleEntryId` | string | no | Platform id (a UUID) of the journal entry recording the sale. Absent when the asset wasn't sold. | | `items[].saleEntryNumber` | string | no | Human-readable number of the sale journal entry, carried alongside saleEntryId. | | `items[].saleReceivingAccountId` | string | no | The account the sale proceeds were received into. Present only when the asset was sold. See id on GET accounting/accounts/:id. | | `items[].revenueAccountId` | string | no | The revenue account for the sale of the asset. Present only when the asset was sold and a revenue account was chosen for the sale. See id on GET accounting/accounts/:id. | | `items[].saleDocumentId` | string | no | The invoice for the sale of the asset, created as a manual taxable document. Present only when the asset was sold. See id on GET tax-documents/manual-taxable-documents/:id. | | `items[].saleDocumentNumber` | string | no | The number of the invoice for the sale of the asset. Present only when the asset was sold. See taxDocumentNumber on GET tax-documents/manual-taxable-documents/:id. | | `items[].vatPercentageId` | string | no | The tax code applied to the sale of the asset. Present only when the asset was sold and a tax code was applied to the sale. See lines.id on GET accounting/tax-configurations/:id. | | `items[].valueAllocationId` | string | no | The value accounting tags set on the fixed asset. Absent when the fixed asset has no tags. | | `items[].lastModifiedDate` | date-time | yes | Instant the fixed asset record last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). Always present — the column is set on every row, and R2.4.1 wants one optionality across all three read entry points. NOTE it tracks the FixedAsset row only: renaming or reassigning the underlying CompanyAsset does not move it. | | `pagination` | object | yes | Whether more fixed assets match, and the cursor for the next page. Example: `{"hasMore":false}`. | | `pagination.hasMore` | boolean | yes | Whether more fixed assets match after this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as `page.cursor`. Absent on the last page. | ```json { "data": { "items": [ { "id": "9d5b1f42-7c30-4e88-b1a6-2f0c7e34d915", "fixedAssetNumber": "FA-000142", "fixedAssetStatus": "active", "companyAssetId": "3a7e5c81-9b24-4d0f-8e63-1c95af207b48", "name": "Forklift - Warehouse 2", "entityId": "c41d8e07-5f62-4a93-b7d1-8e0364f5a2c9", "assetStatus": "inUse", "assetNumber": "CA-0881", "serialNumber": "SN-77120934", "assetTag": "WH2-FORK-03", "typeId": "7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a", "typeName": "Machinery", "currency": "EUR", "inServiceDate": "2024-04-01", "depreciationMethod": "StraightLine", "depreciationInterval": "Monthly", "depreciationStatus": "Ongoing", "acquisitionValueAmount": "42000", "depreciatedAmount": "13333.33", "remainingValue": "28666.67", "lastModifiedDate": "2026-08-18T09:14:22.517Z" } ], "pagination": { "hasMore": false } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `INVALID_CURSOR` | 400 | | `INVALID_TIMESTAMP` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_CURSOR, INVALID_TIMESTAMP - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a fixed asset Operation ID: `getFixedAsset` Group: Accounting › Asset Management › Fixed Assets [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getFixedAsset) `GET /fixed-assets/{id}` Read-only: Reads and returns; changes nothing. Retrieves a fixed asset, including a voided one. `GET fixed-assets` does not return voided assets. A voided asset has a `fixedAssetStatus` of 'voided'. A `depreciationStatus` of 'Completed' can also mean the asset has no depreciation schedule. Check `depreciatedAmount` before you treat an asset as fully depreciated. Amounts are in the asset's own `currency` and are not converted. The exception is `soldAmountAmount`, which is in `soldAmountCurrency`. To find a fixed asset by its fixed asset number, asset number, or serial number, use `POST fixed-assets/search`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the fixed asset you want to retrieve. | ## Response `200` Success Under `data` (getFixedAssetOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the fixed asset. Example: `"9d5b1f42-7c30-4e88-b1a6-2f0c7e34d915"`. | | `fixedAssetNumber` | string | no | The fixed asset number. Unique within the tenant. Each book's fixed asset has its own number, unlike assetNumber, which is the same in every book. Example: 'FA-1'. Example: `"FA-000142"`. | | `fixedAssetStatus` | string | yes | The status of the fixed asset. Possible values: 'active', 'voided'. Voided fixed assets are returned here, unlike on `GET fixed-assets`. Example: `"active"`. | | `companyAssetId` | string | yes | The asset this fixed asset records. Fixed assets of the same asset in different books have the same companyAssetId. Example: `"3a7e5c81-9b24-4d0f-8e63-1c95af207b48"`. | | `name` | string | no | The name of the asset. Example: `"Forklift - Warehouse 2"`. | | `entityId` | string | no | The legal entity that owns the asset. See id on GET base/entities. Example: `"c41d8e07-5f62-4a93-b7d1-8e0364f5a2c9"`. | | `assetStatus` | string | no | The physical status of the asset. Possible values: 'available', 'broken', 'inRepair', 'inUse', 'retired', 'disposed'. Disposing of an asset sets assetStatus to 'disposed' and leaves fixedAssetStatus as 'active'. Example: `"inUse"`. | | `assetNumber` | string | no | The asset number. It is unique within the tenant, and all fixed assets of the same asset have the same number. Example: `"CA-0881"`. | | `serialNumber` | string | no | The serial number of the asset. It is free text and does not have to be unique. Example: `"SN-77120934"`. | | `assetTag` | string | no | The asset tag of the asset, such as the code on a label attached to it. It is free text. Example: `"WH2-FORK-03"`. | | `acquisitionDate` | date | no | The date the asset was acquired. Format: YYYY-MM-DD. Example: `"2024-03-11"`. | | `departmentId` | string | no | The department the asset is assigned to. Absent when the asset has no department. See id on GET people/departments/:id. Example: `"5c8a2b70-1e94-4f37-8d06-b3e75f1290ac"`. | | `businessUnitId` | string | no | The business unit the asset is assigned to. Absent when the asset has no business unit. | | `typeId` | string | no | The type of the fixed asset. Example: `"7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a"`. | | `typeName` | string | no | The name of the fixed asset type. Example: 'Machinery'. Example: `"Machinery"`. | | `bookId` | string | no | The accounting book the fixed asset is recorded in. See id on GET accounting/books/:id. Example: `"e0c37a94-2d15-4b86-9f42-5a7c81de03b6"`. | | `currency` | string | no | The currency of the fixed asset. Format: ISO 4217, for example 'EUR'. Example: `"EUR"`. | | `inServiceDate` | date | no | The date the asset was placed in service. The depreciation schedule is calculated from this date. Format: YYYY-MM-DD. Example: `"2024-04-01"`. | | `depreciationMethod` | string | no | The depreciation method of the fixed asset. Possible values: 'StraightLine', 'StraightLineProRateFirstLast', 'Degressive', 'Immediate', 'Bypassed', 'StraightLineHalfMonth', 'ExactDays'. Example: `"StraightLine"`. | | `depreciationInterval` | string | no | The interval at which depreciation is recorded for the fixed asset. Possible values: 'Monthly', 'Quarterly', 'Yearly'. Example: `"Monthly"`. | | `depreciationStatus` | string | no | The depreciation status of the fixed asset. Possible values: 'NotStarted', 'Ongoing', 'Completed', 'Disposed', 'Voided'. A fixed asset with no depreciation schedule also has the value 'Completed', so check depreciatedAmount before you treat a fixed asset as fully depreciated. Example: `"Ongoing"`. | | `degressionRate` | decimal | no | The yearly rate of declining-balance depreciation, as a decimal fraction. For example, 0.2 is 20%. Used only when depreciationMethod is 'Degressive'. | | `acquisitionValueAmount` | decimal | no | The value the fixed asset was capitalized at, in the `acquisitionValueCurrency`. Example: `"42000"`. | | `acquisitionValueCurrency` | string | no | The currency of acquisitionValueAmount. Format: ISO 4217, for example 'EUR'. Example: `"EUR"`. | | `salvageValueAmount` | decimal | no | The value the asset is expected to have at the end of its useful life (salvage value, also called residual value), in the `salvageValueCurrency`. Depreciation reduces the asset's net book value to this amount, not to zero. Example: `"2000"`. | | `salvageValueCurrency` | string | no | The currency of salvageValueAmount. Format: ISO 4217, for example 'EUR'. Example: `"EUR"`. | | `depreciationStartValueAmount` | decimal | no | The value the fixed asset's depreciation started from, in the `currency`. Present only for imported fixed assets. | | `depreciableAmount` | decimal | no | The amount that is depreciated over the useful life of the fixed asset, in the `currency`. Formula: depreciationStartValueAmount - salvageValueAmount, or when depreciationStartValueAmount is absent, acquisitionValueAmount - salvageValueAmount. Example: `"40000"`. | | `depreciatedAmount` | decimal | no | The total depreciation of the fixed asset, in the `currency`. Formula: initialDepreciatedAmount + the depreciation recorded on the depreciation schedule. Example: `"13333.33"`. | | `initialDepreciatedAmount` | decimal | no | The depreciation the fixed asset had before it was imported, in the `currency`. Formula: acquisitionValueAmount - depreciationStartValueAmount, or 0 when depreciationStartValueAmount is absent. | | `remainingValue` | decimal | no | The net book value of the fixed asset, in the `currency`. Formula: acquisitionValueAmount - depreciatedAmount. It is 0 when fixedAssetStatus is 'voided' or assetStatus is 'retired' or 'disposed'. Example: `"28666.67"`. | | `disposalDate` | date | no | The date the asset was disposed of. Format: YYYY-MM-DD. Present only when the asset has been disposed of. | | `disposalAmount` | decimal | no | The value written off when the asset was disposed of, in the `currency`. Present only when the asset has been disposed of. | | `disposalEntryId` | string | no | The journal entry that records the disposal. Present only when the asset has been disposed of. See id on GET accounting/journal-entries/:id. | | `disposalEntryNumber` | string | no | The number of the journal entry that records the disposal. Present only when the asset has been disposed of. Example: 'JE-4412'. | | `soldAmountAmount` | decimal | no | The amount the asset was sold for, in the `soldAmountCurrency`. Present only when the asset was sold. | | `soldAmountCurrency` | string | no | The currency of soldAmountAmount. Format: ISO 4217, for example 'EUR'. It can differ from currency. | | `saleEntryId` | string | no | Platform id (a UUID) of the journal entry recording the sale. Absent when the asset wasn't sold. | | `saleEntryNumber` | string | no | Human-readable number of the sale journal entry, carried alongside saleEntryId. | | `saleReceivingAccountId` | string | no | The account the sale proceeds were received into. Present only when the asset was sold. See id on GET accounting/accounts/:id. | | `revenueAccountId` | string | no | The revenue account for the sale of the asset. Present only when the asset was sold and a revenue account was chosen for the sale. See id on GET accounting/accounts/:id. | | `saleDocumentId` | string | no | The invoice for the sale of the asset, created as a manual taxable document. Present only when the asset was sold. See id on GET tax-documents/manual-taxable-documents/:id. | | `saleDocumentNumber` | string | no | The number of the invoice for the sale of the asset. Present only when the asset was sold. See taxDocumentNumber on GET tax-documents/manual-taxable-documents/:id. | | `vatPercentageId` | string | no | The tax code applied to the sale of the asset. Present only when the asset was sold and a tax code was applied to the sale. See lines.id on GET accounting/tax-configurations/:id. | | `valueAllocationId` | string | no | The value accounting tags set on the fixed asset. Absent when the fixed asset has no tags. | | `lastModifiedDate` | date-time | yes | Instant the fixed asset record last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). Always present — the column is set on every row, and R2.4.1 wants one optionality across all three read entry points. NOTE it tracks the FixedAsset row only: renaming or reassigning the underlying CompanyAsset does not move it. Example: `"2026-08-18T09:14:22.517Z"`. | ```json { "data": { "id": "9d5b1f42-7c30-4e88-b1a6-2f0c7e34d915", "fixedAssetNumber": "FA-000142", "fixedAssetStatus": "active", "companyAssetId": "3a7e5c81-9b24-4d0f-8e63-1c95af207b48", "name": "Forklift - Warehouse 2", "entityId": "c41d8e07-5f62-4a93-b7d1-8e0364f5a2c9", "assetStatus": "inUse", "assetNumber": "CA-0881", "serialNumber": "SN-77120934", "assetTag": "WH2-FORK-03", "acquisitionDate": "2024-03-11", "departmentId": "5c8a2b70-1e94-4f37-8d06-b3e75f1290ac", "businessUnitId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "typeId": "7b2f9d13-4c68-4e51-a0d7-6f39c58e2b4a", "typeName": "Machinery", "bookId": "e0c37a94-2d15-4b86-9f42-5a7c81de03b6", "currency": "EUR", "inServiceDate": "2024-04-01", "depreciationMethod": "StraightLine", "depreciationInterval": "Monthly", "depreciationStatus": "Ongoing", "degressionRate": "19.99", "acquisitionValueAmount": "42000", "acquisitionValueCurrency": "EUR", "salvageValueAmount": "2000", "salvageValueCurrency": "EUR", "depreciationStartValueAmount": "19.99", "depreciableAmount": "40000", "depreciatedAmount": "13333.33", "initialDepreciatedAmount": "19.99", "remainingValue": "28666.67", "disposalDate": "2026-01-15", "disposalAmount": "19.99", "disposalEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "disposalEntryNumber": "", "soldAmountAmount": "19.99", "soldAmountCurrency": "", "saleEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "saleEntryNumber": "", "saleReceivingAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "revenueAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "saleDocumentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "saleDocumentNumber": "", "vatPercentageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "valueAllocationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-08-18T09:14:22.517Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Books Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingBooks) An accounting book is a self-contained ledger within the tenant, to which journal entries, accounts, and legal entities are assigned. ## Endpoints - [List accounting books](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/books.html): `GET /accounting/books` - [List an accounting book's legal entities](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/listBookEntityAssignments.html): `GET /accounting/books/{bookId}/entity-assignments` - [Retrieve an accounting book](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getBook.html): `GET /accounting/books/{id}` --- # List accounting books Operation ID: `books` Group: Accounting › Books [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/books) `GET /accounting/books` Read-only: Reads and returns; changes nothing. Lists accounting books. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `type` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountingStandard` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `consolidation` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `baseBookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, name, type, accountingStandard, consolidation, lastModifiedDate - Selectable: id, name, type, accountingStandard, consolidation, description, baseBookId, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of booksRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the accounting book. | | `name` | string | no | no | yes | The display name of the accounting book. | | `type` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The kind of accounting book. Possible values: 'full', 'adjustment'. An adjustment book adjusts the full book named in baseBookId. | | `accountingStandard` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The accounting standard the book follows, for example 'IFRS' or 'US GAAP'. Absent when none is set. | | `consolidation` | boolean | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the book consolidates other books or entities. | | `description` | string \| null | no | no | no | A free-form description of the accounting book. Absent when none is set. | | `baseBookId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The full book this adjustment book adjusts. Absent for a full book. See id on GET accounting/books/:id. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the accounting book record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "type": "", "accountingStandard": "", "consolidation": true, "description": "", "baseBookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # List an accounting book's legal entities Operation ID: `listBookEntityAssignments` Group: Accounting › Books [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/listBookEntityAssignments) `GET /accounting/books/{bookId}/entity-assignments` Read-only: Reads and returns; changes nothing. Lists an accounting book's legal entities. Each legal entity comes with the chart of accounts, fiscal calendar, and currency it uses in this book. This endpoint doesn't follow the standard collection rules in Pagination and querying: - Page with `pageSize` and `page`, not `page[size]` and `page[cursor]`. - The assignments are in `data.items`, and the next page's cursor is in `data.pagination.nextCursor`. - There are no filters. - There is no `sort` or `fields`. This endpoint ignores any query parameter it doesn't declare, and returns no error. A request that follows the standard rules gets the first page, unfiltered, every time. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `bookId` | path | string | yes | The unique identifier for the accounting book whose assignments to return. | | `page` | query | string | no | The cursor from the previous response's data.pagination.nextCursor. Omit for the first page. | | `pageSize` | query | string | no | The number of assignments per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ## Response `200` Success Under `data` (listBookEntityAssignmentsOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The book's entity assignments, one per legal entity that posts to it. An empty array when the book has none. | | `items[].id` | string | yes | The unique identifier for the entity assignment. | | `items[].bookId` | string | yes | The accounting book this assignment belongs to, the same book named on the path. See id on GET accounting/books/:id. | | `items[].bookName` | string | no | The display name of the accounting book. | | `items[].entityId` | string | yes | The legal entity assigned to the book. See id on GET base/entities. | | `items[].entityName` | string | no | The name of the assigned legal entity. | | `items[].chartOfAccountsId` | string | no | The chart of accounts used for this entity on this book. Absent when none is assigned. See id on GET accounting/chart-of-accounts/:id. | | `items[].chartOfAccountsName` | string | no | The name of the chart of accounts. Absent when none is assigned. | | `items[].fiscalCalendarId` | string | no | The fiscal calendar used for this entity on this book. Absent when none is assigned. See id on GET accounting/fiscal-calendars/:id. | | `items[].fiscalCalendarName` | string | no | The name of the fiscal calendar. Absent when none is assigned. | | `items[].currency` | string | no | The currency for this entity on this book. Format ISO 4217, for example 'USD'. Absent when none is set. | | `items[].lastModifiedDate` | date-time | yes | The date and time the entity assignment last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as the page query parameter. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more assignments remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "chartOfAccountsId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "chartOfAccountsName": "", "fiscalCalendarId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fiscalCalendarName": "", "currency": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `BOOK_NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: BOOK_NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an accounting book Operation ID: `getBook` Group: Accounting › Books [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getBook) `GET /accounting/books/{id}` Read-only: Reads and returns; changes nothing. Retrieves an accounting book. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier for the accounting book to return. | ## Response `200` Success Under `data` (getBookOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the accounting book. | | `name` | string | yes | The display name of the accounting book. | | `type` | string | yes | The kind of accounting book. Possible values: 'full', 'adjustment'. An adjustment book adjusts the full book named in baseBookId. | | `accountingStandard` | string | no | The accounting standard the book follows, for example 'IFRS' or 'US GAAP'. Absent when none is set. | | `consolidation` | boolean | yes | Whether the book consolidates other books or entities. | | `description` | string | no | A free-form description of the accounting book. Absent when none is set. | | `baseBookId` | string | no | The full book this adjustment book adjusts. Absent for a full book. See id on GET accounting/books/:id. | | `lastModifiedDate` | date-time | yes | The date and time the accounting book record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "type": "", "accountingStandard": "", "consolidation": true, "description": "", "baseBookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Charts of Accounts Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingChartsOfAccounts) A chart of accounts is the numbering scheme that organizes a legal entity's general ledger accounts. ## Endpoints - [List charts of accounts](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/chartsOfAccounts.html): `GET /accounting/chart-of-accounts` - [Search charts of accounts](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchChartOfAccounts.html): `POST /accounting/chart-of-accounts/search` - [Retrieve a chart of accounts](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getChartOfAccounts.html): `GET /accounting/chart-of-accounts/{id}` --- # List charts of accounts Operation ID: `chartsOfAccounts` Group: Accounting › Charts of Accounts [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/chartsOfAccounts) `GET /accounting/chart-of-accounts` Read-only: Reads and returns; changes nothing. Lists charts of accounts. By default, the list is ordered by name. To match on name or description, use POST accounting/chart-of-accounts/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountNumbering` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `accountNumberingDigits` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, name, accountNumbering, accountNumberingDigits, lastModifiedDate - Selectable: id, name, accountNumbering, accountNumberingDigits, description, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of chartsOfAccountsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the chart of accounts. | | `name` | string | no | no | yes | The display name of the chart of accounts. | | `accountNumbering` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How account numbers are assigned within this chart of accounts. Possible values: 'hierarchical' (numbers follow the account's position in the hierarchy), 'manual' (numbers are assigned by hand). | | `accountNumberingDigits` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The total width of an account number, which caps how deep the hierarchy can go. Possible values: 'sixDigits' (up to 4 levels), 'sevenDigits' (5), 'eightDigits' (6), 'nineDigits' (7), 'tenDigits' (8). Absent when no width was set. A width and the numbering strategy are independent, so check this field directly rather than inferring it from accountNumbering. | | `description` | string \| null | no | no | no | A free-form description of the chart of accounts. Absent when none was set. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the chart-of-accounts record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "accountNumbering": "", "accountNumberingDigits": "", "description": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search charts of accounts Operation ID: `searchChartOfAccounts` Group: Accounting › Charts of Accounts [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchChartOfAccounts) `POST /accounting/chart-of-accounts/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches charts of accounts. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchChartOfAccountsInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A chart of accounts must match every filter you send. Omit to return all charts of accounts. | | `filters.id` | string | no | Limits results to the chart of accounts with this identifier. The match is exact. A value that is not a valid identifier matches nothing. | | `filters.name` | string | no | Limits results to charts of accounts whose name contains this text. The match is case-insensitive, and '%' and '_' match themselves rather than acting as wildcards. An empty or blank value matches nothing. | | `filters.description` | string | no | Limits results to charts of accounts whose description contains this text. The match is case-insensitive. | | `filters.accountNumbering` | string | no | Limits results to charts of accounts with this numbering strategy. Possible values: 'hierarchical', 'manual'. Any other value matches nothing. | | `filters.accountNumberingDigits` | string | no | Limits results to charts of accounts with this account-number width. Possible values: 'sixDigits', 'sevenDigits', 'eightDigits', 'nineDigits', 'tenDigits'. Any other value matches nothing. A width and the numbering strategy are independent. | | `page` | object | no | Pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit on the first request. | | `page.size` | number | no | The number of charts of accounts per page, between 1 and 200. Defaults to 50. A value outside that range fails with INVALID_PAGE_SIZE rather than being adjusted. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "description": "", "accountNumbering": "", "accountNumberingDigits": "" }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchChartOfAccountsOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The charts of accounts that match, ordered by name. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the chart of accounts. | | `items[].name` | string | yes | The display name of the chart of accounts. | | `items[].accountNumbering` | string | yes | How account numbers are assigned within this chart of accounts. Possible values: 'hierarchical' (numbers follow the account's position in the hierarchy), 'manual' (numbers are assigned by hand). | | `items[].accountNumberingDigits` | string | no | The total width of an account number, which caps how deep the hierarchy can go. Possible values: 'sixDigits' (up to 4 levels), 'sevenDigits' (5), 'eightDigits' (6), 'nineDigits' (7), 'tenDigits' (8). Absent when no width was set. A width and the numbering strategy are independent, so check this field directly rather than inferring it from accountNumbering. | | `items[].description` | string | no | A free-form description of the chart of accounts. Absent when none was set. | | `items[].lastModifiedDate` | date-time | yes | The date and time the chart-of-accounts record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "accountNumbering": "", "accountNumberingDigits": "", "description": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `INVALID_PAGE_SIZE` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_PAGE_SIZE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a chart of accounts Operation ID: `getChartOfAccounts` Group: Accounting › Charts of Accounts [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getChartOfAccounts) `GET /accounting/chart-of-accounts/{id}` Read-only: Reads and returns; changes nothing. Retrieves a chart of accounts. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier for the chart of accounts to return. | ## Response `200` Success Under `data` (getChartOfAccountsOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the chart of accounts. | | `name` | string | yes | The display name of the chart of accounts. | | `accountNumbering` | string | yes | How account numbers are assigned within this chart of accounts. Possible values: 'hierarchical' (numbers follow the account's position in the hierarchy), 'manual' (numbers are assigned by hand). | | `accountNumberingDigits` | string | no | The total width of an account number, which caps how deep the hierarchy can go. Possible values: 'sixDigits' (up to 4 levels), 'sevenDigits' (5), 'eightDigits' (6), 'nineDigits' (7), 'tenDigits' (8). Absent when no width was set. A width and the numbering strategy are independent, so check this field directly rather than inferring it from accountNumbering. | | `description` | string | no | A free-form description of the chart of accounts. Absent when none was set. | | `lastModifiedDate` | date-time | yes | The date and time the chart-of-accounts record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "accountNumbering": "", "accountNumberingDigits": "", "description": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Credit Memos Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingCreditMemos) Credit memos are amounts a legal entity has credited back to a customer. ## Endpoints - [List credit memos](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/creditMemos.html): `GET /accounting/credit-memos` - [Create a credit memo](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createCreditMemo.html): `POST /accounting/credit-memos` - [Search credit memos](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchCreditMemo.html): `POST /accounting/credit-memos/search` - [Retrieve a credit memo](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getCreditMemo.html): `GET /accounting/credit-memos/{id}` - [Update a credit memo](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateCreditMemo.html): `PATCH /accounting/credit-memos/{id}` - [Apply a credit memo to invoices](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/applyCreditMemo.html): `POST /accounting/credit-memos/{id}/apply` - [Unapply a credit memo from invoices](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/unapplyCreditMemo.html): `POST /accounting/credit-memos/{id}/unapply` --- # List credit memos Operation ID: `creditMemos` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/creditMemos) `GET /accounting/credit-memos` Read-only: Reads and returns; changes nothing. Lists credit memos. To filter by credit memo number, customer name or number, entity name, description, or external identifier, use POST accounting/credit-memos/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `creditMemoDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `creditMemoStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `creditMemoType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `manualCreditMemoType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `customerId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `invoiceId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, creditMemoDate, creditMemoStatus, creditMemoType, manualCreditMemoType, currency, customerId, entityId, invoiceId, subtotal, totalTax, totalDutiesAndCustoms, totalAmountCredited, appliedAmount, unappliedAmount, lastModifiedDate - Selectable: id, creditMemoNumber, creditMemoDate, creditMemoStatus, creditMemoType, manualCreditMemoType, currency, description, externalCreditMemoId, customerId, customerName, customerNumber, entityId, entityName, invoiceId, invoiceNumber, subtotal, totalTax, totalDutiesAndCustoms, totalAmountCredited, appliedAmount, unappliedAmount, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of creditMemosRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the credit memo. | | `creditMemoNumber` | string \| null | no | no | no | The credit memo number, for example 'CM-1'. Absent while none has been assigned. | | `creditMemoDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the credit memo was issued. Format YYYY-MM-DD. | | `creditMemoStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The application status of the credit memo. Possible values: 'unapplied', 'partiallyApplied', 'appliedInFull'. | | `creditMemoType` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How the credit memo was created. Possible values: 'manual', 'creditRebill', 'earlyRenewal', 'lateFees', 'cancellation', 'inventoryReturn'. Only 'manual' credit memos are created through this API. The rest come from internal workflows. | | `manualCreditMemoType` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The reason for a manual credit memo, as a tenant-configured key, for example 'writeOff' or 'customerSatisfaction'. Absent when creditMemoType is not 'manual'. | | `currency` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The currency of the credit memo. Every amount on the record, its lines and its application rows is in this currency. Format ISO 4217, for example 'USD'. | | `description` | string \| null | no | no | no | A free-form description of the credit memo. | | `externalCreditMemoId` | string \| null | no | no | no | The identifier of this credit memo in an external source system. For identifiers owned by your other systems, use externalIdentifiers below. | | `customerId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The customer the credit memo is issued to. See id on GET accounting/customers/:id. | | `customerName` | string \| null | no | no | no | The name of the customer the credit memo is issued to. | | `customerNumber` | string \| null | no | no | no | The customer number of the customer the credit memo is issued to. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The legal entity that issued the credit memo. See id on GET base/entities. | | `entityName` | string \| null | no | no | no | The name of the legal entity that issued the credit memo. | | `invoiceId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The invoice the credit memo was created from. Absent when it was not created from an invoice. See id on GET accounting/invoices/:id. | | `invoiceNumber` | string \| null | no | no | no | The number of the invoice the credit memo was created from. Present whenever invoiceId is. | | `subtotal` | decimal \| null | no | no | yes | The subtotal before tax and duties, in the credit memo currency. Absent when no subtotal has been computed. | | `totalTax` | decimal \| null | no | no | yes | The total tax across all lines, in the credit memo currency. | | `totalDutiesAndCustoms` | decimal \| null | no | no | yes | The total duties and customs across all lines, in the credit memo currency. | | `totalAmountCredited` | decimal \| null | no | no | yes | The total credited, including tax and duties, in the credit memo currency. | | `appliedAmount` | decimal \| null | no | no | yes | The amount applied to invoices and debit memos so far, in the credit memo currency. This is the net of the application rows below. | | `unappliedAmount` | decimal \| null | no | no | yes | The amount still available to apply, in the credit memo currency. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the credit memo last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "creditMemoDate": "2026-01-15", "creditMemoStatus": "", "creditMemoType": "", "manualCreditMemoType": "", "currency": "", "description": "", "externalCreditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "subtotal": "19.99", "totalTax": "19.99", "totalDutiesAndCustoms": "19.99", "totalAmountCredited": "19.99", "appliedAmount": "19.99", "unappliedAmount": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create a credit memo Operation ID: `createCreditMemo` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createCreditMemo) `POST /accounting/credit-memos` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates a manual credit memo. Each item in applications applies the new credit memo to an invoice, with the same checks as POST accounting/credit-memos/:id/apply. If any application can't be recorded, the credit memo isn't created either. The response contains only the new credit memo's id. Read the record with GET accounting/credit-memos/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createCreditMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `customerId` | string | yes | The customer the credit memo is issued to. See id on GET accounting/customers/:id. | | `entityId` | string | yes | The legal entity issuing the credit memo. See id on GET base/entities. | | `currency` | string | yes | The currency of the credit memo. Every amount on the credit memo is in this currency. Format ISO 4217, for example 'USD'. | | `creditMemoDate` | date | yes | The date the credit memo is issued. Use format YYYY-MM-DD. | | `manualCreditMemoType` | string | no | The reason for the credit memo, as a tenant-configured key, for example 'writeOff' or 'customerSatisfaction'. Although the field is optional in the schema, a credit memo cannot be created without it. | | `description` | string | no | A free-form description of the credit memo. | | `externalCreditMemoId` | string | no | The identifier of this credit memo in an external source system. | | `lines` | array | yes | The lines of the credit memo. At least one is required, even when the request also has applications. Each line has a description and a total price. Tax is added internally. | | `lines[].description` | string | yes | The line description, as shown to the customer. | | `lines[].totalPrice` | decimal | yes | The line total before tax, in the credit memo currency. | | `applications` | array | no | Initial applications, each applying the credit memo to an invoice when it is created. | | `applications[].invoiceId` | string | yes | The invoice this application targets. See id on GET accounting/invoices/:id. | | `applications[].appliedAmount` | decimal | yes | The amount to apply, in the credit memo currency. Must be positive. | | `applications[].appliedDate` | date | yes | The date the application is recorded. Use format YYYY-MM-DD. | ```json { "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "currency": "", "creditMemoDate": "2026-01-15", "manualCreditMemoType": "", "description": "", "externalCreditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lines": [ { "description": "", "totalPrice": "19.99" } ], "applications": [ { "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "appliedAmount": "19.99", "appliedDate": "2026-01-15" } ] } ``` ## Response `200` Success Under `data` (createCreditMemoOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created credit memo. Read the full record with GET accounting/credit-memos/:id. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `CUSTOMER_NOT_FOUND` | 400 | | `ENTITY_NOT_FOUND` | 400 | | `INVOICE_NOT_FOUND` | 400 | | `NO_LINES` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: CUSTOMER_NOT_FOUND, ENTITY_NOT_FOUND, INVOICE_NOT_FOUND, NO_LINES - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search credit memos Operation ID: `searchCreditMemo` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchCreditMemo) `POST /accounting/credit-memos/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches credit memos. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchCreditMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A credit memo must match every filter you send. Omit to return all credit memos. | | `filters.id` | string | no | Limits results to the credit memo with this id. | | `filters.creditMemoNumber` | string | no | Limits results to the credit memo with this number, for example 'CM-1'. The match is exact. | | `filters.customerId` | string | no | Limits results to credit memos issued to this customer. See id on GET accounting/customers/:id. | | `filters.customerName` | string | no | Limits results to credit memos whose customer name contains this text. The match is case-insensitive. | | `filters.customerNumber` | string | no | Limits results to credit memos with this customer number. The match is exact. | | `filters.entityId` | string | no | Limits results to credit memos issued by this legal entity. See id on GET base/entities. | | `filters.entityName` | string | no | Limits results to credit memos whose entity name contains this text. The match is case-insensitive. | | `filters.creditMemoStatus` | array | no | Limits results to credit memos in any of these application statuses. Possible values: 'unapplied', 'partiallyApplied', 'appliedInFull'. Any other value matches no credit memo. An empty array does not constrain. | | `filters.creditMemoType` | array | no | Limits results to credit memos of any of these types. Possible values: 'manual', 'creditRebill', 'earlyRenewal', 'lateFees', 'cancellation', 'inventoryReturn'. Any other value matches no credit memo. An empty array does not constrain. | | `filters.description` | string | no | Limits results to credit memos whose description contains this text. The match is case-insensitive. | | `filters.externalCreditMemoId` | string | no | Limits results to the credit memo with this external source system identifier. The match is exact. | | `filters.invoiceNumber` | string | no | Limits results to credit memos created from the invoice with this number. The match is exact. | | `filters.creditMemoDate` | object | no | Limits results to credit memos issued between the given bounds. Omit either bound to leave that side open. | | `filters.creditMemoDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.creditMemoDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.lastModifiedDate` | object | no | Limits results by when the credit memo last changed, the window to poll for an incremental sync. Omit either bound to leave that side open. | | `filters.lastModifiedDate.start` | string | no | The inclusive lower bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.lastModifiedDate.end` | string | no | The inclusive upper bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.externalIdentifier` | object | no | Limits results to credit memos with this external identifier. Both fieldName and value are required. Several credit memos may share a value, so this returns the items array, not a single record. | | `filters.externalIdentifier.fieldName` | string | yes | The configured field name of the identifier, for example 'netsuiteCreditMemoId'. It must be the fieldName of an existing credit-memo external-identifier configuration, or the request fails with UNKNOWN_EXTERNAL_IDENTIFIER. | | `filters.externalIdentifier.value` | string | yes | The identifier value to match, exactly as the other system stores it. | | `page` | object | no | The pagination controls. Omit on the first request. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit on the first request. | | `page.size` | number | no | The number of credit memos per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "creditMemoStatus": [ "" ], "creditMemoType": [ "" ], "description": "", "externalCreditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "creditMemoDate": { "start": "2026-01-15", "end": "2026-01-15" }, "lastModifiedDate": { "start": "", "end": "" }, "externalIdentifier": { "fieldName": "", "value": "" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchCreditMemoOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The matching credit memos for this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the credit memo. | | `items[].creditMemoNumber` | string | no | The credit memo number, for example 'CM-1'. Absent while none has been assigned. | | `items[].creditMemoDate` | date | yes | The date the credit memo was issued. Format YYYY-MM-DD. | | `items[].creditMemoStatus` | string | no | The application status of the credit memo. Possible values: 'unapplied', 'partiallyApplied', 'appliedInFull'. | | `items[].creditMemoType` | string | no | How the credit memo was created. Possible values: 'manual', 'creditRebill', 'earlyRenewal', 'lateFees', 'cancellation', 'inventoryReturn'. Only 'manual' credit memos are created through this API. The rest come from internal workflows. | | `items[].manualCreditMemoType` | string | no | The reason for a manual credit memo, as a tenant-configured key, for example 'writeOff' or 'customerSatisfaction'. Absent when creditMemoType is not 'manual'. | | `items[].currency` | string | yes | The currency of the credit memo. Every amount on the record, its lines and its application rows is in this currency. Format ISO 4217, for example 'USD'. | | `items[].description` | string | no | A free-form description of the credit memo. | | `items[].externalCreditMemoId` | string | no | The identifier of this credit memo in an external source system. For identifiers owned by your other systems, use externalIdentifiers below. | | `items[].customerId` | string | yes | The customer the credit memo is issued to. See id on GET accounting/customers/:id. | | `items[].customerName` | string | no | The name of the customer the credit memo is issued to. | | `items[].customerNumber` | string | no | The customer number of the customer the credit memo is issued to. | | `items[].entityId` | string | yes | The legal entity that issued the credit memo. See id on GET base/entities. | | `items[].entityName` | string | no | The name of the legal entity that issued the credit memo. | | `items[].invoiceId` | string | no | The invoice the credit memo was created from. Absent when it was not created from an invoice. See id on GET accounting/invoices/:id. | | `items[].invoiceNumber` | string | no | The number of the invoice the credit memo was created from. Present whenever invoiceId is. | | `items[].subtotal` | decimal | no | The subtotal before tax and duties, in the credit memo currency. Absent when no subtotal has been computed. | | `items[].totalTax` | decimal | no | The total tax across all lines, in the credit memo currency. | | `items[].totalDutiesAndCustoms` | decimal | no | The total duties and customs across all lines, in the credit memo currency. | | `items[].totalAmountCredited` | decimal | no | The total credited, including tax and duties, in the credit memo currency. | | `items[].appliedAmount` | decimal | no | The amount applied to invoices and debit memos so far, in the credit memo currency. This is the net of the application rows below. | | `items[].unappliedAmount` | decimal | no | The amount still available to apply, in the credit memo currency. | | `items[].lastModifiedDate` | date-time | yes | The date and time the credit memo last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].lines` | array | no | The lines of the credit memo, each crediting one product or free-text item. An empty array when the credit memo has no lines. Every amount is in the credit memo currency. | | `items[].lines[].id` | string | yes | The unique identifier for the credit memo line. | | `items[].lines[].creditMemoLineNumber` | string | no | The line number within the credit memo. | | `items[].lines[].description` | string | no | The line description, as shown to the customer. | | `items[].lines[].productId` | string | no | The product credited by the line. Absent on free-text lines. See id on GET accounting/products/:id. | | `items[].lines[].productCode` | string | no | The product code as it stood when the line was created. It does not follow later changes to the product. | | `items[].lines[].productDescription` | string | no | The product description as it stood when the line was created. It does not follow later changes to the product. | | `items[].lines[].quantity` | decimal | no | The quantity credited. | | `items[].lines[].unitPrice` | decimal | no | The price per unit, in the credit memo currency. | | `items[].lines[].totalPrice` | decimal | no | The line total before tax and duties, in the credit memo currency. | | `items[].lines[].taxAmount` | decimal | no | The tax on the line, in the credit memo currency. | | `items[].lines[].dutiesAndCustomsAmount` | decimal | no | The duties and customs on the line, in the credit memo currency. | | `items[].lines[].amountCredited` | decimal | no | The total credited for the line, in the credit memo currency: totalPrice + taxAmount + dutiesAndCustomsAmount. | | `items[].lines[].taxCode` | string | no | The tax code applied to the line. | | `items[].lines[].taxDate` | date | no | The date the line's tax was determined. Format YYYY-MM-DD. | | `items[].lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `items[].lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | | `items[].applications` | array | no | The application rows, each recording one allocation of the credit memo against an invoice or debit memo. Applying and unapplying both add a row, told apart by actionType and the sign of appliedAmount, so this is an append-only history rather than the current allocation. An empty array when the credit memo has never been applied. | | `items[].applications[].id` | string | yes | The unique identifier for the application row. | | `items[].applications[].appliedAmount` | decimal | no | The signed amount moved by this row, in the row's currency: positive when applying, negative when unapplying. Sum the rows to reconcile against the header appliedAmount. | | `items[].applications[].appliedDate` | date | yes | The date the allocation was recorded. Format YYYY-MM-DD. | | `items[].applications[].actionType` | string | no | What this row did. Possible values: 'applied', 'unapplied'. | | `items[].applications[].type` | string | no | The kind of document the credit memo was allocated against. Possible values: 'invoice', 'debitMemo'. Absent when the stored target kind is not one this version recognizes. | | `items[].applications[].targetId` | string | no | The target document: an invoice when type is 'invoice', a debit memo when type is 'debitMemo'. Absent when the target no longer exists or its kind is unrecognized. See id on GET accounting/invoices/:id and GET accounting/debit-memos/:id. | | `items[].applications[].targetNumber` | string | no | The number of the target document. | | `items[].applications[].currency` | string | yes | The currency of this row's appliedAmount. Matches the credit memo currency. Format ISO 4217, for example 'USD'. | | `items[].externalIdentifiers` | array | no | The credit memo's identifiers in other systems, one per configured field name. An empty array or absent when the credit memo has none. | | `items[].externalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier, for example 'netsuiteCreditMemoId'. | | `items[].externalIdentifiers[].value` | string | yes | The identifier in the other system. | | `items[].externalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. Absent when none is set. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "creditMemoDate": "2026-01-15", "creditMemoStatus": "", "creditMemoType": "", "manualCreditMemoType": "", "currency": "", "description": "", "externalCreditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "subtotal": "19.99", "totalTax": "19.99", "totalDutiesAndCustoms": "19.99", "totalAmountCredited": "19.99", "appliedAmount": "19.99", "unappliedAmount": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoLineNumber": "", "description": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "productCode": "", "productDescription": "", "quantity": "19.99", "unitPrice": "19.99", "totalPrice": "19.99", "taxAmount": "19.99", "dutiesAndCustomsAmount": "19.99", "amountCredited": "19.99", "taxCode": "", "taxDate": "2026-01-15", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ], "applications": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "appliedAmount": "19.99", "appliedDate": "2026-01-15", "actionType": "", "type": "", "targetId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "targetNumber": "", "currency": "" } ], "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 | | `INVALID_TIMESTAMP` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: UNKNOWN_EXTERNAL_IDENTIFIER, INVALID_TIMESTAMP - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a credit memo Operation ID: `getCreditMemo` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getCreditMemo) `GET /accounting/credit-memos/{id}` Read-only: Reads and returns; changes nothing. Retrieves a credit memo. Use the credit memo's id, not its creditMemoNumber. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the credit memo you want to retrieve. | ## Response `200` Success Under `data` (getCreditMemoOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the credit memo. | | `creditMemoNumber` | string | no | The credit memo number, for example 'CM-1'. Absent while none has been assigned. | | `creditMemoDate` | date | yes | The date the credit memo was issued. Format YYYY-MM-DD. | | `creditMemoStatus` | string | no | The application status of the credit memo. Possible values: 'unapplied', 'partiallyApplied', 'appliedInFull'. | | `creditMemoType` | string | no | How the credit memo was created. Possible values: 'manual', 'creditRebill', 'earlyRenewal', 'lateFees', 'cancellation', 'inventoryReturn'. Only 'manual' credit memos are created through this API. The rest come from internal workflows. | | `manualCreditMemoType` | string | no | The reason for a manual credit memo, as a tenant-configured key, for example 'writeOff' or 'customerSatisfaction'. Absent when creditMemoType is not 'manual'. | | `currency` | string | yes | The currency of the credit memo. Every amount on the record, its lines and its application rows is in this currency. Format ISO 4217, for example 'USD'. | | `description` | string | no | A free-form description of the credit memo. | | `externalCreditMemoId` | string | no | The identifier of this credit memo in an external source system. For identifiers owned by your other systems, use externalIdentifiers below. | | `customerId` | string | yes | The customer the credit memo is issued to. See id on GET accounting/customers/:id. | | `customerName` | string | no | The name of the customer the credit memo is issued to. | | `customerNumber` | string | no | The customer number of the customer the credit memo is issued to. | | `entityId` | string | yes | The legal entity that issued the credit memo. See id on GET base/entities. | | `entityName` | string | no | The name of the legal entity that issued the credit memo. | | `invoiceId` | string | no | The invoice the credit memo was created from. Absent when it was not created from an invoice. See id on GET accounting/invoices/:id. | | `invoiceNumber` | string | no | The number of the invoice the credit memo was created from. Present whenever invoiceId is. | | `subtotal` | decimal | no | The subtotal before tax and duties, in the credit memo currency. Absent when no subtotal has been computed. | | `totalTax` | decimal | no | The total tax across all lines, in the credit memo currency. | | `totalDutiesAndCustoms` | decimal | no | The total duties and customs across all lines, in the credit memo currency. | | `totalAmountCredited` | decimal | no | The total credited, including tax and duties, in the credit memo currency. | | `appliedAmount` | decimal | no | The amount applied to invoices and debit memos so far, in the credit memo currency. This is the net of the application rows below. | | `unappliedAmount` | decimal | no | The amount still available to apply, in the credit memo currency. | | `lastModifiedDate` | date-time | yes | The date and time the credit memo last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `lines` | array | no | The lines of the credit memo, each crediting one product or free-text item. An empty array when the credit memo has no lines. Every amount is in the credit memo currency. | | `lines[].id` | string | yes | The unique identifier for the credit memo line. | | `lines[].creditMemoLineNumber` | string | no | The line number within the credit memo. | | `lines[].description` | string | no | The line description, as shown to the customer. | | `lines[].productId` | string | no | The product credited by the line. Absent on free-text lines. See id on GET accounting/products/:id. | | `lines[].productCode` | string | no | The product code as it stood when the line was created. It does not follow later changes to the product. | | `lines[].productDescription` | string | no | The product description as it stood when the line was created. It does not follow later changes to the product. | | `lines[].quantity` | decimal | no | The quantity credited. | | `lines[].unitPrice` | decimal | no | The price per unit, in the credit memo currency. | | `lines[].totalPrice` | decimal | no | The line total before tax and duties, in the credit memo currency. | | `lines[].taxAmount` | decimal | no | The tax on the line, in the credit memo currency. | | `lines[].dutiesAndCustomsAmount` | decimal | no | The duties and customs on the line, in the credit memo currency. | | `lines[].amountCredited` | decimal | no | The total credited for the line, in the credit memo currency: totalPrice + taxAmount + dutiesAndCustomsAmount. | | `lines[].taxCode` | string | no | The tax code applied to the line. | | `lines[].taxDate` | date | no | The date the line's tax was determined. Format YYYY-MM-DD. | | `lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | | `applications` | array | no | The application rows, each recording one allocation of the credit memo against an invoice or debit memo. Applying and unapplying both add a row, told apart by actionType and the sign of appliedAmount, so this is an append-only history rather than the current allocation. An empty array when the credit memo has never been applied. | | `applications[].id` | string | yes | The unique identifier for the application row. | | `applications[].appliedAmount` | decimal | no | The signed amount moved by this row, in the row's currency: positive when applying, negative when unapplying. Sum the rows to reconcile against the header appliedAmount. | | `applications[].appliedDate` | date | yes | The date the allocation was recorded. Format YYYY-MM-DD. | | `applications[].actionType` | string | no | What this row did. Possible values: 'applied', 'unapplied'. | | `applications[].type` | string | no | The kind of document the credit memo was allocated against. Possible values: 'invoice', 'debitMemo'. Absent when the stored target kind is not one this version recognizes. | | `applications[].targetId` | string | no | The target document: an invoice when type is 'invoice', a debit memo when type is 'debitMemo'. Absent when the target no longer exists or its kind is unrecognized. See id on GET accounting/invoices/:id and GET accounting/debit-memos/:id. | | `applications[].targetNumber` | string | no | The number of the target document. | | `applications[].currency` | string | yes | The currency of this row's appliedAmount. Matches the credit memo currency. Format ISO 4217, for example 'USD'. | | `externalIdentifiers` | array | no | The credit memo's identifiers in other systems, one per configured field name. An empty array or absent when the credit memo has none. | | `externalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier, for example 'netsuiteCreditMemoId'. | | `externalIdentifiers[].value` | string | yes | The identifier in the other system. | | `externalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. Absent when none is set. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "creditMemoDate": "2026-01-15", "creditMemoStatus": "", "creditMemoType": "", "manualCreditMemoType": "", "currency": "", "description": "", "externalCreditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "subtotal": "19.99", "totalTax": "19.99", "totalDutiesAndCustoms": "19.99", "totalAmountCredited": "19.99", "appliedAmount": "19.99", "unappliedAmount": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoLineNumber": "", "description": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "productCode": "", "productDescription": "", "quantity": "19.99", "unitPrice": "19.99", "totalPrice": "19.99", "taxAmount": "19.99", "dutiesAndCustomsAmount": "19.99", "amountCredited": "19.99", "taxCode": "", "taxDate": "2026-01-15", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ], "applications": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "appliedAmount": "19.99", "appliedDate": "2026-01-15", "actionType": "", "type": "", "targetId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "targetNumber": "", "currency": "" } ], "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update a credit memo Operation ID: `updateCreditMemo` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateCreditMemo) `PATCH /accounting/credit-memos/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates a credit memo with the fields you send. A field you leave out keeps its current value. updateLines changes only the description of each line you name, and a line you leave out keeps its description. Use the credit memo's id, not its creditMemoNumber. The response is empty. To apply the credit memo to invoices, use POST accounting/credit-memos/:id/apply. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the credit memo you want to update. | ## Request body Required, `application/json`: updateCreditMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `creditMemoDate` | date | no | The new issue date. Use format YYYY-MM-DD. | | `description` | string | no | The new description. | | `externalCreditMemoId` | string | no | The new identifier of this credit memo in an external source system. | | `updateLines` | array | no | The line edits, keyed by line id. Only the description can be edited. Lines cannot be added or removed here. | | `updateLines[].id` | string | yes | The unique identifier for the credit memo line to edit, as returned by GET accounting/credit-memos/:id. | | `updateLines[].description` | string | no | The new line description, as shown to the customer. On a manual credit memo it cannot be cleared, because a description is required there. | ```json { "creditMemoDate": "2026-01-15", "description": "", "externalCreditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "updateLines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "" } ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `LINE_NOT_FOUND` | 400 | | `LINE_DESCRIPTION_REQUIRED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, LINE_NOT_FOUND, LINE_DESCRIPTION_REQUIRED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Apply a credit memo to invoices Operation ID: `applyCreditMemo` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/applyCreditMemo) `POST /accounting/credit-memos/{id}/apply` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Applies a credit memo to invoices. Each item in applications applies part of the credit memo to one invoice of the same customer. The amount applied to an invoice can't exceed its amount due, and the total can't exceed the credit memo's unapplied amount. The response is empty. Read the new allocations in applications on GET accounting/credit-memos/:id. To reverse an allocation, use POST accounting/credit-memos/:id/unapply. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the credit memo you want to apply. | ## Request body Required, `application/json`: applyCreditMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `applications` | array | yes | The allocations to record. At least one is required. Several rows may target the same invoice, and are summed against that invoice's amount due. | | `applications[].invoiceId` | string | yes | The invoice to apply against. See id on GET accounting/invoices/:id. | | `applications[].appliedAmount` | decimal | yes | The amount to apply, in the credit memo currency. Must be positive. The credit memo currency governs. There is no per-row currency. | | `applications[].appliedDate` | date | yes | The date the allocation is recorded. Use format YYYY-MM-DD. It must fall in an open accounting period. | ```json { "applications": [ { "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "appliedAmount": "19.99", "appliedDate": "2026-01-15" } ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `INVOICE_NOT_FOUND` | 400 | | `NO_APPLICATIONS` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, INVOICE_NOT_FOUND, NO_APPLICATIONS - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Unapply a credit memo from invoices Operation ID: `unapplyCreditMemo` Group: Accounting › Credit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/unapplyCreditMemo) `POST /accounting/credit-memos/{id}/unapply` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Unapplies a credit memo from invoices. Each reversal is recorded as a negative allocation. The original allocation stays, so applications on GET accounting/credit-memos/:id lists both the allocation and its reversal. The response is empty. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the credit memo you want to unapply. | ## Request body Required, `application/json`: unapplyCreditMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `applications` | array | yes | The reversals to record. At least one is required. Several rows may target the same invoice, and are summed against what is currently applied to it. | | `applications[].invoiceId` | string | yes | The invoice to release the credit from. See id on GET accounting/invoices/:id. | | `applications[].appliedAmount` | decimal | yes | The amount to reverse, in the credit memo currency. Must be positive. It is stored as a negative row and may not exceed what is currently applied to this invoice. | | `applications[].appliedDate` | date | yes | The date the reversal is recorded. Use format YYYY-MM-DD. It must fall in an open accounting period. | ```json { "applications": [ { "invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "appliedAmount": "19.99", "appliedDate": "2026-01-15" } ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `INVOICE_NOT_FOUND` | 400 | | `NO_APPLICATIONS` | 400 | | `UNAPPLY_EXCEEDS_APPLIED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, INVOICE_NOT_FOUND, NO_APPLICATIONS, UNAPPLY_EXCEEDS_APPLIED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Customers Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingCustomers) The customer directory of accounting: who the entity bills and is billed by. Sits under Accounting, beside the other domains of that module. ## Endpoints - [List customers](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/customers.html): `GET /accounting/customers` - [Create a customer](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createCustomer.html): `POST /accounting/customers` - [Search customers](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchCustomer.html): `POST /accounting/customers/search` - [Retrieve a customer](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getCustomer.html): `GET /accounting/customers/{id}` - [Update a customer](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateCustomer.html): `PATCH /accounting/customers/{id}` --- # List customers Operation ID: `customers` Group: Accounting › Customers [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/customers) `GET /accounting/customers` Read-only: Reads and returns; changes nothing. Lists customers. To filter by customer number, name, legal entity, or external identifier, use `POST accounting/customers/search`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `customerType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `partnerType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `businessType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `customerSegment` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `salesChannel` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `globalParentId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `defaultCurrency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `taxExempt` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `paymentTermLabel` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `country` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, customerName, customerType, businessType, customerSegment, defaultCurrency, taxExempt, paymentTermLabel, exemptionNumber, invoiceEmail, lastModifiedDate - Selectable: id, customerNumber, customerName, customerCode, customerType, partnerType, businessType, customerSegment, status, salesChannel, entityId, entityName, globalParentId, globalParentName, defaultCurrency, taxExempt, paymentTermLabel, exemptionNumber, invoiceEmail, country, stateProvince, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of customersRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the customer. | | `customerNumber` | string \| null | no | no | no | The customer number. Example: 'CUS-789012'. | | `customerName` | string | no | no | yes | The name of the customer, as it appears on invoices. | | `customerCode` | string \| null | no | no | no | Customer code as maintained by whoever set the record up — a human-readable key that is not the system-assigned customerNumber, and absent where nobody assigned one. Do not treat it as unique unless the tenant maintains it that way. Returned only — it can be neither filtered nor sorted on. | | `customerType` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The customer type. Possible values: 'default' (a direct customer), 'partner' (a reseller or third-party payer that is invoiced for its end customers), 'endCustomer' (a customer that buys through a partner). | | `partnerType` | "reseller" \| "thirdPartyPayer" \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Partner type, set only when customerType is 'partner'. | | `businessType` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The business type of the customer. Possible values: 'company', 'individual'. | | `customerSegment` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The market segment of the customer. Possible values: 'SMB', 'MidMarket', 'Enterprise'. Absent when businessType is 'individual'. | | `status` | "active" \| "inactive" \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Whether the customer record is active. This is the record's own state and says nothing about trading: an active customer may have no order and no receivable, and an inactive one may still carry an open balance. A report counting customers by commercial activity reads sales orders or invoices instead, and says which. | | `salesChannel` | "salesAssisted" \| "selfServe" \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | How the customer was sold to. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The legal entity that owns this customer. | | `entityName` | string \| null | no | no | no | The name of the legal entity that invoices this customer. | | `globalParentId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The top-level customer of the hierarchy this customer belongs to — the customer itself when it is the top. Absent when the customer belongs to no hierarchy. The id rather than the name, so a hierarchy can be traversed rather than only grouped. | | `globalParentName` | string \| null | no | no | no | Display name of the global parent customer. Returned only — it can be neither filtered nor sorted on. | | `defaultCurrency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The default currency for the customer's transactions, as a 3-letter ISO 4217 code. | | `taxExempt` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the customer is exempt from tax. | | `paymentTermLabel` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The display name of the payment term applied to the customer's invoices. See label on GET accounting/payment-terms. | | `exemptionNumber` | string \| null | no | no | yes | The tax-exemption certificate number. Absent when taxExempt is false. | | `invoiceEmail` | string \| null | no | no | yes | The email address invoices are sent to. | | `country` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | ISO 3166 country code of the customer's DEFAULT sold-to address — the country a report groups or maps by. A customer with several addresses is counted once, at the default sold-to; absent when no default sold-to address is on file, which is a gap in the record rather than a customer without a country. The full address is on GET accounting/customers/:id. | | `stateProvince` | string \| null | no | no | no | State or province of the same default sold-to address. The spelling is NOT uniform: most values are ISO 3166-2 subdivision codes (US-CA, GB-ENG) and some are plain names (Idaho, Virginia), because the address record accepts either. A filter written for one form misses the other, and grouping by this field splits one state into two — normalise first, or group by country instead. Absent where the address carries no state. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the customer record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerNumber": "", "customerName": "", "customerCode": "", "customerType": "", "partnerType": "reseller", "businessType": "", "customerSegment": "", "status": "active", "salesChannel": "salesAssisted", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "globalParentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "globalParentName": "", "defaultCurrency": "", "taxExempt": true, "paymentTermLabel": "", "exemptionNumber": "", "invoiceEmail": "", "country": "", "stateProvince": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create a customer Operation ID: `createCustomer` Group: Accounting › Customers [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createCustomer) `POST /accounting/customers` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates a customer. The combination of name, legal entity, and customer type must be unique. If another customer already has the same combination, the request fails with DUPLICATE_CUSTOMER. The response contains only the new customer's id. Read the record with GET accounting/customers/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createCustomerInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | The name of the customer, as it appears on invoices. Example: `"Example Robotics Inc"`. | | `entityId` | string | yes | The legal entity that invoices this customer. To move the customer to another legal entity later, use PATCH accounting/customers/:id. Example: `"3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45"`. | | `customerType` | string | no | The customer type. Accepted values: 'default', 'partner'. Defaults to 'default'. 'endCustomer' is not supported and fails with END_CUSTOMER_NOT_SUPPORTED. Example: `"default"`. | | `partnerType` | string | no | The partner type, for a customer whose customerType is 'partner'. Accepted values: 'reseller', 'thirdPartyPayer'. | | `businessType` | string | no | The business type of the customer. Accepted values: 'company', 'individual'. Defaults to 'company'. Example: `"company"`. | | `invoiceEmail` | string | no | The email address invoices are sent to. Example: `"billing@example-robotics.com"`. | | `mainEmail` | string | no | The email address of the customer's main contact. | | `taxExempt` | boolean | no | Whether the customer is exempt from tax. Defaults to false. | | `defaultCurrency` | string | no | The default currency for the customer's transactions, as a 3-letter ISO 4217 code. Defaults to the legal entity's currency. Example: `"USD"`. | | `segment` | string | no | The market segment of the customer. Accepted values: 'SMB', 'MidMarket', 'Enterprise'. Ignored when businessType is 'individual'. | | `exemptionNumber` | string | no | The tax-exemption certificate number. Accepted only when taxExempt is true. Otherwise the request fails with EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT. | | `paymentTermCode` | string | no | The code of a payment term on the customer's legal entity's list. A code that is not on the list fails with UNKNOWN_REFERENCE. See code on GET accounting/payment-terms. | | `parentId` | string | no | The id of the parent customer. An unknown id fails with UNKNOWN_REFERENCE. | | `partnerId` | string | no | The id of the partner this customer buys through. An unknown id fails with UNKNOWN_REFERENCE. | | `salesChannel` | string | no | The sales channel for this customer. Accepted values: 'salesAssisted' (sold through a salesperson), 'selfServe'. | | `additionalContactEmail` | string | no | Another email address for the customer, besides invoiceEmail and mainEmail. | | `mainContactName` | string | no | The name of the customer's main contact. | | `preferredLanguage` | string | no | The customer's preferred language. Accepted values: 'english'. | | `crmCustomerId` | string | no | The customer's identifier in an external CRM system. | | `businessIdentificationNumber` | string | no | The customer's business registration number. | | `invoiceConsolidation` | string | no | How the customer's invoices are consolidated. Accepted values: 'customer', 'purchaseOrderNumber', 'salesArrangement'. | | `invoiceDeliveryMode` | string | no | How invoices are delivered to the customer. Accepted values: 'manual', 'automated'. | | `isExemptFromCreditCardFee` | boolean | no | Whether the customer is exempt from credit-card fees. Defaults to false. | | `billableTravelExpenseInvoiceDetail` | string | no | How billable travel and expense items appear on invoices. Accepted values: 'detailed', 'consolidated'. Defaults to 'detailed'. | | `status` | string | no | The status of the customer. Accepted values: 'active', 'inactive'. Defaults to 'active'. | | `taxExemptCode` | string | no | The tax-exemption reason code. Cleared when taxExempt is false. | | `taxRegistrations` | array | no | The customer's tax registrations. | | `taxRegistrations[].country` | string | yes | The country the registration applies in, as an ISO 3166-1 code. | | `taxRegistrations[].taxNumber` | string | yes | The tax identification number issued by the tax authority. | | `taxRegistrations[].type` | string | yes | The kind of tax registration. Accepted values: 'tax', 'salesTax', 'VAT', 'GST', 'EIN', 'TIN'. | | `taxRegistrations[].effectiveDate` | date | no | The date the registration takes effect. Use format YYYY-MM-DD. | | `taxRegistrations[].isActive` | boolean | no | Whether the registration is active. Only one active registration is allowed per country and tax type. Defaults to true. | | `payment` | object | no | The customer's link to a payment provider. `externalPaymentMethodId` requires `externalCustomerId` in the same request. | | `payment.provider` | string | no | The name of a payment provider connected to your tenant. An unknown name fails with UNKNOWN_PAYMENT_PROVIDER, which lists the connected providers. Required when any other payment field is sent. Example: 'Stripe'. | | `payment.externalCustomerId` | string | no | The customer's identifier in the payment provider's system. | | `payment.externalPaymentMethodId` | string | no | The customer's default payment method in the payment provider's system. | | `addresses` | array | no | The customer's addresses. Mark at most one entry per type as the default with isDefault. When a type has none marked, its first entry is the default. Example: `[{"type":"billTo","line1":"500 Market St","city":"San Francisco","stateProvince":"CA","country":"US","zipCode":"94105","isDefault":true}]`. | | `addresses[].type` | string | yes | The address type. Accepted values: 'billTo', 'soldTo'. | | `addresses[].attention` | string | no | The person or department that mail to this address is for. | | `addresses[].isDefault` | boolean | no | Whether this entry is the default address of its type. | | `addresses[].line1` | string | yes | The first address line: street number and name, or PO box. | | `addresses[].line2` | string | no | The second address line: apartment, suite, unit, building, or floor. | | `addresses[].city` | string | yes | The city or locality. | | `addresses[].stateProvince` | string | no | The state, province, or region. | | `addresses[].country` | string | yes | The country, as a name or ISO 3166-1 code. | | `addresses[].zipCode` | string | yes | The postal or ZIP code. | | `externalIdentifiers` | array | no | The customer's identifiers in other systems, one per configured field name. A field name not configured for your tenant fails with UNKNOWN_EXTERNAL_IDENTIFIER. | | `externalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier. Example: 'netsuiteCustomerId'. | | `externalIdentifiers[].value` | string | yes | The identifier in the other system. | | `externalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. | ```json { "name": "Example Robotics Inc", "entityId": "3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45", "customerType": "default", "partnerType": "", "businessType": "company", "invoiceEmail": "billing@example-robotics.com", "mainEmail": "", "taxExempt": true, "defaultCurrency": "USD", "segment": "", "exemptionNumber": "", "paymentTermCode": "", "parentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "partnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesChannel": "", "additionalContactEmail": "", "mainContactName": "", "preferredLanguage": "", "crmCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "businessIdentificationNumber": "", "invoiceConsolidation": "", "invoiceDeliveryMode": "", "isExemptFromCreditCardFee": true, "billableTravelExpenseInvoiceDetail": "", "status": "", "taxExemptCode": "", "taxRegistrations": [ { "country": "", "taxNumber": "", "type": "", "effectiveDate": "2026-01-15", "isActive": true } ], "payment": { "provider": "", "externalCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalPaymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "addresses": [ { "type": "billTo", "line1": "500 Market St", "city": "San Francisco", "stateProvince": "CA", "country": "US", "zipCode": "94105", "isDefault": true } ], "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] } ``` ## Response `200` Success Under `data` (createCustomerOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created customer. Example: `"9f2c6a10-7d34-4c8e-9b21-5a0f3e6d81b7"`. | ```json { "data": { "id": "9f2c6a10-7d34-4c8e-9b21-5a0f3e6d81b7" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `INVALID_ENTITY` | 400 | | `INVALID_FIELD_VALUE` | 400 | | `MULTIPLE_DEFAULT_ADDRESSES` | 400 | | `DUPLICATE_CUSTOMER` | 400 | | `END_CUSTOMER_NOT_SUPPORTED` | 400 | | `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 | | `DUPLICATE_EXTERNAL_IDENTIFIER` | 400 | | `UNKNOWN_REFERENCE` | 400 | | `UNKNOWN_PAYMENT_PROVIDER` | 400 | | `PAYMENT_METHOD_REQUIRES_CUSTOMER_ID` | 400 | | `EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_ENTITY, INVALID_FIELD_VALUE, MULTIPLE_DEFAULT_ADDRESSES, DUPLICATE_CUSTOMER, END_CUSTOMER_NOT_SUPPORTED, UNKNOWN_EXTERNAL_IDENTIFIER, DUPLICATE_EXTERNAL_IDENTIFIER, UNKNOWN_REFERENCE, UNKNOWN_PAYMENT_PROVIDER, PAYMENT_METHOD_REQUIRES_CUSTOMER_ID, EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search customers Operation ID: `searchCustomer` Group: Accounting › Customers [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchCustomer) `POST /accounting/customers/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches customers. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchCustomerInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A customer must match every filter you send. Omit to return all customers. | | `filters.id` | string | no | Limits results to the customer with this id. | | `filters.customerNumber` | string | no | Limits results to the customer with this customer number. The match is exact. Example: 'CUS-789012'. | | `filters.name` | string | no | Limits results to customers whose name contains this text. The match is case-insensitive. | | `filters.entityId` | string | no | Limits results to customers of this legal entity. | | `filters.customerType` | string | no | Limits results to customers of this type. Accepted values: 'default', 'partner', 'endCustomer'. Any other value matches no customer. | | `filters.externalIdentifier` | object | no | Limits results to customers that have this external identifier. Several customers can share one value. | | `filters.externalIdentifier.fieldName` | string | yes | The configured field name of the identifier. A name that is not configured fails with UNKNOWN_EXTERNAL_IDENTIFIER. Example: 'netsuiteCustomerId'. | | `filters.externalIdentifier.value` | string | yes | The identifier in the other system. The match is exact. | | `page` | object | no | Pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's `pagination.nextCursor`. Omit on the first request. | | `page.size` | number | no | The number of customers per page, between 1 and 200. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerNumber": "", "name": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerType": "", "externalIdentifier": { "fieldName": "", "value": "" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchCustomerOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The customers on this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the customer. | | `items[].name` | string | yes | The name of the customer, as it appears on invoices. | | `items[].customerNumber` | string | yes | The customer number. Example: 'CUS-789012'. | | `items[].customerType` | string | no | The customer type. Possible values: 'default' (a direct customer), 'partner' (a reseller or third-party payer that is invoiced for its end customers), 'endCustomer' (a customer that buys through a partner). | | `items[].partnerType` | string | no | The partner type of a customer whose customerType is 'partner'. Possible values: 'reseller', 'thirdPartyPayer'. | | `items[].businessType` | string | no | The business type of the customer. Possible values: 'company', 'individual'. | | `items[].entityId` | string | yes | The legal entity that invoices this customer. | | `items[].entityName` | string | no | The name of the legal entity that invoices this customer. | | `items[].lastModifiedDate` | date-time | yes | The date and time the customer record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].invoiceEmail` | string | no | The email address invoices are sent to. | | `items[].mainEmail` | string | no | The email address of the customer's main contact. | | `items[].taxExempt` | boolean | no | Whether the customer is exempt from tax. | | `items[].defaultCurrency` | string | no | The default currency for the customer's transactions, as a 3-letter ISO 4217 code. | | `items[].segment` | string | no | The market segment of the customer. Possible values: 'SMB', 'MidMarket', 'Enterprise'. Absent when businessType is 'individual'. | | `items[].paymentTermLabel` | string | no | The display name of the payment term applied to the customer's invoices. See label on GET accounting/payment-terms. | | `items[].exemptionNumber` | string | no | The tax-exemption certificate number. Absent when taxExempt is false. | | `items[].addresses` | array | no | The customer's addresses, bill-to entries first, then sold-to. Each entry names its type and whether it is that type's default. An empty array when the customer has no address. | | `items[].addresses[].type` | string | yes | The address type. Possible values: 'billTo', 'soldTo'. | | `items[].addresses[].isDefault` | boolean | yes | Whether this is the customer's default address of its type. | | `items[].addresses[].addressId` | string | no | The unique identifier for the address. | | `items[].addresses[].attention` | string | no | The person or department that mail to this address is for. | | `items[].addresses[].line1` | string | no | The first address line: street number and name, or PO box. | | `items[].addresses[].line2` | string | no | The second address line: apartment, suite, unit, building, or floor. | | `items[].addresses[].city` | string | no | The city or locality. | | `items[].addresses[].stateProvince` | string | no | The state, province, or region. | | `items[].addresses[].country` | string | no | The country, as a name or ISO 3166-1 code. | | `items[].addresses[].zipCode` | string | no | The postal or ZIP code. | | `items[].taxExemptCode` | string | no | The tax-exemption reason code. | | `items[].salesChannel` | string | no | The sales channel for this customer. Possible values: 'salesAssisted' (sold through a salesperson), 'selfServe'. | | `items[].additionalContactEmail` | string | no | Another email address for the customer, besides invoiceEmail and mainEmail. | | `items[].mainContactName` | string | no | The name of the customer's main contact. | | `items[].preferredLanguage` | string | no | The customer's preferred language. Possible values: 'english'. | | `items[].crmCustomerId` | string | no | The customer's identifier in an external CRM system. | | `items[].businessIdentificationNumber` | string | no | The customer's business registration number. | | `items[].invoiceConsolidation` | string | no | How the customer's invoices are consolidated. Possible values: 'customer', 'purchaseOrderNumber', 'salesArrangement'. | | `items[].invoiceDeliveryMode` | string | no | How invoices are delivered to the customer. Possible values: 'manual', 'automated'. | | `items[].isExemptFromCreditCardFee` | boolean | no | Whether the customer is exempt from credit-card fees. | | `items[].billableTravelExpenseInvoiceDetail` | string | no | How billable travel and expense items appear on invoices. Possible values: 'detailed', 'consolidated'. | | `items[].status` | string | no | The status of the customer. Possible values: 'active', 'inactive'. | | `items[].paymentTerm` | string | no | The code of the payment term applied to the customer's invoices. See code on GET accounting/payment-terms. `paymentTermLabel` is its display name. | | `items[].taxRegistrations` | array | no | The customer's tax registrations. An empty array when the customer has none. | | `items[].taxRegistrations[].country` | string | yes | The country the registration applies in, as an ISO 3166-1 code. | | `items[].taxRegistrations[].taxNumber` | string | yes | The tax identification number issued by the tax authority. | | `items[].taxRegistrations[].type` | string | yes | The kind of tax registration. Possible values: 'tax', 'salesTax', 'VAT', 'GST', 'EIN', 'TIN'. | | `items[].taxRegistrations[].effectiveDate` | string | no | The date the registration takes effect. Format YYYY-MM-DD. | | `items[].taxRegistrations[].isActive` | boolean | no | Whether the registration is active. Only one active registration is allowed per country and tax type. | | `items[].taxRegistrations[].note` | string | no | An internal note on the registration. | | `items[].taxRegistrations[].validationStatus` | string | no | The validation state the tax engine assigned. | | `items[].taxRegistrations[].validationMessages` | string | no | Detail behind the validation state, when the tax engine recorded any. | | `items[].payment` | object | no | The customer's link to a payment provider. Absent when no provider is linked. | | `items[].payment.provider` | string | no | The name of the payment provider. Example: 'Stripe'. | | `items[].payment.externalCustomerId` | string | no | The customer's identifier in the payment provider's system. | | `items[].payment.externalPaymentMethodId` | string | no | The customer's default payment method in the payment provider's system. | | `items[].externalIdentifiers` | array | no | The customer's identifiers in other systems, one per configured field name. An empty array when the customer has none. | | `items[].externalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier. Example: 'netsuiteCustomerId'. | | `items[].externalIdentifiers[].value` | string | yes | The identifier in the other system. | | `items[].externalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. | | `pagination` | object | yes | Cursors for the next page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as `page.cursor`. Absent on the last page. | | `pagination.hasMore` | boolean | yes | Whether more customers remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "customerNumber": "", "customerType": "", "partnerType": "", "businessType": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "invoiceEmail": "", "mainEmail": "", "taxExempt": true, "defaultCurrency": "", "segment": "", "paymentTermLabel": "", "exemptionNumber": "", "addresses": [ { "type": "", "isDefault": true, "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "attention": "", "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" } ], "taxExemptCode": "", "salesChannel": "", "additionalContactEmail": "", "mainContactName": "", "preferredLanguage": "", "crmCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "businessIdentificationNumber": "", "invoiceConsolidation": "", "invoiceDeliveryMode": "", "isExemptFromCreditCardFee": true, "billableTravelExpenseInvoiceDetail": "", "status": "", "paymentTerm": "", "taxRegistrations": [ { "country": "", "taxNumber": "", "type": "", "effectiveDate": "", "isActive": true, "note": "", "validationStatus": "", "validationMessages": "" } ], "payment": { "provider": "", "externalCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalPaymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: UNKNOWN_EXTERNAL_IDENTIFIER - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a customer Operation ID: `getCustomer` Group: Accounting › Customers [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getCustomer) `GET /accounting/customers/{id}` Read-only: Reads and returns; changes nothing. Retrieves a customer. Use the customer's id, not its customerNumber. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the customer you want to retrieve. | ## Response `200` Success Under `data` (getCustomerOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the customer. | | `name` | string | yes | The name of the customer, as it appears on invoices. | | `customerNumber` | string | yes | The customer number. Example: 'CUS-789012'. | | `customerType` | string | no | The customer type. Possible values: 'default' (a direct customer), 'partner' (a reseller or third-party payer that is invoiced for its end customers), 'endCustomer' (a customer that buys through a partner). | | `partnerType` | string | no | The partner type of a customer whose customerType is 'partner'. Possible values: 'reseller', 'thirdPartyPayer'. | | `businessType` | string | no | The business type of the customer. Possible values: 'company', 'individual'. | | `entityId` | string | yes | The legal entity that invoices this customer. | | `entityName` | string | no | The name of the legal entity that invoices this customer. | | `lastModifiedDate` | date-time | yes | The date and time the customer record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `invoiceEmail` | string | no | The email address invoices are sent to. | | `mainEmail` | string | no | The email address of the customer's main contact. | | `taxExempt` | boolean | no | Whether the customer is exempt from tax. | | `defaultCurrency` | string | no | The default currency for the customer's transactions, as a 3-letter ISO 4217 code. | | `segment` | string | no | The market segment of the customer. Possible values: 'SMB', 'MidMarket', 'Enterprise'. Absent when businessType is 'individual'. | | `paymentTermLabel` | string | no | The display name of the payment term applied to the customer's invoices. See label on GET accounting/payment-terms. | | `exemptionNumber` | string | no | The tax-exemption certificate number. Absent when taxExempt is false. | | `addresses` | array | no | The customer's addresses, bill-to entries first, then sold-to. Each entry names its type and whether it is that type's default. An empty array when the customer has no address. | | `addresses[].type` | string | yes | The address type. Possible values: 'billTo', 'soldTo'. | | `addresses[].isDefault` | boolean | yes | Whether this is the customer's default address of its type. | | `addresses[].addressId` | string | no | The unique identifier for the address. | | `addresses[].attention` | string | no | The person or department that mail to this address is for. | | `addresses[].line1` | string | no | The first address line: street number and name, or PO box. | | `addresses[].line2` | string | no | The second address line: apartment, suite, unit, building, or floor. | | `addresses[].city` | string | no | The city or locality. | | `addresses[].stateProvince` | string | no | The state, province, or region. | | `addresses[].country` | string | no | The country, as a name or ISO 3166-1 code. | | `addresses[].zipCode` | string | no | The postal or ZIP code. | | `taxExemptCode` | string | no | The tax-exemption reason code. | | `salesChannel` | string | no | The sales channel for this customer. Possible values: 'salesAssisted' (sold through a salesperson), 'selfServe'. | | `additionalContactEmail` | string | no | Another email address for the customer, besides invoiceEmail and mainEmail. | | `mainContactName` | string | no | The name of the customer's main contact. | | `preferredLanguage` | string | no | The customer's preferred language. Possible values: 'english'. | | `crmCustomerId` | string | no | The customer's identifier in an external CRM system. | | `businessIdentificationNumber` | string | no | The customer's business registration number. | | `invoiceConsolidation` | string | no | How the customer's invoices are consolidated. Possible values: 'customer', 'purchaseOrderNumber', 'salesArrangement'. | | `invoiceDeliveryMode` | string | no | How invoices are delivered to the customer. Possible values: 'manual', 'automated'. | | `isExemptFromCreditCardFee` | boolean | no | Whether the customer is exempt from credit-card fees. | | `billableTravelExpenseInvoiceDetail` | string | no | How billable travel and expense items appear on invoices. Possible values: 'detailed', 'consolidated'. | | `status` | string | no | The status of the customer. Possible values: 'active', 'inactive'. | | `paymentTerm` | string | no | The code of the payment term applied to the customer's invoices. See code on GET accounting/payment-terms. `paymentTermLabel` is its display name. | | `taxRegistrations` | array | no | The customer's tax registrations. An empty array when the customer has none. | | `taxRegistrations[].country` | string | yes | The country the registration applies in, as an ISO 3166-1 code. | | `taxRegistrations[].taxNumber` | string | yes | The tax identification number issued by the tax authority. | | `taxRegistrations[].type` | string | yes | The kind of tax registration. Possible values: 'tax', 'salesTax', 'VAT', 'GST', 'EIN', 'TIN'. | | `taxRegistrations[].effectiveDate` | string | no | The date the registration takes effect. Format YYYY-MM-DD. | | `taxRegistrations[].isActive` | boolean | no | Whether the registration is active. Only one active registration is allowed per country and tax type. | | `taxRegistrations[].note` | string | no | An internal note on the registration. | | `taxRegistrations[].validationStatus` | string | no | The validation state the tax engine assigned. | | `taxRegistrations[].validationMessages` | string | no | Detail behind the validation state, when the tax engine recorded any. | | `payment` | object | no | The customer's link to a payment provider. Absent when no provider is linked. | | `payment.provider` | string | no | The name of the payment provider. Example: 'Stripe'. | | `payment.externalCustomerId` | string | no | The customer's identifier in the payment provider's system. | | `payment.externalPaymentMethodId` | string | no | The customer's default payment method in the payment provider's system. | | `externalIdentifiers` | array | no | The customer's identifiers in other systems, one per configured field name. An empty array when the customer has none. | | `externalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier. Example: 'netsuiteCustomerId'. | | `externalIdentifiers[].value` | string | yes | The identifier in the other system. | | `externalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "customerNumber": "", "customerType": "", "partnerType": "", "businessType": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "invoiceEmail": "", "mainEmail": "", "taxExempt": true, "defaultCurrency": "", "segment": "", "paymentTermLabel": "", "exemptionNumber": "", "addresses": [ { "type": "", "isDefault": true, "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "attention": "", "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" } ], "taxExemptCode": "", "salesChannel": "", "additionalContactEmail": "", "mainContactName": "", "preferredLanguage": "", "crmCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "businessIdentificationNumber": "", "invoiceConsolidation": "", "invoiceDeliveryMode": "", "isExemptFromCreditCardFee": true, "billableTravelExpenseInvoiceDetail": "", "status": "", "paymentTerm": "", "taxRegistrations": [ { "country": "", "taxNumber": "", "type": "", "effectiveDate": "", "isActive": true, "note": "", "validationStatus": "", "validationMessages": "" } ], "payment": { "provider": "", "externalCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalPaymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update a customer Operation ID: `updateCustomer` Group: Accounting › Customers [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateCustomer) `PATCH /accounting/customers/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates a customer with the fields you send. A field you leave out keeps its current value. To clear a field that accepts null, send null. Use the customer's id, not its customerNumber. taxRegistrations and addresses each replace the whole list, so include every item you want to keep. To change addresses one at a time, use createAddresses, updateAddresses, and deleteAddresses. External identifiers change through createExternalIdentifiers, updateExternalIdentifiers, and deleteExternalIdentifiers. To move the customer to another legal entity, send entityId. Documents created afterward use the new legal entity. Existing sales orders, invoices, and payments keep the legal entity they were created under. The customer keeps its payment term unless you also send paymentTermCode, and the request fails with PAYMENT_TERM_NOT_AVAILABLE_FOR_ENTITY when the new legal entity doesn't have that payment term. defaultCurrency doesn't change, so send it too if the new legal entity uses another currency. If you change name, customerType, or entityId, another customer with the same name, legal entity, and customer type makes the request fail with DUPLICATE_CUSTOMER. The response is empty. Read the updated record with GET accounting/customers/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the customer you want to update. | ## Request body Required, `application/json`: updateCustomerInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `entityId` | string | no | The id of the legal entity to move the customer to. Documents created afterward use this legal entity, and existing ones keep theirs. | | `name` | string | no | The name of the customer, as it appears on invoices. | | `customerType` | string | no | The customer type. Accepted values: 'default', 'partner'. Cannot be changed once the customer appears on a sales order. That change fails with CUSTOMER_TYPE_CHANGE_NOT_ALLOWED. 'endCustomer' is not supported and fails with END_CUSTOMER_NOT_SUPPORTED. | | `partnerType` | string \| null | no | The partner type, for a customer whose customerType is 'partner'. Accepted values: 'reseller', 'thirdPartyPayer'. | | `businessType` | string | no | The business type of the customer. Accepted values: 'company', 'individual'. | | `invoiceEmail` | string \| null | no | The email address invoices are sent to. Example: `"ap@example-robotics.com"`. | | `mainEmail` | string \| null | no | The email address of the customer's main contact. | | `taxExempt` | boolean | no | Whether the customer is exempt from tax. Setting it to false also clears exemptionNumber and taxExemptCode. | | `defaultCurrency` | string | no | The default currency for the customer's transactions, as a 3-letter ISO 4217 code. | | `segment` | string \| null | no | The market segment of the customer. Accepted values: 'SMB', 'MidMarket', 'Enterprise'. Ignored when businessType is 'individual', in this request or already set. Example: `"MidMarket"`. | | `exemptionNumber` | string \| null | no | The tax-exemption certificate number. Accepted only when taxExempt is true, in this request or already set. Otherwise the request fails with EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT. | | `paymentTermCode` | string | no | The code of a payment term on the customer's legal entity's list. When the request also changes entityId, the code must be on the new legal entity's list. A code that is not on the list fails with UNKNOWN_REFERENCE. See code on GET accounting/payment-terms. | | `parentId` | string | no | The id of the parent customer. An unknown id fails with UNKNOWN_REFERENCE. | | `partnerId` | string | no | The id of the partner this customer buys through. An unknown id fails with UNKNOWN_REFERENCE. | | `salesChannel` | string \| null | no | The sales channel for this customer. Accepted values: 'salesAssisted' (sold through a salesperson), 'selfServe'. | | `additionalContactEmail` | string \| null | no | Another email address for the customer, besides invoiceEmail and mainEmail. | | `mainContactName` | string \| null | no | The name of the customer's main contact. | | `preferredLanguage` | string \| null | no | The customer's preferred language. Accepted values: 'english'. | | `crmCustomerId` | string \| null | no | The customer's identifier in an external CRM system. | | `businessIdentificationNumber` | string \| null | no | The customer's business registration number. | | `invoiceConsolidation` | string \| null | no | How the customer's invoices are consolidated. Accepted values: 'customer', 'purchaseOrderNumber', 'salesArrangement'. | | `invoiceDeliveryMode` | string \| null | no | How invoices are delivered to the customer. Accepted values: 'manual', 'automated'. | | `isExemptFromCreditCardFee` | boolean | no | Whether the customer is exempt from credit-card fees. | | `billableTravelExpenseInvoiceDetail` | string | no | How billable travel and expense items appear on invoices. Accepted values: 'detailed', 'consolidated'. | | `status` | string | no | The status of the customer. Accepted values: 'active', 'inactive'. | | `taxExemptCode` | string \| null | no | The tax-exemption reason code. Cleared when taxExempt is false. | | `taxRegistrations` | array | no | The customer's tax registrations. This array replaces the customer's whole set of registrations. An entry matching an existing registration on country and type updates it. Any other entry is added. A registration not in the array is deleted. Send an empty array to remove them all. | | `taxRegistrations[].country` | string | yes | The country the registration applies in, as an ISO 3166-1 code. | | `taxRegistrations[].taxNumber` | string | yes | The tax identification number issued by the tax authority. | | `taxRegistrations[].type` | string | yes | The kind of tax registration. Accepted values: 'tax', 'salesTax', 'VAT', 'GST', 'EIN', 'TIN'. | | `taxRegistrations[].effectiveDate` | date | no | The date the registration takes effect. Use format YYYY-MM-DD. | | `taxRegistrations[].isActive` | boolean | no | Whether the registration is active. Only one active registration is allowed per country and tax type. Defaults to true. | | `payment` | object | no | The customer's link to a payment provider. `externalPaymentMethodId` requires `externalCustomerId`, in this request or already set. | | `payment.provider` | string | no | The name of a payment provider connected to your tenant. An unknown name fails with UNKNOWN_PAYMENT_PROVIDER, which lists the connected providers. Required when any other payment field is sent. Example: 'Stripe'. | | `payment.externalCustomerId` | string | no | The customer's identifier in the payment provider's system. | | `payment.externalPaymentMethodId` | string | no | The customer's default payment method in the payment provider's system. | | `addresses` | array | no | The customer's addresses. This array replaces the customer's whole address set, both types, and every address gets a new addressId. Send an empty array to remove every address. | | `addresses[].type` | string | yes | The address type. Accepted values: 'billTo', 'soldTo'. | | `addresses[].attention` | string | no | The person or department that mail to this address is for. | | `addresses[].isDefault` | boolean | no | Whether this entry is the default address of its type. | | `addresses[].line1` | string | yes | The first address line: street number and name, or PO box. | | `addresses[].line2` | string | no | The second address line: apartment, suite, unit, building, or floor. | | `addresses[].city` | string | yes | The city or locality. | | `addresses[].stateProvince` | string | no | The state, province, or region. | | `addresses[].country` | string | yes | The country, as a name or ISO 3166-1 code. | | `addresses[].zipCode` | string | yes | The postal or ZIP code. | | `createAddresses` | array | no | Addresses to add to the customer, alongside its existing ones. Mark at most one entry per type as the default with isDefault. Cannot be combined with `addresses`. That combination fails with INVALID_FIELD_VALUE. | | `createAddresses[].type` | string | yes | The address type. Accepted values: 'billTo', 'soldTo'. | | `createAddresses[].attention` | string | no | The person or department that mail to this address is for. | | `createAddresses[].isDefault` | boolean | no | Whether this entry is the default address of its type. | | `createAddresses[].line1` | string | yes | The first address line: street number and name, or PO box. | | `createAddresses[].line2` | string | no | The second address line: apartment, suite, unit, building, or floor. | | `createAddresses[].city` | string | yes | The city or locality. | | `createAddresses[].stateProvince` | string | no | The state, province, or region. | | `createAddresses[].country` | string | yes | The country, as a name or ISO 3166-1 code. | | `createAddresses[].zipCode` | string | yes | The postal or ZIP code. | | `updateAddresses` | array | no | Changes to existing addresses of the customer, each named by its addressId. A field you omit keeps its current value. Each address keeps its addressId. An addressId the customer does not have fails with ADDRESS_NOT_FOUND. Cannot be combined with `addresses`. That combination fails with INVALID_FIELD_VALUE. | | `updateAddresses[].addressId` | string | yes | The address to update. See addressId on GET accounting/customers/:id. | | `updateAddresses[].type` | string | no | The address type. Accepted values: 'billTo', 'soldTo'. A different value moves the address to that type. | | `updateAddresses[].attention` | string | no | The person or department that mail to this address is for. | | `updateAddresses[].isDefault` | boolean | no | Whether this entry is the default address of its type. | | `updateAddresses[].line1` | string | no | The first address line: street number and name, or PO box. | | `updateAddresses[].line2` | string | no | The second address line: apartment, suite, unit, building, or floor. | | `updateAddresses[].city` | string | no | The city or locality. | | `updateAddresses[].stateProvince` | string | no | The state, province, or region. | | `updateAddresses[].country` | string | no | The country, as a name or ISO 3166-1 code. | | `updateAddresses[].zipCode` | string | no | The postal or ZIP code. | | `deleteAddresses` | array | no | The addressIds of the addresses to remove from the customer. An addressId the customer does not have fails with ADDRESS_NOT_FOUND. When you remove the default address of a type, another address of that type becomes the default. Cannot be combined with `addresses`. That combination fails with INVALID_FIELD_VALUE. See addressId on GET accounting/customers/:id. | | `createExternalIdentifiers` | array | no | External identifiers to add. A fieldName the customer already has fails with DUPLICATE_EXTERNAL_IDENTIFIER. | | `createExternalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier. Example: 'netsuiteCustomerId'. | | `createExternalIdentifiers[].value` | string | yes | The identifier in the other system. | | `createExternalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. | | `updateExternalIdentifiers` | array | no | External identifiers to replace, matched by fieldName. A fieldName the customer does not have fails with EXTERNAL_IDENTIFIER_NOT_FOUND. | | `updateExternalIdentifiers[].fieldName` | string | yes | The configured field name of the identifier. Example: 'netsuiteCustomerId'. | | `updateExternalIdentifiers[].value` | string | yes | The identifier in the other system. | | `updateExternalIdentifiers[].externalUrl` | string | no | A link to the record in the other system. | | `deleteExternalIdentifiers` | array | no | The fieldNames of external identifiers to remove. A fieldName the customer does not have fails with EXTERNAL_IDENTIFIER_NOT_FOUND. | ```json { "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "customerType": "", "partnerType": "", "businessType": "", "invoiceEmail": "ap@example-robotics.com", "mainEmail": "", "taxExempt": true, "defaultCurrency": "", "segment": "MidMarket", "exemptionNumber": "", "paymentTermCode": "", "parentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "partnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesChannel": "", "additionalContactEmail": "", "mainContactName": "", "preferredLanguage": "", "crmCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "businessIdentificationNumber": "", "invoiceConsolidation": "", "invoiceDeliveryMode": "", "isExemptFromCreditCardFee": true, "billableTravelExpenseInvoiceDetail": "", "status": "", "taxExemptCode": "", "taxRegistrations": [ { "country": "", "taxNumber": "", "type": "", "effectiveDate": "2026-01-15", "isActive": true } ], "payment": { "provider": "", "externalCustomerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalPaymentMethodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "addresses": [ { "type": "", "attention": "", "isDefault": true, "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" } ], "createAddresses": [ { "type": "", "attention": "", "isDefault": true, "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" } ], "updateAddresses": [ { "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "type": "", "attention": "", "isDefault": true, "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" } ], "deleteAddresses": [ "" ], "createExternalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ], "updateExternalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ], "deleteExternalIdentifiers": [ "" ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `INVALID_ENTITY` | 400 | | `PAYMENT_TERM_NOT_AVAILABLE_FOR_ENTITY` | 400 | | `INVALID_FIELD_VALUE` | 400 | | `MULTIPLE_DEFAULT_ADDRESSES` | 400 | | `DUPLICATE_CUSTOMER` | 400 | | `END_CUSTOMER_NOT_SUPPORTED` | 400 | | `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 | | `DUPLICATE_EXTERNAL_IDENTIFIER` | 400 | | `EXTERNAL_IDENTIFIER_NOT_FOUND` | 400 | | `ADDRESS_NOT_FOUND` | 400 | | `CUSTOMER_TYPE_CHANGE_NOT_ALLOWED` | 400 | | `UNKNOWN_REFERENCE` | 400 | | `UNKNOWN_PAYMENT_PROVIDER` | 400 | | `PAYMENT_METHOD_REQUIRES_CUSTOMER_ID` | 400 | | `EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, INVALID_ENTITY, PAYMENT_TERM_NOT_AVAILABLE_FOR_ENTITY, INVALID_FIELD_VALUE, MULTIPLE_DEFAULT_ADDRESSES, DUPLICATE_CUSTOMER, END_CUSTOMER_NOT_SUPPORTED, UNKNOWN_EXTERNAL_IDENTIFIER, DUPLICATE_EXTERNAL_IDENTIFIER, EXTERNAL_IDENTIFIER_NOT_FOUND, ADDRESS_NOT_FOUND, CUSTOMER_TYPE_CHANGE_NOT_ALLOWED, UNKNOWN_REFERENCE, UNKNOWN_PAYMENT_PROVIDER, PAYMENT_METHOD_REQUIRES_CUSTOMER_ID, EXEMPTION_NUMBER_REQUIRES_TAX_EXEMPT - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Debit Memos Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingDebitMemos) A debit memo is an additional charge to a customer that settles a credit memo. ## Endpoints - [List debit memos](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/debitMemos.html): `GET /accounting/debit-memos` - [Create a debit memo](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createDebitMemo.html): `POST /accounting/debit-memos` - [Search debit memos](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchDebitMemo.html): `POST /accounting/debit-memos/search` - [Retrieve a debit memo](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getDebitMemo.html): `GET /accounting/debit-memos/{id}` - [Update a debit memo](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateDebitMemo.html): `PATCH /accounting/debit-memos/{id}` --- # List debit memos Operation ID: `debitMemos` Group: Accounting › Debit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/debitMemos) `GET /accounting/debit-memos` Read-only: Reads and returns; changes nothing. Lists debit memos. To filter by debit memo number, customer name or number, purchase order, customer message, or external identifier, use POST accounting/debit-memos/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `creditMemoId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `debitMemoDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `dueDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `taxDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `approvalStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `debitMemoStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `debitMemoType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `customerId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `paymentTerm` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `isMigrated` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `debitGlAccountId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `billToAddressId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `soldToAddressId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, creditMemoId, customerId, entityId, debitMemoDate, dueDate, taxDate, approvalStatus, debitMemoStatus, debitMemoType, currency, paymentTerm, isMigrated, lastModifiedDate - Selectable: id, debitMemoNumber, creditMemoId, creditMemoNumber, debitMemoDate, dueDate, taxDate, approvalStatus, debitMemoStatus, debitMemoType, currency, customerId, customerName, customerNumber, entityId, entityName, paymentTerm, purchaseOrder, customerMessage, externalDebitMemoId, isMigrated, debitGlAccountId, billToAddressId, soldToAddressId, subtotal, totalAmount, totalTax, totalAmountDebited, lineDiscounts, unappliedCreditAmount, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of debitMemosRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the debit memo. | | `debitMemoNumber` | string \| null | no | no | no | The debit memo number, for example 'DM-1'. Absent while none has been assigned. | | `creditMemoId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The source credit memo this debit memo settles. Absent on a standalone debit memo. See id on GET accounting/credit-memos/:id. | | `creditMemoNumber` | string \| null | no | no | no | The number of the source credit memo. Present whenever creditMemoId is. | | `debitMemoDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the debit memo was issued. Format YYYY-MM-DD. | | `dueDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the debit memo falls due. Format YYYY-MM-DD. Computed from debitMemoDate plus the payment term's days. | | `taxDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the debit memo's tax was determined on. Format YYYY-MM-DD. Defaults to debitMemoDate. | | `approvalStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The approval status of the debit memo. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Only an approved debit memo has financial effect. | | `debitMemoStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The payment and credit coverage status. Possible values: 'unpaid', 'isOverdue', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'disputed'. Absent before approval. | | `debitMemoType` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The reason key for the charge, for example 'orderEntryError'. The accepted keys are configured per tenant. | | `currency` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The currency of the debit memo. Format ISO 4217, for example 'USD'. It governs every amount on the record. | | `customerId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The customer the debit memo is issued to. See id on GET accounting/customers/:id. | | `customerName` | string \| null | no | no | no | The display name of the customer. | | `customerNumber` | string \| null | no | no | no | The customer number. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The legal entity that issued the debit memo. See id on GET base/entities. | | `entityName` | string \| null | no | no | no | The name of the issuing legal entity. | | `paymentTerm` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The payment-term key applied to the debit memo. See code on GET accounting/payment-terms. | | `purchaseOrder` | string \| null | no | no | no | The customer's own purchase-order reference. | | `customerMessage` | string \| null | no | no | no | A free-text message shown to the customer on the document. | | `externalDebitMemoId` | string \| null | no | no | no | The identifier of this debit memo in an external source system. | | `isMigrated` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the debit memo was loaded from a previous system. When true, it is excluded from dunning and its journal entries are auto-reversed. | | `debitGlAccountId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The general ledger account the journal entry debits for the debit memo's full amount. See id on GET accounting/accounts/:id. | | `billToAddressId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The bill-to address on the document. There is no endpoint for it. | | `soldToAddressId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The sold-to address on the document. There is no endpoint for it. | | `subtotal` | decimal \| null | no | no | no | The sum of the lines' pre-discount totals, in the debit memo currency. | | `totalAmount` | decimal \| null | no | no | no | The total including tax, in the debit memo currency. | | `totalTax` | decimal \| null | no | no | no | The total tax across all lines, in the debit memo currency. | | `totalAmountDebited` | decimal \| null | no | no | no | The gross amount debited, in the debit memo currency. | | `lineDiscounts` | decimal \| null | no | no | no | The sum of the prorated discount amounts across all lines, in the debit memo currency. | | `unappliedCreditAmount` | decimal \| null | no | no | no | The credit still unapplied on the source credit memo, in the debit memo currency. Absent on a standalone debit memo. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the debit memo record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "debitMemoNumber": "", "creditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "debitMemoDate": "2026-01-15", "dueDate": "2026-01-15", "taxDate": "2026-01-15", "approvalStatus": "", "debitMemoStatus": "", "debitMemoType": "", "currency": "", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "paymentTerm": "", "purchaseOrder": "", "customerMessage": "", "externalDebitMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isMigrated": true, "debitGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "billToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "soldToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "subtotal": "19.99", "totalAmount": "19.99", "totalTax": "19.99", "totalAmountDebited": "19.99", "lineDiscounts": "19.99", "unappliedCreditAmount": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create a debit memo Operation ID: `createDebitMemo` Group: Accounting › Debit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createDebitMemo) `POST /accounting/debit-memos` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates a debit memo to settle a source credit memo. The customer, legal entity, and currency come from the credit memo named by creditMemoId, not from the request. debitMemoType, billToAddressId, and soldToAddressId default to the credit memo's values when you leave them out. By default, the debit memo is approved as it's created, and its journal entry is created in the general ledger in the same request. In a tenant where debit memos go through approval, it's created as a draft instead, with no journal entry. The response contains the created debit memo, including its lines. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createDebitMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `creditMemoId` | string | yes | The source credit memo this debit memo settles. Required: a standalone debit memo cannot be created over this API. The customer, legal entity and currency come from this credit memo, not the request. See id on GET accounting/credit-memos/:id. Example: `"5a0f3e6d-81b7-4c8e-9b21-9c2a6a107d34"`. | | `debitMemoDate` | date | yes | The date the debit memo is issued. Use format YYYY-MM-DD. Must fall in an open accounting period unless isMigrated is true. Example: `"2026-08-01"`. | | `paymentTerm` | string | yes | The payment-term key to apply. When dueDate is omitted, it is computed from debitMemoDate plus this term's days. See code on GET accounting/payment-terms. Example: `"net30"`. | | `debitMemoType` | string | no | The reason key for the charge, for example 'orderEntryError'. Omit to inherit the source credit memo's reason key. When neither supplies one the call fails with MISSING_REQUIRED_FIELD. The accepted keys are configured per tenant. Example: `"orderEntryError"`. | | `dueDate` | date | no | The date the debit memo falls due. Use format YYYY-MM-DD. Omit to compute it from debitMemoDate plus the payment term's days. | | `taxDate` | date | no | The date to determine tax on. Use format YYYY-MM-DD. Omit to use debitMemoDate. | | `billToAddressId` | string | no | The bill-to address for the document. Omit to inherit the source credit memo's. When neither supplies one the call fails with MISSING_REQUIRED_FIELD. There is no endpoint for it. | | `soldToAddressId` | string | no | The sold-to address for the document. Omit to inherit the source credit memo's. When neither supplies one the call fails with MISSING_REQUIRED_FIELD. There is no endpoint for it. | | `purchaseOrder` | string | no | The customer's own purchase-order reference to record on the document. Example: `"PO-88213"`. | | `customerMessage` | string | no | A free-text message to show the customer on the document. | | `debitGlAccountId` | string | no | The general ledger account the journal entry debits for the debit memo's full amount. Omit to use the entity's customer receivables account. See id on GET accounting/accounts/:id. | | `isMigrated` | boolean | no | Whether the debit memo was loaded from a previous system. When true, it is excluded from dunning, its journal entries are auto-reversed, and the open-accounting-period check on debitMemoDate is skipped. Defaults to false. | | `lines` | array | yes | The lines of the debit memo. At least one is required, or the request fails with NO_LINES. Every amount is in the source credit memo's currency. Example: `[{"creditMemoLineId":"7d34c8e9-b21f-4a5c-9f3e-6d81b7d0aec3","description":"Re-debit of over-credited licence fee","quantity":"2","unitOfMeasure":"license","unitPrice":"250.00","totalPrice":"500.00"}]`. | | `lines[].creditMemoLineId` | string | yes | The source credit memo line this line debits. Must be a line of the credit memo named by creditMemoId, or the request fails with CREDIT_MEMO_LINE_NOT_FOUND. The debit memo line inherits its tax-exclusion state from that source line. See lines[].id on GET accounting/credit-memos/:id. | | `lines[].totalPrice` | decimal | yes | The line total excluding tax, in the source credit memo's currency. Must be greater than 0. | | `lines[].description` | string | no | A free-text line description, shown to the customer. | | `lines[].productId` | string | no | The product the line refers to. Omit on a free-text line. See id on GET accounting/products/:id. | | `lines[].quantity` | decimal | no | The quantity debited. A count, not an amount, so it has no currency. | | `lines[].unitOfMeasure` | string | no | The unit-of-measure key, for example 'each'. An unrecognized value fails with INVALID_ENUM_VALUE. | | `lines[].unitPrice` | decimal | no | The price per unit, in the source credit memo's currency. | | `lines[].adjustedUnitPrice` | decimal | no | The price per unit after adjustment, in the source credit memo's currency. | | `lines[].taxAmount` | decimal | no | The tax on the line, in the source credit memo's currency. Omit to have the tax engine determine it. | | `lines[].taxCode` | string | no | The tax code to apply to the line. | | `lines[].taxDate` | date | no | The date to determine the line's tax on. Use format YYYY-MM-DD. | | `lines[].taxMode` | any | no | How the line's prices are quoted for tax. One of: `"taxInclusive"`, `"taxExclusive"`. | | `lines[].taxExemptCode` | string | no | The customer-level tax-exempt code to apply to the line. | | `lines[].creditGlAccountId` | string | no | The line's credit general ledger account, the credit side of the journal entry. See id on GET accounting/accounts/:id. | | `lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Use format YYYY-MM-DD. | | `lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Use format YYYY-MM-DD. | ```json { "creditMemoId": "5a0f3e6d-81b7-4c8e-9b21-9c2a6a107d34", "debitMemoDate": "2026-08-01", "paymentTerm": "net30", "debitMemoType": "orderEntryError", "dueDate": "2026-01-15", "taxDate": "2026-01-15", "billToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "soldToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "purchaseOrder": "PO-88213", "customerMessage": "", "debitGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isMigrated": true, "lines": [ { "creditMemoLineId": "7d34c8e9-b21f-4a5c-9f3e-6d81b7d0aec3", "description": "Re-debit of over-credited licence fee", "quantity": "2", "unitOfMeasure": "license", "unitPrice": "250.00", "totalPrice": "500.00" } ] } ``` ## Response `200` Success Under `data` (createDebitMemoOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created debit memo. Example: `"3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45"`. | | `debitMemoNumber` | string | no | The debit memo number, for example 'DM-1'. Absent while none has been assigned. Example: `"DM-1042"`. | | `creditMemoId` | string | no | The source credit memo this debit memo settles. See id on GET accounting/credit-memos/:id. Example: `"5a0f3e6d-81b7-4c8e-9b21-9c2a6a107d34"`. | | `creditMemoNumber` | string | no | The number of the source credit memo. Present whenever creditMemoId is. | | `debitMemoDate` | date | yes | The date the debit memo was issued. Format YYYY-MM-DD. Example: `"2026-08-01"`. | | `dueDate` | date | no | The date the debit memo falls due. Format YYYY-MM-DD. As supplied, or computed from debitMemoDate plus the payment term's days. Example: `"2026-08-31"`. | | `taxDate` | date | no | The date the debit memo's tax was determined on. Format YYYY-MM-DD. Defaults to debitMemoDate. | | `approvalStatus` | string | no | The approval status of the new debit memo. Possible values: 'approved', 'draft'. It's 'approved' unless debit memos in your tenant go through approval. Example: `"approved"`. | | `debitMemoStatus` | string | no | The payment and credit coverage status. Possible values: 'unpaid', 'isOverdue', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'disputed'. Absent before approval. | | `debitMemoType` | string | yes | The reason key for the charge, as supplied or inherited from the source credit memo, for example 'orderEntryError'. Example: `"orderEntryError"`. | | `currency` | string | yes | The currency of the debit memo, taken from the source credit memo. Format ISO 4217, for example 'USD'. It governs every amount on the record. Example: `"USD"`. | | `customerId` | string | yes | The customer the debit memo is issued to, taken from the source credit memo. See id on GET accounting/customers/:id. Example: `"9c2a6a10-7d34-4c8e-9b21-5a0f3e6d81b7"`. | | `customerName` | string | no | The display name of the customer. | | `customerNumber` | string | no | The customer number. | | `entityId` | string | yes | The legal entity that issued the debit memo, taken from the source credit memo. See id on GET base/entities. Example: `"1a24d5e9-3b7d-4f80-9c6d-2e8f70b13a45"`. | | `entityName` | string | no | The name of the issuing legal entity. | | `paymentTerm` | string | yes | The payment-term key applied to the debit memo. See code on GET accounting/payment-terms. Example: `"net30"`. | | `purchaseOrder` | string | no | The customer's own purchase-order reference. | | `customerMessage` | string | no | A free-text message shown to the customer on the document. | | `externalDebitMemoId` | string | no | The identifier of this debit memo in an external source system. | | `isMigrated` | boolean | no | Whether the debit memo was loaded from a previous system. When true, it is excluded from dunning and its journal entries are auto-reversed. | | `debitGlAccountId` | string | no | The general ledger account the journal entry debits for the debit memo's full amount. Absent when you didn't send one. The journal entry then uses the entity's customer receivables account. See id on GET accounting/accounts/:id. | | `billToAddressId` | string | yes | The bill-to address on the document. There is no endpoint for it. Example: `"9f3e6d81-b7d0-4aec-8c3f-2e8f70b13a45"`. | | `soldToAddressId` | string | yes | The sold-to address on the document. There is no endpoint for it. Example: `"b7d0aec3-9f3e-4d81-8c3f-6d81b7d0aec3"`. | | `subtotal` | decimal | no | The sum of the lines' pre-discount totals, in the debit memo currency. | | `totalAmount` | decimal | no | The total including tax, in the debit memo currency. Example: `"500.00"`. | | `totalTax` | decimal | no | The total tax across all lines, in the debit memo currency. | | `totalAmountDebited` | decimal | no | The gross amount debited, in the debit memo currency. | | `lineDiscounts` | decimal | no | The sum of the prorated discount amounts across all lines, in the debit memo currency. | | `unappliedCreditAmount` | decimal | no | The credit still unapplied on the source credit memo, in the debit memo currency. Absent on a standalone debit memo. | | `lastModifiedDate` | date-time | yes | The date and time the debit memo record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Example: `"2026-08-01T09:12:44.000Z"`. | | `lines` | array | no | The lines of the created debit memo, each with its assigned id and line number. | | `lines[].id` | string | yes | The unique identifier for the debit memo line. | | `lines[].lineNumber` | string | no | The sequential line number within the debit memo. | | `lines[].description` | string | no | A free-text line description, shown to the customer. | | `lines[].productId` | string | no | The debited product. Absent on a free-text line. See id on GET accounting/products/:id. | | `lines[].creditMemoLineId` | string | no | The source credit memo line this line debits. See lines[].id on GET accounting/credit-memos/:id. | | `lines[].quantity` | decimal | no | The quantity debited. A count, not an amount, so it has no currency. | | `lines[].unitOfMeasure` | string | no | The unit-of-measure key, for example 'each'. | | `lines[].unitPrice` | decimal | no | The price per unit, in the debit memo currency. | | `lines[].adjustedUnitPrice` | decimal | no | The price per unit after adjustment, in the debit memo currency. | | `lines[].listPrice` | decimal | no | The list price per unit as it stood on the source credit memo line, in the debit memo currency. | | `lines[].listPriceTotal` | decimal | no | The pre-discount line total, in the debit memo currency. | | `lines[].discountAmount` | decimal | no | The discount from the source credit memo line, in the debit memo currency. | | `lines[].discountPercentage` | decimal | no | The discount as a percentage of the list price. A percentage, not an amount, so it has no currency. | | `lines[].totalPrice` | decimal | no | The line total excluding tax, in the debit memo currency. | | `lines[].amountDebited` | decimal | no | The amount debited for the line, in the debit memo currency. | | `lines[].totalAmountDue` | decimal | no | The amount of the source credit memo line still available to debit, in the debit memo currency. | | `lines[].taxAmount` | decimal | no | The tax on the line, in the debit memo currency. | | `lines[].taxCode` | string | no | The tax code applied to the line. | | `lines[].taxDate` | date | no | The date the line's tax was determined on. Format YYYY-MM-DD. | | `lines[].taxMode` | string | no | How the line's prices are quoted for tax. Possible values: 'TaxInclusive', 'TaxExclusive'. | | `lines[].taxExemptCode` | string | no | The customer-level tax-exempt code applied to the line. | | `lines[].creditGlAccountId` | string | no | The line's credit general ledger account, the credit side of the journal entry. See id on GET accounting/accounts/:id. | | `lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | ```json { "data": { "id": "3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45", "debitMemoNumber": "DM-1042", "creditMemoId": "5a0f3e6d-81b7-4c8e-9b21-9c2a6a107d34", "creditMemoNumber": "", "debitMemoDate": "2026-08-01", "dueDate": "2026-08-31", "taxDate": "2026-01-15", "approvalStatus": "approved", "debitMemoStatus": "", "debitMemoType": "orderEntryError", "currency": "USD", "customerId": "9c2a6a10-7d34-4c8e-9b21-5a0f3e6d81b7", "customerName": "", "customerNumber": "", "entityId": "1a24d5e9-3b7d-4f80-9c6d-2e8f70b13a45", "entityName": "", "paymentTerm": "net30", "purchaseOrder": "", "customerMessage": "", "externalDebitMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isMigrated": true, "debitGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "billToAddressId": "9f3e6d81-b7d0-4aec-8c3f-2e8f70b13a45", "soldToAddressId": "b7d0aec3-9f3e-4d81-8c3f-6d81b7d0aec3", "subtotal": "19.99", "totalAmount": "500.00", "totalTax": "19.99", "totalAmountDebited": "19.99", "lineDiscounts": "19.99", "unappliedCreditAmount": "19.99", "lastModifiedDate": "2026-08-01T09:12:44.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": "", "description": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoLineId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "quantity": "19.99", "unitOfMeasure": "", "unitPrice": "19.99", "adjustedUnitPrice": "19.99", "listPrice": "19.99", "listPriceTotal": "19.99", "discountAmount": "19.99", "discountPercentage": "19.99", "totalPrice": "19.99", "amountDebited": "19.99", "totalAmountDue": "19.99", "taxAmount": "19.99", "taxCode": "", "taxDate": "2026-01-15", "taxMode": "", "taxExemptCode": "", "creditGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `CREDIT_MEMO_NOT_FOUND` | 400 | | `CREDIT_MEMO_LINE_NOT_FOUND` | 400 | | `PRODUCT_NOT_FOUND` | 400 | | `ACCOUNT_NOT_FOUND` | 400 | | `ADDRESS_NOT_FOUND` | 400 | | `INVALID_AMOUNT` | 400 | | `INVALID_ENUM_VALUE` | 400 | | `NO_LINES` | 400 | | `MISSING_REQUIRED_FIELD` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: CREDIT_MEMO_NOT_FOUND, CREDIT_MEMO_LINE_NOT_FOUND, PRODUCT_NOT_FOUND, ACCOUNT_NOT_FOUND, ADDRESS_NOT_FOUND, INVALID_AMOUNT, INVALID_ENUM_VALUE, NO_LINES, MISSING_REQUIRED_FIELD - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search debit memos Operation ID: `searchDebitMemo` Group: Accounting › Debit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchDebitMemo) `POST /accounting/debit-memos/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches debit memos. Each debit memo in the results includes its lines. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchDebitMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A debit memo must match every filter you send. Omit to return all debit memos. | | `filters.id` | string | no | Limits results to the debit memo with this identifier. | | `filters.debitMemoNumber` | string | no | Limits results to the debit memo with this number, for example 'DM-1'. The match is exact. | | `filters.creditMemoId` | string | no | Limits results to debit memos settling this credit memo. See id on GET accounting/credit-memos/:id. | | `filters.creditMemoNumber` | string | no | Limits results to debit memos settling the credit memo with this number. The match is exact. | | `filters.customerId` | string | no | Limits results to debit memos issued to this customer. See id on GET accounting/customers/:id. | | `filters.customerName` | string | no | Limits results to debit memos whose customer name contains this text. The match is case-insensitive. | | `filters.customerNumber` | string | no | Limits results to debit memos with this customer number. The match is exact. | | `filters.entityId` | string | no | Limits results to debit memos issued by this legal entity. See id on GET base/entities. | | `filters.entityName` | string | no | Limits results to debit memos whose legal entity name contains this text. The match is case-insensitive. | | `filters.approvalStatus` | array | no | Limits results to debit memos in any of these approval statuses. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Any other value matches no debit memo. | | `filters.debitMemoStatus` | array | no | Limits results to debit memos in any of these coverage statuses. Possible values: 'unpaid', 'isOverdue', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'disputed'. Any other value matches no debit memo. | | `filters.debitMemoType` | array | no | Limits results to debit memos carrying any of these reason keys, for example 'orderEntryError'. The accepted keys are configured per tenant. | | `filters.currency` | string | no | Limits results to debit memos in this currency. Format ISO 4217, for example 'USD'. The match is exact. | | `filters.paymentTerm` | string | no | Limits results to debit memos with this payment-term key. The match is exact. See code on GET accounting/payment-terms. | | `filters.purchaseOrder` | string | no | Limits results to debit memos whose purchase-order reference contains this text. The match is case-insensitive. | | `filters.customerMessage` | string | no | Limits results to debit memos whose customer message contains this text. The match is case-insensitive. | | `filters.externalDebitMemoId` | string | no | Limits results to the debit memo with this external system identifier. The match is exact. | | `filters.isMigrated` | boolean | no | Limits results by whether the debit memo was loaded from a previous system. Omit to return both. | | `filters.debitMemoDate` | object | no | Limits results to debit memos issued in this date range. Omit either bound to leave that side open. | | `filters.debitMemoDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.debitMemoDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.dueDate` | object | no | Limits results to debit memos falling due in this date range. Omit either bound to leave that side open. | | `filters.dueDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.dueDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.lastModifiedDate` | object | no | Limits results to debit memos last changed in this range. Omit either bound to leave that side open. | | `filters.lastModifiedDate.start` | string | no | The inclusive lower bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.lastModifiedDate.end` | string | no | The inclusive upper bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `page` | object | no | The pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit for the first page. | | `page.size` | number | no | The number of debit memos per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "debitMemoNumber": "", "creditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "approvalStatus": [ "" ], "debitMemoStatus": [ "" ], "debitMemoType": [ "" ], "currency": "", "paymentTerm": "", "purchaseOrder": "", "customerMessage": "", "externalDebitMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isMigrated": true, "debitMemoDate": { "start": "2026-01-15", "end": "2026-01-15" }, "dueDate": { "start": "2026-01-15", "end": "2026-01-15" }, "lastModifiedDate": { "start": "", "end": "" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchDebitMemoOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The debit memos on this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the debit memo. | | `items[].debitMemoNumber` | string | no | The debit memo number, for example 'DM-1'. Absent while none has been assigned. | | `items[].creditMemoId` | string | no | The source credit memo this debit memo settles. Absent on a standalone debit memo. See id on GET accounting/credit-memos/:id. | | `items[].creditMemoNumber` | string | no | The number of the source credit memo. Present whenever creditMemoId is. | | `items[].debitMemoDate` | date | yes | The date the debit memo was issued. Format YYYY-MM-DD. | | `items[].dueDate` | date | no | The date the debit memo falls due. Format YYYY-MM-DD. Computed from debitMemoDate plus the payment term's days. | | `items[].taxDate` | date | no | The date the debit memo's tax was determined on. Format YYYY-MM-DD. Defaults to debitMemoDate. | | `items[].approvalStatus` | string | no | The approval status of the debit memo. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Only an approved debit memo has financial effect. | | `items[].debitMemoStatus` | string | no | The payment and credit coverage status. Possible values: 'unpaid', 'isOverdue', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'disputed'. Absent before approval. | | `items[].debitMemoType` | string | yes | The reason key for the charge, for example 'orderEntryError'. The accepted keys are configured per tenant. | | `items[].currency` | string | yes | The currency of the debit memo. Format ISO 4217, for example 'USD'. It governs every amount on the record. | | `items[].customerId` | string | yes | The customer the debit memo is issued to. See id on GET accounting/customers/:id. | | `items[].customerName` | string | no | The display name of the customer. | | `items[].customerNumber` | string | no | The customer number. | | `items[].entityId` | string | yes | The legal entity that issued the debit memo. See id on GET base/entities. | | `items[].entityName` | string | no | The name of the issuing legal entity. | | `items[].paymentTerm` | string | yes | The payment-term key applied to the debit memo. See code on GET accounting/payment-terms. | | `items[].purchaseOrder` | string | no | The customer's own purchase-order reference. | | `items[].customerMessage` | string | no | A free-text message shown to the customer on the document. | | `items[].externalDebitMemoId` | string | no | The identifier of this debit memo in an external source system. | | `items[].isMigrated` | boolean | no | Whether the debit memo was loaded from a previous system. When true, it is excluded from dunning and its journal entries are auto-reversed. | | `items[].debitGlAccountId` | string | no | The general ledger account the journal entry debits for the debit memo's full amount. See id on GET accounting/accounts/:id. | | `items[].billToAddressId` | string | yes | The bill-to address on the document. There is no endpoint for it. | | `items[].soldToAddressId` | string | yes | The sold-to address on the document. There is no endpoint for it. | | `items[].subtotal` | decimal | no | The sum of the lines' pre-discount totals, in the debit memo currency. | | `items[].totalAmount` | decimal | no | The total including tax, in the debit memo currency. | | `items[].totalTax` | decimal | no | The total tax across all lines, in the debit memo currency. | | `items[].totalAmountDebited` | decimal | no | The gross amount debited, in the debit memo currency. | | `items[].lineDiscounts` | decimal | no | The sum of the prorated discount amounts across all lines, in the debit memo currency. | | `items[].unappliedCreditAmount` | decimal | no | The credit still unapplied on the source credit memo, in the debit memo currency. Absent on a standalone debit memo. | | `items[].lastModifiedDate` | date-time | yes | The date and time the debit memo record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].lines` | array | no | The lines of the debit memo, each debiting one line of the source credit memo. An empty array when the debit memo has no lines. All line amounts are in the debit memo currency. | | `items[].lines[].id` | string | yes | The unique identifier for the debit memo line. | | `items[].lines[].lineNumber` | string | no | The sequential line number within the debit memo. | | `items[].lines[].description` | string | no | A free-text line description, shown to the customer. | | `items[].lines[].productId` | string | no | The debited product. Absent on a free-text line. See id on GET accounting/products/:id. | | `items[].lines[].creditMemoLineId` | string | no | The source credit memo line this line debits. See lines[].id on GET accounting/credit-memos/:id. | | `items[].lines[].quantity` | decimal | no | The quantity debited. A count, not an amount, so it has no currency. | | `items[].lines[].unitOfMeasure` | string | no | The unit-of-measure key, for example 'each'. | | `items[].lines[].unitPrice` | decimal | no | The price per unit, in the debit memo currency. | | `items[].lines[].adjustedUnitPrice` | decimal | no | The price per unit after adjustment, in the debit memo currency. | | `items[].lines[].listPrice` | decimal | no | The list price per unit as it stood on the source credit memo line, in the debit memo currency. | | `items[].lines[].listPriceTotal` | decimal | no | The pre-discount line total, in the debit memo currency. | | `items[].lines[].discountAmount` | decimal | no | The discount from the source credit memo line, in the debit memo currency. | | `items[].lines[].discountPercentage` | decimal | no | The discount as a percentage of the list price. A percentage, not an amount, so it has no currency. | | `items[].lines[].totalPrice` | decimal | no | The line total excluding tax, in the debit memo currency. | | `items[].lines[].amountDebited` | decimal | no | The amount debited for the line, in the debit memo currency. | | `items[].lines[].totalAmountDue` | decimal | no | The amount of the source credit memo line still available to debit, in the debit memo currency. | | `items[].lines[].taxAmount` | decimal | no | The tax on the line, in the debit memo currency. | | `items[].lines[].taxCode` | string | no | The tax code applied to the line. | | `items[].lines[].taxDate` | date | no | The date the line's tax was determined on. Format YYYY-MM-DD. | | `items[].lines[].taxMode` | string | no | How the line's prices are quoted for tax. Possible values: 'TaxInclusive', 'TaxExclusive'. | | `items[].lines[].taxExemptCode` | string | no | The customer-level tax-exempt code applied to the line. | | `items[].lines[].creditGlAccountId` | string | no | The line's credit general ledger account, the credit side of the journal entry. See id on GET accounting/accounts/:id. | | `items[].lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `items[].lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "debitMemoNumber": "", "creditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "debitMemoDate": "2026-01-15", "dueDate": "2026-01-15", "taxDate": "2026-01-15", "approvalStatus": "", "debitMemoStatus": "", "debitMemoType": "", "currency": "", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "paymentTerm": "", "purchaseOrder": "", "customerMessage": "", "externalDebitMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isMigrated": true, "debitGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "billToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "soldToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "subtotal": "19.99", "totalAmount": "19.99", "totalTax": "19.99", "totalAmountDebited": "19.99", "lineDiscounts": "19.99", "unappliedCreditAmount": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": "", "description": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoLineId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "quantity": "19.99", "unitOfMeasure": "", "unitPrice": "19.99", "adjustedUnitPrice": "19.99", "listPrice": "19.99", "listPriceTotal": "19.99", "discountAmount": "19.99", "discountPercentage": "19.99", "totalPrice": "19.99", "amountDebited": "19.99", "totalAmountDue": "19.99", "taxAmount": "19.99", "taxCode": "", "taxDate": "2026-01-15", "taxMode": "", "taxExemptCode": "", "creditGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `INVALID_TIMESTAMP` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_TIMESTAMP - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a debit memo Operation ID: `getDebitMemo` Group: Accounting › Debit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getDebitMemo) `GET /accounting/debit-memos/{id}` Read-only: Reads and returns; changes nothing. Retrieves a debit memo. Use the debit memo's id, not its debitMemoNumber. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the debit memo you want to retrieve. | ## Response `200` Success Under `data` (getDebitMemoOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the debit memo. | | `debitMemoNumber` | string | no | The debit memo number, for example 'DM-1'. Absent while none has been assigned. | | `creditMemoId` | string | no | The source credit memo this debit memo settles. Absent on a standalone debit memo. See id on GET accounting/credit-memos/:id. | | `creditMemoNumber` | string | no | The number of the source credit memo. Present whenever creditMemoId is. | | `debitMemoDate` | date | yes | The date the debit memo was issued. Format YYYY-MM-DD. | | `dueDate` | date | no | The date the debit memo falls due. Format YYYY-MM-DD. Computed from debitMemoDate plus the payment term's days. | | `taxDate` | date | no | The date the debit memo's tax was determined on. Format YYYY-MM-DD. Defaults to debitMemoDate. | | `approvalStatus` | string | no | The approval status of the debit memo. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Only an approved debit memo has financial effect. | | `debitMemoStatus` | string | no | The payment and credit coverage status. Possible values: 'unpaid', 'isOverdue', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'disputed'. Absent before approval. | | `debitMemoType` | string | yes | The reason key for the charge, for example 'orderEntryError'. The accepted keys are configured per tenant. | | `currency` | string | yes | The currency of the debit memo. Format ISO 4217, for example 'USD'. It governs every amount on the record. | | `customerId` | string | yes | The customer the debit memo is issued to. See id on GET accounting/customers/:id. | | `customerName` | string | no | The display name of the customer. | | `customerNumber` | string | no | The customer number. | | `entityId` | string | yes | The legal entity that issued the debit memo. See id on GET base/entities. | | `entityName` | string | no | The name of the issuing legal entity. | | `paymentTerm` | string | yes | The payment-term key applied to the debit memo. See code on GET accounting/payment-terms. | | `purchaseOrder` | string | no | The customer's own purchase-order reference. | | `customerMessage` | string | no | A free-text message shown to the customer on the document. | | `externalDebitMemoId` | string | no | The identifier of this debit memo in an external source system. | | `isMigrated` | boolean | no | Whether the debit memo was loaded from a previous system. When true, it is excluded from dunning and its journal entries are auto-reversed. | | `debitGlAccountId` | string | no | The general ledger account the journal entry debits for the debit memo's full amount. See id on GET accounting/accounts/:id. | | `billToAddressId` | string | yes | The bill-to address on the document. There is no endpoint for it. | | `soldToAddressId` | string | yes | The sold-to address on the document. There is no endpoint for it. | | `subtotal` | decimal | no | The sum of the lines' pre-discount totals, in the debit memo currency. | | `totalAmount` | decimal | no | The total including tax, in the debit memo currency. | | `totalTax` | decimal | no | The total tax across all lines, in the debit memo currency. | | `totalAmountDebited` | decimal | no | The gross amount debited, in the debit memo currency. | | `lineDiscounts` | decimal | no | The sum of the prorated discount amounts across all lines, in the debit memo currency. | | `unappliedCreditAmount` | decimal | no | The credit still unapplied on the source credit memo, in the debit memo currency. Absent on a standalone debit memo. | | `lastModifiedDate` | date-time | yes | The date and time the debit memo record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `lines` | array | no | The lines of the debit memo, each debiting one line of the source credit memo. An empty array when the debit memo has no lines. All line amounts are in the debit memo currency. | | `lines[].id` | string | yes | The unique identifier for the debit memo line. | | `lines[].lineNumber` | string | no | The sequential line number within the debit memo. | | `lines[].description` | string | no | A free-text line description, shown to the customer. | | `lines[].productId` | string | no | The debited product. Absent on a free-text line. See id on GET accounting/products/:id. | | `lines[].creditMemoLineId` | string | no | The source credit memo line this line debits. See lines[].id on GET accounting/credit-memos/:id. | | `lines[].quantity` | decimal | no | The quantity debited. A count, not an amount, so it has no currency. | | `lines[].unitOfMeasure` | string | no | The unit-of-measure key, for example 'each'. | | `lines[].unitPrice` | decimal | no | The price per unit, in the debit memo currency. | | `lines[].adjustedUnitPrice` | decimal | no | The price per unit after adjustment, in the debit memo currency. | | `lines[].listPrice` | decimal | no | The list price per unit as it stood on the source credit memo line, in the debit memo currency. | | `lines[].listPriceTotal` | decimal | no | The pre-discount line total, in the debit memo currency. | | `lines[].discountAmount` | decimal | no | The discount from the source credit memo line, in the debit memo currency. | | `lines[].discountPercentage` | decimal | no | The discount as a percentage of the list price. A percentage, not an amount, so it has no currency. | | `lines[].totalPrice` | decimal | no | The line total excluding tax, in the debit memo currency. | | `lines[].amountDebited` | decimal | no | The amount debited for the line, in the debit memo currency. | | `lines[].totalAmountDue` | decimal | no | The amount of the source credit memo line still available to debit, in the debit memo currency. | | `lines[].taxAmount` | decimal | no | The tax on the line, in the debit memo currency. | | `lines[].taxCode` | string | no | The tax code applied to the line. | | `lines[].taxDate` | date | no | The date the line's tax was determined on. Format YYYY-MM-DD. | | `lines[].taxMode` | string | no | How the line's prices are quoted for tax. Possible values: 'TaxInclusive', 'TaxExclusive'. | | `lines[].taxExemptCode` | string | no | The customer-level tax-exempt code applied to the line. | | `lines[].creditGlAccountId` | string | no | The line's credit general ledger account, the credit side of the journal entry. See id on GET accounting/accounts/:id. | | `lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "debitMemoNumber": "", "creditMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoNumber": "", "debitMemoDate": "2026-01-15", "dueDate": "2026-01-15", "taxDate": "2026-01-15", "approvalStatus": "", "debitMemoStatus": "", "debitMemoType": "", "currency": "", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "paymentTerm": "", "purchaseOrder": "", "customerMessage": "", "externalDebitMemoId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isMigrated": true, "debitGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "billToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "soldToAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "subtotal": "19.99", "totalAmount": "19.99", "totalTax": "19.99", "totalAmountDebited": "19.99", "lineDiscounts": "19.99", "unappliedCreditAmount": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": "", "description": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "creditMemoLineId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "quantity": "19.99", "unitOfMeasure": "", "unitPrice": "19.99", "adjustedUnitPrice": "19.99", "listPrice": "19.99", "listPriceTotal": "19.99", "discountAmount": "19.99", "discountPercentage": "19.99", "totalPrice": "19.99", "amountDebited": "19.99", "totalAmountDue": "19.99", "taxAmount": "19.99", "taxCode": "", "taxDate": "2026-01-15", "taxMode": "", "taxExemptCode": "", "creditGlAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update a debit memo Operation ID: `updateDebitMemo` Group: Accounting › Debit Memos [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateDebitMemo) `PATCH /accounting/debit-memos/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates a debit memo with the fields you send. A field you leave out keeps its current value. Use the debit memo's id, not its debitMemoNumber. You can change only customerMessage, purchaseOrder, and paymentTerm. This endpoint can't change the dates, amounts, lines, general ledger account, or addresses of a debit memo. The response is empty. Read the updated record with GET accounting/debit-memos/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the debit memo you want to update. | ## Request body Required, `application/json`: updateDebitMemoInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `customerMessage` | string | no | A new free-text message to show the customer on the document. Pass an empty string to clear it. Example: `"Revised: covers the re-debited licence fee only."`. | | `purchaseOrder` | string | no | A new purchase-order reference from the customer. Pass an empty string to clear it. Example: `"PO-88214"`. | | `paymentTerm` | string | no | A new payment-term key to record on the document. This does not recompute dueDate. See code on GET accounting/payment-terms. | ```json { "customerMessage": "Revised: covers the re-debited licence fee only.", "purchaseOrder": "PO-88214", "paymentTerm": "" } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Employee Advances Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingEmployeeAdvances) An employee advance is money paid to an employee in advance. The money is requested by the employee and paid once the request is approved. Each request belongs to the employee who created it and to that employee's legal entity. A request goes through your tenant's approval policy, from 'pendingApproval' to 'approved' or 'rejected'. Issuing an approved request creates the outbound payment and its journal entry and sets the status to 'issued'. An expense report can later settle the advance. ## Endpoints - [List employee advance requests](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/employeeAdvances.html): `GET /accounting/employee-advances` - [Create an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createEmployeeAdvanceRequest.html): `POST /accounting/employee-advances` - [Search employee advance requests](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchEmployeeAdvanceRequests.html): `POST /accounting/employee-advances/search` - [Retrieve an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getEmployeeAdvanceRequest.html): `GET /accounting/employee-advances/{id}` - [Update and resubmit an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateEmployeeAdvanceRequest.html): `PATCH /accounting/employee-advances/{id}` - [Delete an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/deleteEmployeeAdvanceRequest.html): `DELETE /accounting/employee-advances/{id}` - [Approve an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/approveEmployeeAdvanceRequest.html): `POST /accounting/employee-advances/{id}/approve` - [Issue an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/issueEmployeeAdvanceRequest.html): `POST /accounting/employee-advances/{id}/issue` - [Reject an employee advance request](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/rejectEmployeeAdvanceRequest.html): `POST /accounting/employee-advances/{id}/reject` --- # List employee advance requests Operation ID: `employeeAdvances` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/employeeAdvances) `GET /accounting/employee-advances` Read-only: Reads and returns; changes nothing. Lists employee advance requests. Each item is a summary of the request, with its amount, currency, status, and request date. For employeeName, the trip dates, notes, the decision note, and the issued payment, use GET accounting/employee-advances/:id or POST accounting/employee-advances/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `advanceRequestNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `employeeId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `categoryId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `requestDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, advanceRequestNumber, status, requestDate, amount, lastModifiedDate - Selectable: id, advanceRequestNumber, employeeId, entityId, categoryId, amount, currency, purpose, status, requestDate, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of employeeAdvancesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Stable, platform-assigned id (a UUID) of the advance request — the public identifier used by GET accounting/employee-advances/:id. Filter e.g. filter[id][in]=,. | | `advanceRequestNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | System-generated document number (ADV-...). Filter target: filter[advanceRequestNumber][eq]=ADV-1042. | | `employeeId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Id (a UUID) of the requesting employee. Filter e.g. filter[employeeId][eq]=. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Id (a UUID) of the entity the request belongs to. | | `categoryId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | Id (a UUID) of the AdvancePaymentCategory this request draws from. | | `amount` | decimal \| null | no | no | yes | Requested amount, in currency. Orderable, e.g. sort=-amount. | | `currency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | ISO 4217 three-letter currency code that governs amount. Filter target: filter[currency][in]=USD,EUR. | | `purpose` | string \| null | no | no | no | What the advance is for. | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Lifecycle status: draft, pendingApproval, approved, rejected or issued. Filter target: filter[status][eq]=pendingApproval. | | `requestDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Date the request was submitted, as YYYY-MM-DD. Filter target: filter[requestDate][gte]=2026-01-01. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Instant the request last changed, as ISO 8601 UTC (YYYY-MM-DDTHH:mm:ss.sssZ). filter[lastModifiedDate][gte]=… plus sort=lastModifiedDate is a complete incremental-sync cursor. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "advanceRequestNumber": "", "employeeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amount": "19.99", "currency": "", "purpose": "", "status": "", "requestDate": "2026-01-15", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create an employee advance request Operation ID: `createEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createEmployeeAdvanceRequest) `POST /accounting/employee-advances` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates an employee advance request for the employee who sends it. The advance is in that employee's legal entity. There is no field to name another employee. The response is only the new request's id. The request is created in 'pendingApproval' and goes straight into your tenant's approval policy. The request can already be approved by the time the response arrives, for example when that employee is also the policy's approver. No payment or journal entry is created until the approved request is issued with POST accounting/employee-advances/:id/issue. Read the request's status with GET accounting/employee-advances/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createEmployeeAdvanceRequestInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `categoryId` | string | yes | Id (a UUID) of the AdvancePaymentCategory this request draws from. Must belong to the calling employee's own entity. Example: `"5c8e2f14-9a3b-4d67-8e21-1b4c6a9d3e02"`. | | `amount` | decimal | yes | Requested amount, in the calling employee's entity functional currency. Must be greater than zero. Stored as the request's single purpose line. Example: `"750.00"`. | | `purpose` | string | yes | What the advance is for, e.g. the trip or event. Required, non-blank. Stored as the request's single purpose line. Example: `"Client site visit — flights and hotel"`. | | `tripStart` | date | no | First day of the trip the advance covers. Optional. Example: `"2026-09-10"`. | | `tripEnd` | date | no | Last day of the trip the advance covers. Must not be before tripStart. Optional. Example: `"2026-09-13"`. | | `notes` | string | no | Free-text notes from the requester. Optional. | | `requestDate` | date | no | The requester's own calendar day, as observed client-side. Omit to default to the appserver's current day. | | `attachments` | array | no | Files to attach to the request. Each names an already-uploaded file by its id. A tenant may require at least one; see ATTACHMENT_REQUIRED. Example: `[{"fileName":"itinerary.pdf","fileId":"3f9c1e2a-7b4d-4e6f-9a1c-2d5e8f0b6c3a"}]`. | | `attachments[].fileName` | string | yes | Display name of the attached file. | | `attachments[].fileId` | string | yes | Id of the already-uploaded file. | ```json { "categoryId": "5c8e2f14-9a3b-4d67-8e21-1b4c6a9d3e02", "amount": "750.00", "purpose": "Client site visit — flights and hotel", "tripStart": "2026-09-10", "tripEnd": "2026-09-13", "notes": "", "requestDate": "2026-01-15", "attachments": [ { "fileName": "itinerary.pdf", "fileId": "3f9c1e2a-7b4d-4e6f-9a1c-2d5e8f0b6c3a" } ] } ``` ## Response `200` Success Under `data` (createEmployeeAdvanceRequestOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Id (a UUID) assigned to the newly created advance request. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ```json { "data": { "id": "b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `CATEGORY_NOT_FOUND` | 400 | | `CATEGORY_WRONG_ENTITY` | 400 | | `NO_FUNCTIONAL_CURRENCY` | 400 | | `INVALID_AMOUNT` | 400 | | `MISSING_PURPOSE` | 400 | | `PURPOSE_NOT_FOUND` | 400 | | `INVALID_TRIP_DATES` | 400 | | `ATTACHMENT_REQUIRED` | 400 | | `ADVANCE_LIMIT_EXCEEDED` | 400 | | `UNEXPECTED_ERROR` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: CATEGORY_NOT_FOUND, CATEGORY_WRONG_ENTITY, NO_FUNCTIONAL_CURRENCY, INVALID_AMOUNT, MISSING_PURPOSE, PURPOSE_NOT_FOUND, INVALID_TRIP_DATES, ATTACHMENT_REQUIRED, ADVANCE_LIMIT_EXCEEDED, UNEXPECTED_ERROR - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search employee advance requests Operation ID: `searchEmployeeAdvanceRequests` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchEmployeeAdvanceRequests) `POST /accounting/employee-advances/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches employee advance requests. The filters are status, employee, legal entity, category, and request number. Each match is returned in full, newest first. Unlike GET accounting/employee-advances, each item includes employeeName, the trip dates, notes, the decision note, and the issued payment. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchEmployeeAdvanceRequestsInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | Search filters, nested as one object. All are optional and are ANDed; omit the whole object to match every request. Example: `{"status":"pendingApproval"}`. | | `filters.status` | string | no | Restrict to requests in this lifecycle status: draft, pendingApproval, approved, rejected or issued. | | `filters.employeeId` | string | no | Restrict to requests from this employee (a UUID). A uuid that matches no employee returns an empty page, not an error. | | `filters.entityId` | string | no | Restrict to requests in this entity (a UUID). A uuid that matches no entity returns an empty page, not an error. | | `filters.categoryId` | string | no | Restrict to requests drawing from this AdvancePaymentCategory (a UUID). A uuid that matches no category returns an empty page, not an error. | | `filters.advanceRequestNumber` | string | no | Exact match on the request's document number (ADV-...). | | `page` | object | no | Pagination controls. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | Opaque forward cursor from a previous response's pagination.nextCursor; omit for the first page. | | `page.size` | number | no | Maximum number of requests to return. Defaults to 50, capped at 200. | ```json { "filters": { "status": "pendingApproval" }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchEmployeeAdvanceRequestsOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | Matching advance requests for this page, newest first. | | `items[].id` | string | yes | | | `items[].advanceRequestNumber` | string | no | | | `items[].employeeId` | string | yes | | | `items[].employeeName` | string | no | | | `items[].entityId` | string | yes | | | `items[].categoryId` | string | yes | | | `items[].amount` | object | yes | | | `items[].amount.amount` | string | yes | | | `items[].amount.currency` | string | yes | | | `items[].purpose` | string | yes | | | `items[].tripStart` | date | no | | | `items[].tripEnd` | date | no | | | `items[].notes` | string | no | | | `items[].status` | string | yes | | | `items[].requestDate` | date | yes | | | `items[].decisionNote` | string | no | | | `items[].outboundPaymentId` | string | no | | | `items[].outboundPaymentHeaderNumber` | string | no | | | `items[].outboundPaymentApplicationStatus` | string | no | | | `pagination` | object | yes | Pagination cursor for this page. | | `pagination.nextCursor` | string | no | Opaque cursor to pass as page.cursor for the next page; absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "advanceRequestNumber": "", "employeeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "employeeName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amount": { "amount": "", "currency": "" }, "purpose": "", "tripStart": "2026-01-15", "tripEnd": "2026-01-15", "notes": "", "status": "", "requestDate": "2026-01-15", "decisionNote": "", "outboundPaymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "outboundPaymentHeaderNumber": "", "outboundPaymentApplicationStatus": "" } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an employee advance request Operation ID: `getEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getEmployeeAdvanceRequest) `GET /accounting/employee-advances/{id}` Read-only: Reads and returns; changes nothing. Retrieves an employee advance request. Use the request's id, not its advanceRequestNumber. The response has the request's amount, status, and the approver's decision note. Once the request has been issued, it also has the payment made for it. POST accounting/employee-advances/search returns the same fields for each request. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | Id of the advance request to fetch (a UUID); bound from the :id path segment. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Response `200` Success Under `data` (getEmployeeAdvanceRequestOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | Id of the advance request (a UUID). | | `advanceRequestNumber` | string | no | System-generated document number (ADV-...). | | `employeeId` | string | yes | Id (a UUID) of the requesting employee. | | `employeeName` | string | no | Display name of the requesting employee. | | `entityId` | string | yes | Id (a UUID) of the entity the request belongs to. | | `categoryId` | string | yes | Id (a UUID) of the AdvancePaymentCategory this request draws from. | | `amount` | object | yes | Requested amount: the total of the request's purpose lines. | | `amount.amount` | string | yes | | | `amount.currency` | string | yes | | | `purpose` | string | yes | What the advance is for. A request split across several purposes lists them comma-separated, e.g. 'Travel, Per Diem'. | | `tripStart` | date | no | First day of the trip the advance covers. | | `tripEnd` | date | no | Last day of the trip the advance covers. | | `notes` | string | no | Free-text notes from the requester. | | `status` | string | yes | Lifecycle status: draft, pendingApproval, approved, rejected or issued. | | `requestDate` | date | yes | Date the request was submitted. | | `decisionNote` | string | no | Approver note recorded on approval or rejection. | | `outboundPaymentId` | string | no | Id (a UUID) of the OutboundPaymentHeaderBase created when this request was issued, if any. | | `outboundPaymentHeaderNumber` | string | no | Document number of the issued payment, if any. | | `outboundPaymentApplicationStatus` | string | no | Application status of the issued payment (unapplied/partiallyApplied/applied), if any. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "advanceRequestNumber": "", "employeeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "employeeName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amount": { "amount": "", "currency": "" }, "purpose": "", "tripStart": "2026-01-15", "tripEnd": "2026-01-15", "notes": "", "status": "", "requestDate": "2026-01-15", "decisionNote": "", "outboundPaymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "outboundPaymentHeaderNumber": "", "outboundPaymentApplicationStatus": "" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update and resubmit an employee advance request Operation ID: `updateEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateEmployeeAdvanceRequest) `PATCH /accounting/employee-advances/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates and resubmits an employee advance request. Use the request's id, not its advanceRequestNumber. This replaces the whole advance request. categoryId, amount, and purpose are required. A field you leave out goes back to its default: tripStart, tripEnd, and notes are cleared. attachments works differently: leave it out to keep the current attachments. An attachments list you send replaces the whole list, so include every attachment you want to keep. The request's status returns to 'pendingApproval', and any approval progress is reset. Only the employee who made the request can change it. For anyone else, the update fails with NOT_OWNER. A request in 'draft', 'pendingApproval', or 'rejected' can be changed. An 'approved' or 'issued' request can't, and the update fails with NOT_EDITABLE. The response is empty. The resubmitted request can already be approved by the time the response arrives, so read its status with GET accounting/employee-advances/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the advance request to update. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Request body Required, `application/json`: updateEmployeeAdvanceRequestInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `categoryId` | string | yes | Id (a UUID) of the AdvancePaymentCategory this request draws from. Must belong to the calling employee's own entity. Example: `"5c8e2f14-9a3b-4d67-8e21-1b4c6a9d3e02"`. | | `amount` | decimal | yes | Requested amount, in the calling employee's entity functional currency. Must be greater than zero. Stored as the request's single purpose line. Example: `"900.00"`. | | `purpose` | string | yes | What the advance is for. Required, non-blank. Stored as the request's single purpose line. Example: `"Client site visit — flights, hotel and ground transport"`. | | `tripStart` | date | no | First day of the trip the advance covers. Optional. | | `tripEnd` | date | no | Last day of the trip the advance covers. Must not be before tripStart. Optional. | | `notes` | string | no | Free-text notes from the requester. Optional. | | `attachments` | array | no | Files to attach to the request. Replaces the stored set entirely (this is a full replace, not a partial patch). A tenant may require at least one; see ATTACHMENT_REQUIRED. Example: `[{"fileName":"itinerary.pdf","fileId":"3f9c1e2a-7b4d-4e6f-9a1c-2d5e8f0b6c3a"}]`. | | `attachments[].fileName` | string | yes | Display name of the attached file. | | `attachments[].fileId` | string | yes | Id of the already-uploaded file. | ```json { "categoryId": "5c8e2f14-9a3b-4d67-8e21-1b4c6a9d3e02", "amount": "900.00", "purpose": "Client site visit — flights, hotel and ground transport", "tripStart": "2026-01-15", "tripEnd": "2026-01-15", "notes": "", "attachments": [ { "fileName": "itinerary.pdf", "fileId": "3f9c1e2a-7b4d-4e6f-9a1c-2d5e8f0b6c3a" } ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `REQUEST_NOT_FOUND` | 400 | | `NOT_OWNER` | 400 | | `NOT_EDITABLE` | 400 | | `CATEGORY_NOT_FOUND` | 400 | | `CATEGORY_WRONG_ENTITY` | 400 | | `NO_FUNCTIONAL_CURRENCY` | 400 | | `INVALID_AMOUNT` | 400 | | `MISSING_PURPOSE` | 400 | | `PURPOSE_NOT_FOUND` | 400 | | `INVALID_TRIP_DATES` | 400 | | `ATTACHMENT_REQUIRED` | 400 | | `ADVANCE_LIMIT_EXCEEDED` | 400 | | `UNEXPECTED_ERROR` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: REQUEST_NOT_FOUND, NOT_OWNER, NOT_EDITABLE, CATEGORY_NOT_FOUND, CATEGORY_WRONG_ENTITY, NO_FUNCTIONAL_CURRENCY, INVALID_AMOUNT, MISSING_PURPOSE, PURPOSE_NOT_FOUND, INVALID_TRIP_DATES, ATTACHMENT_REQUIRED, ADVANCE_LIMIT_EXCEEDED, UNEXPECTED_ERROR - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Delete an employee advance request Operation ID: `deleteEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/deleteEmployeeAdvanceRequest) `DELETE /accounting/employee-advances/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Deletes an employee advance request. The request's attachments, approval actions, policy snapshot, and approver notification log are deleted with it. An issued request, or one with a payment linked to it, can't be deleted. Neither can a request linked to an expense report that settles it. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the advance request to delete; bound from the :id path segment. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `REQUEST_NOT_FOUND` | 400 | | `REQUEST_ISSUED` | 400 | | `HAS_SETTLEMENT` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: REQUEST_NOT_FOUND, REQUEST_ISSUED, HAS_SETTLEMENT - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Approve an employee advance request Operation ID: `approveEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/approveEmployeeAdvanceRequest) `POST /accounting/employee-advances/{id}/approve` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Approves an employee advance request. The approval goes through your tenant's approval policy, and the response is the status that results. With no approvers configured, the request goes straight to 'approved'. With a multi-step policy, it moves on one step. Only a request in 'pendingApproval' can be approved. For any other status, the request fails with STATUS_CONFLICT. Approval doesn't pay the advance. To pay it, use POST accounting/employee-advances/:id/issue. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the advance request; bound from the :id path segment. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Response `200` Success Under `data` (approveEmployeeAdvanceRequestOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | yes | Status after the operation. The workflow decides it, so it is read back rather than assumed. Example: `"approved"`. | ```json { "data": { "status": "approved" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `STATUS_CONFLICT` | 400 | | `APPROVAL_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, STATUS_CONFLICT, APPROVAL_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Issue an employee advance request Operation ID: `issueEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/issueEmployeeAdvanceRequest) `POST /accounting/employee-advances/{id}/issue` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Issues the payment for an approved employee advance request. Creates the outbound payment and its journal entry for an approved request, links the payment to the request, and sets the request's status to 'issued'. Only a request in 'approved' status can be issued. For any other status, the request fails with NOT_APPROVED. The accounts and currency come from the request's category and legal entity, not from the request body. The request fails if the category has no bank and offset account for the legal entity's leading book, with an error that has no code in the error list. The response contains the payment's id. To see the payment, use GET accounting/outbound-payments/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the approved advance request to issue. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Request body Required, `application/json`: issueEmployeeAdvanceRequestInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `paymentDate` | date | no | Payment execution date. Omit to default to the appserver's current day. Example: `"2026-09-05"`. | ```json { "paymentDate": "2026-09-05" } ``` ## Response `200` Success Under `data` (issueEmployeeAdvanceRequestOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `outboundPaymentId` | string | yes | Id (a UUID) of the OutboundPaymentHeaderBase created for this issuance. Example: `"c3d8f4a1-7e2b-4c95-9a03-5f1d8e6b2c47"`. | ```json { "data": { "outboundPaymentId": "c3d8f4a1-7e2b-4c95-9a03-5f1d8e6b2c47" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `REQUEST_NOT_FOUND` | 400 | | `NOT_APPROVED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: REQUEST_NOT_FOUND, NOT_APPROVED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Reject an employee advance request Operation ID: `rejectEmployeeAdvanceRequest` Group: Accounting › Employee Advances [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/rejectEmployeeAdvanceRequest) `POST /accounting/employee-advances/{id}/reject` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Rejects an employee advance request. The rejection goes through your tenant's approval policy, and the response is the status that results. Only a request in 'pendingApproval' can be rejected. For any other status, the request fails with STATUS_CONFLICT. A rejected request is kept, with its number and audit trail. The employee can change it and resubmit it with PATCH accounting/employee-advances/:id. To remove it, use DELETE accounting/employee-advances/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | Id (a UUID) of the advance request; bound from the :id path segment. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Request body Required, `application/json`: rejectEmployeeAdvanceRequestInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `reviewMessage` | string | no | Why the request is being rejected. Recorded as the request's decisionNote and shown to the requester. Omit to leave no note. Example: `"Trip was cancelled"`. | ```json { "reviewMessage": "Trip was cancelled" } ``` ## Response `200` Success Under `data` (rejectEmployeeAdvanceRequestOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | yes | Status after the operation. The workflow decides it, so it is read back rather than assumed. Example: `"rejected"`. | ```json { "data": { "status": "rejected" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `STATUS_CONFLICT` | 400 | | `APPROVAL_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, STATUS_CONFLICT, APPROVAL_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Expense Categories Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingExpenseCategories) An expense category classifies expense items and sets which general ledger accounts their journal entries use. Each category is available in a set of legal entities and is linked to a set of general ledger accounts. Expense items refer to a category. To stop using a category that expense items refer to, set its status to 'inactive'. ## Endpoints - [List expense categories](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/expenseCategories.html): `GET /accounting/expense-categories` - [Create an expense category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createExpenseCategory.html): `POST /accounting/expense-categories` - [Search expense categories](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchExpenseCategory.html): `POST /accounting/expense-categories/search` - [Retrieve an expense category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getExpenseCategory.html): `GET /accounting/expense-categories/{id}` - [Update an expense category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateExpenseCategory.html): `PATCH /accounting/expense-categories/{id}` - [Delete an expense category](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/deleteExpenseCategory.html): `DELETE /accounting/expense-categories/{id}` --- # List expense categories Operation ID: `expenseCategories` Group: Accounting › Expense Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/expenseCategories) `GET /accounting/expense-categories` Read-only: Reads and returns; changes nothing. Lists expense categories. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `category` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `categoryType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `reimbursable` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `requireAttachment` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `requireAttendees` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `externalId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, category, categoryType, status, lastModifiedDate - Selectable: id, category, categoryType, reimbursable, status, requireAttachment, requireAttendees, externalId, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of expenseCategoriesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the expense category. | | `category` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The display name of the expense category, for example 'Meals'. Unique within the tenant. | | `categoryType` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The kind of expense. Possible values: 'Pur', 'Dis', 'PerDiem'. 'Dis' is distance traveled. | | `reimbursable` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether expenses in this category are reimbursed to the employee. | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The lifecycle status of the expense category. Possible values: 'active', 'inactive'. An inactive category stays readable so historical expense items still resolve, and is not offered for new ones. | | `requireAttachment` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether an expense in this category must have a receipt attachment. | | `requireAttendees` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether an expense in this category must list attendees. | | `externalId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier this category has in an external system. It is not unique. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the expense category last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "category": "", "categoryType": "", "reimbursable": true, "status": "", "requireAttachment": true, "requireAttendees": true, "externalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create an expense category Operation ID: `createExpenseCategory` Group: Accounting › Expense Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createExpenseCategory) `POST /accounting/expense-categories` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates an expense category. The category is available in the legal entities in entityIds, and journal entries for its expense items use the general ledger accounts in accountIds. The response contains only its id. The name must be unique within the tenant. A reimbursable category must have at least one account in accountIds. At most one active distance ('Dis') category can exist for each reimbursable value, one reimbursable and one not. Every account in accountIds must have an account subtype that is enabled for expense reports. Otherwise the request fails with an error that has no code in the error list. Read the new record with GET accounting/expense-categories/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createExpenseCategoryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `category` | string | yes | The name of the expense category, for example 'Client Entertainment'. Must be unique across the tenant. Example: `"Client Entertainment"`. | | `categoryType` | string | no | The kind of expense. Accepted values: 'Pur', 'Dis', 'PerDiem'. 'Dis' is distance traveled. Omit to leave it unset. Example: `"Pur"`. | | `status` | string | no | The lifecycle status. Accepted values: 'active', 'inactive'. Defaults to 'active'. | | `reimbursable` | boolean | no | Whether expenses in this category are reimbursed to the employee. Defaults to true. A reimbursable category must have at least one accountId. Example: `true`. | | `requireAttachment` | boolean | no | Whether an expense in this category must have a receipt attachment. Example: `true`. | | `requireAttendees` | boolean | no | Whether an expense in this category must list attendees. | | `entityIds` | array | no | The legal entities this category is available in. This array replaces the whole set. Omit to leave it unchanged. Send an empty array to remove every entity. See id on GET base/entities. | | `accountIds` | array | no | The general ledger accounts that journal entries for this category's expense items use. This array replaces the whole set. Required unless the category is not reimbursable. See id on GET accounting/accounts/:id. Example: `["7c1a9e40-2b53-4d68-9f01-3a5b7c9d1e24"]`. | ```json { "category": "Client Entertainment", "categoryType": "Pur", "status": "", "reimbursable": true, "requireAttachment": true, "requireAttendees": true, "entityIds": [ "" ], "accountIds": [ "7c1a9e40-2b53-4d68-9f01-3a5b7c9d1e24" ] } ``` ## Response `200` Success Under `data` (createExpenseCategoryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created expense category. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ```json { "data": { "id": "b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `DUPLICATE_CATEGORY` | 400 | | `DUPLICATE_DISTANCE_CATEGORY` | 400 | | `NO_ACCOUNT_SELECTED` | 400 | | `REFERENCE_NOT_FOUND` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: DUPLICATE_CATEGORY, DUPLICATE_DISTANCE_CATEGORY, NO_ACCOUNT_SELECTED, REFERENCE_NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search expense categories Operation ID: `searchExpenseCategory` Group: Accounting › Expense Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchExpenseCategory) `POST /accounting/expense-categories/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches expense categories. You can filter by id, name, kind, status, or external identifier. Results are returned newest first, and each item has the same fields as GET accounting/expense-categories/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchExpenseCategoryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A category must match every filter you send. Omit to return all expense categories. Example: `{"status":"active"}`. | | `filters.id` | string | no | Limits results to the category with this identifier. | | `filters.category` | string | no | Limits results to the category with this exact name, for example 'Meals'. The name is unique within the tenant, so this returns at most one category. The match is case-sensitive. | | `filters.categoryType` | string | no | Limits results to categories of this kind. Accepted values: 'Pur', 'Dis', 'PerDiem'. Any other value matches no category. | | `filters.status` | string | no | Limits results to categories in this lifecycle status. Accepted values: 'active', 'inactive'. Any other value matches no category. | | `filters.externalId` | string | no | Limits results to categories with this external identifier. It is not unique, so this can return several categories. | | `page` | object | no | Pagination controls. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit for the first page. | | `page.size` | number | no | The number of expense categories per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "status": "active" }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchExpenseCategoryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The expense categories on this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the expense category. | | `items[].category` | string | yes | The display name of the expense category, for example 'Meals'. Unique within the tenant. | | `items[].categoryType` | string | no | The kind of expense. Possible values: 'Pur', 'Dis', 'PerDiem'. 'Dis' is distance traveled. | | `items[].reimbursable` | boolean | yes | Whether expenses in this category are reimbursed to the employee. | | `items[].status` | string | no | The lifecycle status of the expense category. Possible values: 'active', 'inactive'. An inactive category stays readable so historical expense items still resolve, and is not offered for new ones. | | `items[].requireAttachment` | boolean | no | Whether an expense in this category must have a receipt attachment. | | `items[].requireAttendees` | boolean | no | Whether an expense in this category must list attendees. | | `items[].externalId` | string | no | The identifier this category has in an external system. It is not unique. | | `items[].lastModifiedDate` | date-time | yes | The date and time the expense category last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "category": "", "categoryType": "", "reimbursable": true, "status": "", "requireAttachment": true, "requireAttendees": true, "externalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an expense category Operation ID: `getExpenseCategory` Group: Accounting › Expense Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getExpenseCategory) `GET /accounting/expense-categories/{id}` Read-only: Reads and returns; changes nothing. Retrieves an expense category. The response has the category's name, kind, status, reimbursement setting, and receipt and attendee requirements. Its legal entities and general ledger accounts are not included. To find a category by its external identifier, which is not unique, use POST accounting/expense-categories/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the expense category you want to retrieve. Example: `"9c1b7e40-3a6d-4f52-8f0b-1d2c7a5e93b1"`. | ## Response `200` Success Under `data` (getExpenseCategoryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the expense category. | | `category` | string | yes | The display name of the expense category, for example 'Meals'. Unique within the tenant. | | `categoryType` | string | no | The kind of expense. Possible values: 'Pur', 'Dis', 'PerDiem'. 'Dis' is distance traveled. | | `reimbursable` | boolean | yes | Whether expenses in this category are reimbursed to the employee. | | `status` | string | no | The lifecycle status of the expense category. Possible values: 'active', 'inactive'. An inactive category stays readable so historical expense items still resolve, and is not offered for new ones. | | `requireAttachment` | boolean | no | Whether an expense in this category must have a receipt attachment. | | `requireAttendees` | boolean | no | Whether an expense in this category must list attendees. | | `externalId` | string | no | The identifier this category has in an external system. It is not unique. | | `lastModifiedDate` | date-time | yes | The date and time the expense category last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "category": "", "categoryType": "", "reimbursable": true, "status": "", "requireAttachment": true, "requireAttendees": true, "externalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update an expense category Operation ID: `updateExpenseCategory` Group: Accounting › Expense Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateExpenseCategory) `PATCH /accounting/expense-categories/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates an expense category with the fields you send. A field you leave out keeps its current value. entityIds and accountIds are lists. A list you send replaces the whole list, so include every item you want to keep. A reimbursable category must keep at least one account, so an empty accountIds fails. Every linked account must have an account subtype that is enabled for expense reports, even when you leave accountIds out. Both failures return an error that has no code in the error list. Send entityIds or accountIds to change the category's legal entities or general ledger accounts. The name must stay unique within the tenant. At most one active distance ('Dis') category can exist for each reimbursable value. When the update leaves the category with no prepaid account, draft amortization schedules on its expense items are deleted. The response is empty. Read the updated record with GET accounting/expense-categories/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense category you want to update. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Request body Required, `application/json`: updateExpenseCategoryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `category` | string | no | The new name. Omit to leave it unchanged. It must stay unique across the tenant. The category's own current name is not a collision. | | `categoryType` | string | no | The new kind of expense. Accepted values: 'Pur', 'Dis', 'PerDiem'. Omit to leave it unchanged. | | `status` | string | no | The new lifecycle status. Accepted values: 'active', 'inactive'. Setting 'inactive' retires a category that is in use without deleting it. Example: `"inactive"`. | | `reimbursable` | boolean | no | Whether expenses in this category are reimbursed to the employee. Omit to leave it unchanged. | | `requireAttachment` | boolean | no | Whether an expense in this category must have a receipt attachment. Omit to leave it unchanged. | | `requireAttendees` | boolean | no | Whether an expense in this category must list attendees. Omit to leave it unchanged. Example: `true`. | | `entityIds` | array | no | The legal entities this category is available in. This array replaces the whole set. Omit to leave it unchanged. Send an empty array to clear it. See id on GET base/entities. | | `accountIds` | array | no | The general ledger accounts that journal entries for this category's expense items use. This array replaces the whole set. Omit to leave it unchanged. See id on GET accounting/accounts/:id. | ```json { "category": "", "categoryType": "", "status": "inactive", "reimbursable": true, "requireAttachment": true, "requireAttendees": true, "entityIds": [ "" ], "accountIds": [ "" ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `DUPLICATE_CATEGORY` | 400 | | `DUPLICATE_DISTANCE_CATEGORY` | 400 | | `REFERENCE_NOT_FOUND` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, DUPLICATE_CATEGORY, DUPLICATE_DISTANCE_CATEGORY, REFERENCE_NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Delete an expense category Operation ID: `deleteExpenseCategory` Group: Accounting › Expense Categories [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/deleteExpenseCategory) `DELETE /accounting/expense-categories/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Deletes an expense category. The category's links to general ledger accounts are deleted with it. While any expense item refers to the category, drafts included, the request fails with EXPENSE_CATEGORY_IN_USE. To retire a category that is in use, set its status to 'inactive' with PATCH accounting/expense-categories/:id instead. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense category you want to delete. Example: `"b81f4a22-6d90-4c53-8e17-9f2a4b6c8d05"`. | ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `EXPENSE_CATEGORY_IN_USE` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, EXPENSE_CATEGORY_IN_USE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Expense Reports Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingExpenseReports) An expense report is a set of expense items that an employee submits for approval and reimbursement. A report belongs to one employee, and every expense item on it belongs to that employee. The report is reimbursable unless one of its items sets reimbursable to false. The report totals are in reimbursementCurrency, and each item's amount is in the item's own currency. A submitted report starts in 'pendingApproval' and goes through your tenant's approval policy. An approved, reimbursable report is paid with an outbound payment. ## Endpoints - [List expense reports](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/expenseReports.html): `GET /accounting/expense-reports` - [Create an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createExpenseReport.html): `POST /accounting/expense-reports` - [Search expense reports](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchExpenseReport.html): `POST /accounting/expense-reports/search` - [Retrieve an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getExpenseReport.html): `GET /accounting/expense-reports/{id}` - [Update an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateExpenseReport.html): `PATCH /accounting/expense-reports/{id}` - [Delete an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/deleteExpenseReport.html): `DELETE /accounting/expense-reports/{id}` - [Approve an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/approveExpenseReport.html): `POST /accounting/expense-reports/{id}/approve` - [Cancel an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/cancelExpenseReportSubmission.html): `POST /accounting/expense-reports/{id}/cancel-submission` - [Reject an expense report](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/rejectExpenseReport.html): `POST /accounting/expense-reports/{id}/reject` --- # List expense reports Operation ID: `expenseReports` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/expenseReports) `GET /accounting/expense-reports` Read-only: Reads and returns; changes nothing. Lists expense reports, without their expense items. To get the items, use GET accounting/expense-reports/:id or POST accounting/expense-reports/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `expenseNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `paymentStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `submittedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `reimbursementCurrency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `externalExpenseReportId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `origin` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, expenseNumber, status, paymentStatus, submittedDate, reimbursementCurrency, reimbursableTotal, openAmount, lastModifiedDate - Selectable: id, expenseNumber, status, paymentStatus, submittedDate, reimbursementCurrency, reimbursableTotal, reimbursableTotalNetAmount, amountPaid, openAmount, pendingAmount, containsBillableItem, pendingPayment, externalExpenseReportId, origin, employeeName, entityName, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of expenseReportsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the expense report. | | `expenseNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The expense number. | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The approval status of the report. Values include 'draft', 'pendingApproval', 'approved', 'rejected' and 'paid'. | | `paymentStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The payment status of the report. | | `submittedDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the employee submitted the report. Format YYYY-MM-DD. | | `reimbursementCurrency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The currency the report is reimbursed in. Format ISO 4217, for example 'USD'. Governs every header total below. | | `reimbursableTotal` | decimal \| null | no | no | yes | The total reimbursable amount across the report's items, in reimbursementCurrency. | | `reimbursableTotalNetAmount` | decimal \| null | no | no | no | The total reimbursable amount excluding VAT, in reimbursementCurrency. | | `amountPaid` | decimal \| null | no | no | no | The amount already reimbursed to the employee, in reimbursementCurrency. | | `openAmount` | decimal \| null | no | no | yes | The amount still owed to the employee, reimbursableTotal minus amountPaid, in reimbursementCurrency. | | `pendingAmount` | decimal \| null | no | no | no | The amount committed to an in-flight payment run but not yet paid, in reimbursementCurrency. | | `containsBillableItem` | boolean \| null | no | no | no | Whether at least one item on the report is marked billable to a customer. | | `pendingPayment` | boolean \| null | no | no | no | Whether a payment for this report is in flight. | | `externalExpenseReportId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier this report has in the upstream system that produced it. It is not unique. | | `origin` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The source system this report came from, as free-form text. | | `employeeName` | string \| null | no | no | no | The name of the employee the report belongs to. | | `entityName` | string \| null | no | no | no | The name of the legal entity the report is posted under. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the expense report last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "expenseNumber": "", "status": "", "paymentStatus": "", "submittedDate": "2026-01-15", "reimbursementCurrency": "", "reimbursableTotal": "19.99", "reimbursableTotalNetAmount": "19.99", "amountPaid": "19.99", "openAmount": "19.99", "pendingAmount": "19.99", "containsBillableItem": true, "pendingPayment": true, "externalExpenseReportId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "origin": "", "employeeName": "", "entityName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create an expense report Operation ID: `createExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createExpenseReport) `POST /accounting/expense-reports` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates an expense report. The report is created with its expense items and starts in status 'pendingApproval'. The response contains only the new report's id. Read the record with GET accounting/expense-reports/:id, where expenseNumber appears once it is assigned. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createExpenseReportInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `employeeId` | string | yes | The employee the report belongs to. See id on GET people/person/:id. Example: `"5f1b8c02-7d34-4e56-9a78-1b2c3d4e5f60"`. | | `entityId` | string | yes | The legal entity the report is posted under. See id on GET base/entities. Example: `"8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e"`. | | `reimbursementCurrency` | string | yes | The currency the employee is reimbursed in. Format ISO 4217, for example 'USD'. Individual items may be in other currencies. Example: `"EUR"`. | | `submittedDate` | date | yes | The date the report is submitted. Use format YYYY-MM-DD. Example: `"2026-08-27"`. | | `departmentId` | string | no | The department the report is charged to. See id on GET people/departments/:id. | | `offsetAccountId` | string | no | The GL offset account for the reimbursement. See id on GET accounting/accounts/:id. | | `externalExpenseReportId` | string | no | The identifier this report has in the upstream system that produced it. It is not unique. | | `externalUrl` | string | no | A link to the report in the upstream system that produced it. | | `origin` | string | no | The source system this report came from, as free-form text. | | `markAsPaid` | boolean | no | Whether to mark the report paid on creation. Defaults to false. | | `skipJournalEntry` | boolean | no | Whether to skip journal-entry creation for this report. Defaults to false. | | `expenseItems` | array | yes | The expense items on the report, in the order they should appear. At least one is expected. Example: `[{"amount":"48.90","currency":"EUR","billDate":"2026-08-20","merchant":"Deutsche Bahn","expenseType":"Pur"}]`. | | `expenseItems[].amount` | decimal | yes | The amount of the expense, as a decimal in this item's own currency. | | `expenseItems[].currency` | string | yes | The currency the expense was incurred in. Format ISO 4217, for example 'USD'. Governs this item's amount, and need not match the report's reimbursementCurrency. | | `expenseItems[].billDate` | date | yes | The date on the receipt. Use format YYYY-MM-DD. | | `expenseItems[].merchant` | string | yes | The merchant the expense was paid to, as printed on the receipt. | | `expenseItems[].expenseType` | string | yes | The kind of expense. Values include 'Pur' (purchase), 'Dis' (distance traveled) and 'PerDiem'. | | `expenseItems[].description` | string | no | A description of the expense. | | `expenseItems[].categoryId` | string | no | The expense category this item is booked under. See id on GET accounting/expense-categories/:id. | | `expenseItems[].accountId` | string | no | The general ledger account this item's journal entry uses. See id on GET accounting/accounts/:id. Omit to let the category decide. | | `expenseItems[].reimbursable` | boolean | no | Whether this item is reimbursed to the employee. Defaults to true. | | `expenseItems[].externalExpenseItemId` | string | no | The identifier this item has in the upstream system that produced it. | | `expenseItems[].origin` | string | no | The source system this item came from, as free-form text. | | `expenseItems[].attendees` | string | no | Who attended, when the item's category requires it. Free text. | | `expenseItems[].attachments` | array | no | Receipts for this item, each naming an already-uploaded file by its id. Required when the category sets requireAttachment. On an existing item, omitting this leaves its attachments untouched, and an empty array clears them. | | `expenseItems[].attachments[].fileName` | string | yes | The display name of the attached file. | | `expenseItems[].attachments[].fileId` | string | yes | The identifier of the already-uploaded file. There is no endpoint for it. | | `expenseItems[].amortizationSchedules` | array | no | The amortization schedules to defer this item over, one per accounting book. An expense item has no line type, so what makes it amortizable is its account: the category or account it books to must be a prepaid-expenses account. Each schedule starts out not started and begins recognizing once the report posts. | | `expenseItems[].amortizationSchedules[].bookId` | string | no | The accounting book this schedule posts into. See id on GET accounting/books/:id. Omit it on a single-book entity to use that entity's default transaction book; send one entry per book otherwise. Two entries for the same book are refused with DUPLICATE_AMORTIZATION_BOOK. | | `expenseItems[].amortizationSchedules[].deferredAccountId` | string | no | The general ledger account the deferred balance sits on until it is recognized. See id on GET accounting/accounts/:id. Defaults to the item's own accountId. | | `expenseItems[].amortizationSchedules[].recognitionAccountId` | string | yes | The general ledger account each installment recognizes the expense into. See id on GET accounting/accounts/:id. | | `expenseItems[].amortizationSchedules[].amortizationMethod` | string | yes | How the schedule spreads its total. Values include 'exactDays', 'pointInTime', 'straightLine', 'straightLineProRateFirstLast' and 'usageBased'. | | `expenseItems[].amortizationSchedules[].amortizationStartDate` | date | yes | The first day the schedule amortizes over. Format YYYY-MM-DD. It must not be after amortizationEndDate. | | `expenseItems[].amortizationSchedules[].amortizationEndDate` | date | yes | The last day the schedule amortizes over, inclusive. Format YYYY-MM-DD. | | `expenseItems[].amortizationSchedules[].term` | decimal | yes | The number of months the schedule spreads over. A count, not an amount, so it carries no currency. It is required here, because a schedule without one cannot be turned into installments when the report posts. | | `expenseItems[].amortizationSchedules[].description` | string | no | A note to record against the schedule. | ```json { "employeeId": "5f1b8c02-7d34-4e56-9a78-1b2c3d4e5f60", "entityId": "8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e", "reimbursementCurrency": "EUR", "submittedDate": "2026-08-27", "departmentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "offsetAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalExpenseReportId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalUrl": "", "origin": "", "markAsPaid": true, "skipJournalEntry": true, "expenseItems": [ { "amount": "48.90", "currency": "EUR", "billDate": "2026-08-20", "merchant": "Deutsche Bahn", "expenseType": "Pur" } ] } ``` ## Response `200` Success Under `data` (createExpenseReportOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created expense report. Example: `"c7d2e910-3b45-4f68-8a91-2d3e4f5a6b78"`. | ```json { "data": { "id": "c7d2e910-3b45-4f68-8a91-2d3e4f5a6b78" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `REFERENCE_NOT_FOUND` | 400 | | `AMORTIZATION_NOT_ALLOWED_ON_LINE` | 400 | | `AMORTIZATION_DATE_RANGE_INVALID` | 400 | | `AMORTIZATION_BOOK_NOT_CONFIGURED` | 400 | | `DUPLICATE_AMORTIZATION_BOOK` | 400 | | `AMORTIZATION_TERM_MISMATCH` | 400 | | `AMORTIZATION_METHOD_INVALID` | 400 | | `AMORTIZATION_SCHEDULE_NOT_FOUND` | 400 | | `AMORTIZATION_SCHEDULE_POSTED` | 400 | | `TOO_MANY_LINES` | 400 | | `CATEGORY_REQUIREMENT_UNMET` | 400 | | `CATEGORY_INACTIVE` | 400 | | `ITEM_NEEDS_CATEGORY_OR_ACCOUNT` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: REFERENCE_NOT_FOUND, AMORTIZATION_NOT_ALLOWED_ON_LINE, AMORTIZATION_DATE_RANGE_INVALID, AMORTIZATION_BOOK_NOT_CONFIGURED, DUPLICATE_AMORTIZATION_BOOK, AMORTIZATION_TERM_MISMATCH, AMORTIZATION_METHOD_INVALID, AMORTIZATION_SCHEDULE_NOT_FOUND, AMORTIZATION_SCHEDULE_POSTED, TOO_MANY_LINES, CATEGORY_REQUIREMENT_UNMET, CATEGORY_INACTIVE, ITEM_NEEDS_CATEGORY_OR_ACCOUNT - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search expense reports Operation ID: `searchExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchExpenseReport) `POST /accounting/expense-reports/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches expense reports. Each match includes the report's main fields and its expense items. Results are returned newest first. You can filter by employee or legal entity, by id or by name. GET accounting/expense-reports has none of these filters. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchExpenseReportInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. An expense report must match every filter you send. Omit to return all expense reports. Example: `{"status":"pendingApproval"}`. | | `filters.id` | string | no | Limits results to the expense report with this identifier. | | `filters.expenseNumber` | string | no | Limits results to reports with this expense number. It is not unique, so this can match several reports. | | `filters.employeeId` | string | no | Limits results to reports belonging to this employee, given as the employee id. See id on GET people/person/:id. An id that matches no employee yields an empty page, not an error. | | `filters.entityId` | string | no | Limits results to reports posted under this legal entity, given as the entity id. See id on GET base/entities. An id that matches no entity yields an empty page, not an error. | | `filters.employeeName` | string | no | Limits results to reports whose employee has this name. The match is exact and case-sensitive. | | `filters.entityName` | string | no | Limits results to reports whose legal entity has this name. The match is exact and case-sensitive. | | `filters.status` | string | no | Limits results to reports in this approval status. Values include 'draft', 'pendingApproval', 'approved', 'rejected' and 'paid'. Any other value matches no report. | | `filters.paymentStatus` | string | no | Limits results to reports in this payment status. Any other value matches no report. | | `filters.externalExpenseReportId` | string | no | Limits results to reports with this upstream identifier. It is not unique, so this can match several reports. | | `page` | object | no | Pagination controls. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit for the first request. | | `page.size` | number | no | The number of expense reports per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "status": "pendingApproval" }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchExpenseReportOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The expense reports that match, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the expense report. | | `items[].expenseNumber` | string | no | The expense number. Absent until one has been assigned. | | `items[].status` | string | yes | The approval status of the report. Values include 'draft', 'pendingApproval', 'approved', 'rejected' and 'paid'. | | `items[].paymentStatus` | string | no | The payment status of the report. Absent until a payment run has touched it. | | `items[].submittedDate` | date | no | The date the employee submitted the report for approval. Format YYYY-MM-DD. | | `items[].reimbursementCurrency` | string | yes | The currency the report is reimbursed in. Format ISO 4217, for example 'USD'. Governs every header total below. | | `items[].reimbursableTotal` | decimal | no | The total reimbursable amount across the report's items, in reimbursementCurrency. | | `items[].reimbursableTotalNetAmount` | decimal | no | The total reimbursable amount excluding VAT, in reimbursementCurrency. | | `items[].amountPaid` | decimal | no | The amount already reimbursed to the employee, in reimbursementCurrency. | | `items[].openAmount` | decimal | no | The amount still owed to the employee, reimbursableTotal minus amountPaid, in reimbursementCurrency. | | `items[].pendingAmount` | decimal | no | The amount committed to an in-flight payment run but not yet paid, in reimbursementCurrency. | | `items[].containsBillableItem` | boolean | no | Whether at least one item on the report is marked billable to a customer. | | `items[].markAsPaid` | boolean | no | Whether the report was flagged as paid without going through a payment run. | | `items[].pendingPayment` | boolean | no | Whether a payment for this report is in flight. | | `items[].skipJournalEntry` | boolean | no | Whether the report is configured not to post a journal entry. | | `items[].reviewMessage` | string | no | The reviewer's note left on approval or rejection. | | `items[].externalUrl` | string | no | A link to the report in the upstream system that produced it. | | `items[].externalExpenseReportId` | string | no | The identifier this report has in the upstream system that produced it. It is not unique. | | `items[].origin` | string | no | The source system this report came from, as free-form text. | | `items[].employeeId` | string | no | The employee the report belongs to. See id on GET people/person/:id. | | `items[].employeeName` | string | no | The name of the employee the report belongs to. | | `items[].entityId` | string | no | The legal entity the report is posted under. See id on GET base/entities. | | `items[].entityName` | string | no | The name of the legal entity the report is posted under. | | `items[].departmentId` | string | no | The department the report is charged to. See id on GET people/departments/:id. | | `items[].offsetAccountId` | string | no | The GL offset account for the reimbursement. See id on GET accounting/accounts/:id. | | `items[].lastModifiedDate` | date-time | yes | The date and time the expense report last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].expenseItems` | array | no | The expense items on the report, in their order on it. An empty array when the report has none. | | `items[].expenseItems[].id` | string | yes | The unique identifier for the expense item. | | `items[].expenseItems[].itemNumber` | string | no | The expense item number. | | `items[].expenseItems[].merchant` | string | no | The merchant the expense was incurred with. | | `items[].expenseItems[].billDate` | date | no | The date the expense was incurred. Format YYYY-MM-DD. | | `items[].expenseItems[].expenseType` | string | no | The kind of expense. Values include 'Pur' (purchase), 'Dis' (distance traveled) and 'PerDiem'. | | `items[].expenseItems[].description` | string | no | A description of the expense item. | | `items[].expenseItems[].amount` | decimal | no | The amount of the expense item, as a decimal in the item's own currency. | | `items[].expenseItems[].currency` | string | no | The currency this item's amount is in. Format ISO 4217, for example 'USD'. May differ from the report's reimbursementCurrency. | | `items[].expenseItems[].reimbursable` | boolean | no | Whether this item is reimbursable to the employee. | | `items[].expenseItems[].categoryId` | string | no | The expense category the item is booked to. See id on GET accounting/expense-categories/:id. | | `items[].expenseItems[].externalExpenseItemId` | string | no | The identifier this item has in the upstream system that produced it. | | `items[].expenseItems[].attendees` | string | no | Who attended, when the item's category requires it. | | `items[].expenseItems[].attachments` | array | no | The receipts on this item. | | `items[].expenseItems[].attachments[].id` | string | yes | The unique identifier for the attachment record. | | `items[].expenseItems[].attachments[].fileName` | string | no | The display name of the attached file. | | `items[].expenseItems[].attachments[].fileId` | string | no | The identifier of the stored file. There is no endpoint for it. | | `items[].expenseItems[].amortizationSchedules` | array | no | The amortization schedules this item is deferred over, one per accounting book. Absent when the item is not amortized. An expense item has no line type, so what makes it amortizable is its account: the category or account it books to must be a prepaid-expenses account. Read the scheduled installments with GET accounting/amortization-schedule-lines, filtering on amortizationScheduleHeaderId. | | `items[].expenseItems[].amortizationSchedules[].id` | string | yes | The unique identifier for the amortization schedule. Pass it as amortizationScheduleHeaderId to GET accounting/amortization-schedule-lines to read its installments. | | `items[].expenseItems[].amortizationSchedules[].bookId` | string | no | The accounting book this schedule posts into. It is what distinguishes the entries of this array. See id on GET accounting/books/:id. | | `items[].expenseItems[].amortizationSchedules[].bookName` | string | no | The name of the accounting book this schedule posts into. | | `items[].expenseItems[].amortizationSchedules[].status` | string | no | How far the schedule has progressed. Values include 'notStarted', 'inProgress', 'completed' and 'voided'. A schedule created alongside an unposted report reads 'notStarted' until the report posts. | | `items[].expenseItems[].amortizationSchedules[].amortizationMethod` | string | no | How the schedule spreads its total. Values include 'exactDays', 'pointInTime', 'straightLine', 'straightLineProRateFirstLast' and 'usageBased'. | | `items[].expenseItems[].amortizationSchedules[].amortizationStartDate` | date | no | The first day the schedule amortizes over. Format YYYY-MM-DD. | | `items[].expenseItems[].amortizationSchedules[].amortizationEndDate` | date | no | The last day the schedule amortizes over, inclusive. Format YYYY-MM-DD. | | `items[].expenseItems[].amortizationSchedules[].term` | decimal | no | The number of months the schedule spreads over. A count, not an amount, so it carries no currency. | | `items[].expenseItems[].amortizationSchedules[].description` | string | no | The note recorded against the schedule, where one was given. | | `items[].expenseItems[].amortizationSchedules[].deferredAccountId` | string | no | The general ledger account the deferred balance sits on until it is recognized. See id on GET accounting/accounts/:id. | | `items[].expenseItems[].amortizationSchedules[].recognitionAccountId` | string | no | The general ledger account each installment recognizes the expense into. See id on GET accounting/accounts/:id. It is taken from the schedule's installments, so it is absent on a schedule that has none yet. | | `items[].expenseItems[].amortizationSchedules[].totalRecognized` | decimal | no | The amount recognized so far across the schedule's posted installments, in this item's currency. Zero until the first installment posts. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more reports remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "expenseNumber": "", "status": "", "paymentStatus": "", "submittedDate": "2026-01-15", "reimbursementCurrency": "", "reimbursableTotal": "19.99", "reimbursableTotalNetAmount": "19.99", "amountPaid": "19.99", "openAmount": "19.99", "pendingAmount": "19.99", "containsBillableItem": true, "markAsPaid": true, "pendingPayment": true, "skipJournalEntry": true, "reviewMessage": "", "externalUrl": "", "externalExpenseReportId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "origin": "", "employeeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "employeeName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "departmentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "offsetAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "expenseItems": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "itemNumber": "", "merchant": "", "billDate": "2026-01-15", "expenseType": "", "description": "", "amount": "19.99", "currency": "", "reimbursable": true, "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalExpenseItemId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "attendees": "", "attachments": [ null ], "amortizationSchedules": [ null ] } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an expense report Operation ID: `getExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getExpenseReport) `GET /accounting/expense-reports/{id}` Read-only: Reads and returns; changes nothing. Retrieves an expense report. Use the report's id, not its expenseNumber. The response has the report's main fields and every expense item on it. POST accounting/expense-reports/search returns the same record for each report. GET accounting/expense-reports returns the main fields only. The report totals are in reimbursementCurrency, and each item's amount is in the item's own currency. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the expense report you want to retrieve. Example: `"3f8c5a91-7b2e-4d16-9c4a-8e1f2b6d5307"`. | ## Response `200` Success Under `data` (getExpenseReportOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the expense report. | | `expenseNumber` | string | no | The expense number. Absent until one has been assigned. | | `status` | string | yes | The approval status of the report. Values include 'draft', 'pendingApproval', 'approved', 'rejected' and 'paid'. | | `paymentStatus` | string | no | The payment status of the report. Absent until a payment run has touched it. | | `submittedDate` | date | no | The date the employee submitted the report for approval. Format YYYY-MM-DD. Absent while the report is still a draft. | | `reimbursementCurrency` | string | yes | The currency the report is reimbursed in. Format ISO 4217, for example 'USD'. Governs every header total below. | | `reimbursableTotal` | decimal | no | The total reimbursable amount across the report's items, in reimbursementCurrency. | | `reimbursableTotalNetAmount` | decimal | no | The total reimbursable amount excluding VAT, in reimbursementCurrency. | | `amountPaid` | decimal | no | The amount already reimbursed to the employee, in reimbursementCurrency. | | `openAmount` | decimal | no | The amount still owed to the employee, reimbursableTotal minus amountPaid, in reimbursementCurrency. | | `pendingAmount` | decimal | no | The amount committed to an in-flight payment run but not yet paid, in reimbursementCurrency. | | `containsBillableItem` | boolean | no | Whether at least one item on the report is marked billable to a customer. | | `markAsPaid` | boolean | no | Whether the report was flagged as paid without going through a payment run. | | `pendingPayment` | boolean | no | Whether a payment for this report is in flight. | | `skipJournalEntry` | boolean | no | Whether the report is configured not to post a journal entry. | | `reviewMessage` | string | no | The reviewer's note left on approval or rejection. | | `externalUrl` | string | no | A link to the report in the upstream system that produced it. | | `externalExpenseReportId` | string | no | The identifier this report has in the upstream system that produced it. It is not unique. | | `origin` | string | no | The source system this report came from, as free-form text. | | `employeeId` | string | no | The employee the report belongs to. See id on GET people/person/:id. | | `employeeName` | string | no | The name of the employee the report belongs to. | | `entityId` | string | no | The legal entity the report is posted under. See id on GET base/entities. | | `entityName` | string | no | The name of the legal entity the report is posted under. | | `departmentId` | string | no | The department the report is charged to. See id on GET people/departments/:id. | | `offsetAccountId` | string | no | The GL offset account for the reimbursement. See id on GET accounting/accounts/:id. | | `lastModifiedDate` | date-time | yes | The date and time the expense report last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `approvals` | array | no | The approval decisions on this report, oldest first. An empty array when no approval policy has matched the report. | | `approvals[].decision` | string | yes | What was decided. Values include 'approve' and 'reject'. | | `approvals[].employeeName` | string | no | The name of the employee who decided. | | `approvals[].message` | string | no | The reviewer's note, where one was given. | | `approvals[].decidedAt` | date-time | yes | The date and time the decision was recorded, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `expenseItems` | array | no | The expense items on the report, in their order on it. An empty array when the report has none. | | `expenseItems[].id` | string | yes | The unique identifier for the expense item. | | `expenseItems[].itemNumber` | string | no | The expense item number. | | `expenseItems[].merchant` | string | no | The merchant the expense was incurred with. | | `expenseItems[].billDate` | date | no | The date the expense was incurred. Format YYYY-MM-DD. | | `expenseItems[].expenseType` | string | no | The kind of expense. Values include 'Pur' (purchase), 'Dis' (distance traveled) and 'PerDiem'. | | `expenseItems[].description` | string | no | A description of the expense item. | | `expenseItems[].amount` | decimal | no | The amount of the expense item, as a decimal in the item's own currency. | | `expenseItems[].currency` | string | no | The currency this item's amount is in. Format ISO 4217, for example 'USD'. May differ from the report's reimbursementCurrency. | | `expenseItems[].reimbursable` | boolean | no | Whether this item is reimbursable to the employee. | | `expenseItems[].categoryId` | string | no | The expense category the item is booked to. See id on GET accounting/expense-categories/:id. | | `expenseItems[].externalExpenseItemId` | string | no | The identifier this item has in the upstream system that produced it. | | `expenseItems[].attendees` | string | no | Who attended, when the item's category requires it. | | `expenseItems[].attachments` | array | no | The receipts on this item. | | `expenseItems[].attachments[].id` | string | yes | The unique identifier for the attachment record. | | `expenseItems[].attachments[].fileName` | string | no | The display name of the attached file. | | `expenseItems[].attachments[].fileId` | string | no | The identifier of the stored file. There is no endpoint for it. | | `expenseItems[].amortizationSchedules` | array | no | The amortization schedules this item is deferred over, one per accounting book. Absent when the item is not amortized. An expense item has no line type, so what makes it amortizable is its account: the category or account it books to must be a prepaid-expenses account. Read the scheduled installments with GET accounting/amortization-schedule-lines, filtering on amortizationScheduleHeaderId. | | `expenseItems[].amortizationSchedules[].id` | string | yes | The unique identifier for the amortization schedule. Pass it as amortizationScheduleHeaderId to GET accounting/amortization-schedule-lines to read its installments. | | `expenseItems[].amortizationSchedules[].bookId` | string | no | The accounting book this schedule posts into. It is what distinguishes the entries of this array. See id on GET accounting/books/:id. | | `expenseItems[].amortizationSchedules[].bookName` | string | no | The name of the accounting book this schedule posts into. | | `expenseItems[].amortizationSchedules[].status` | string | no | How far the schedule has progressed. Values include 'notStarted', 'inProgress', 'completed' and 'voided'. A schedule created alongside an unposted report reads 'notStarted' until the report posts. | | `expenseItems[].amortizationSchedules[].amortizationMethod` | string | no | How the schedule spreads its total. Values include 'exactDays', 'pointInTime', 'straightLine', 'straightLineProRateFirstLast' and 'usageBased'. | | `expenseItems[].amortizationSchedules[].amortizationStartDate` | date | no | The first day the schedule amortizes over. Format YYYY-MM-DD. | | `expenseItems[].amortizationSchedules[].amortizationEndDate` | date | no | The last day the schedule amortizes over, inclusive. Format YYYY-MM-DD. | | `expenseItems[].amortizationSchedules[].term` | decimal | no | The number of months the schedule spreads over. A count, not an amount, so it carries no currency. | | `expenseItems[].amortizationSchedules[].description` | string | no | The note recorded against the schedule, where one was given. | | `expenseItems[].amortizationSchedules[].deferredAccountId` | string | no | The general ledger account the deferred balance sits on until it is recognized. See id on GET accounting/accounts/:id. | | `expenseItems[].amortizationSchedules[].recognitionAccountId` | string | no | The general ledger account each installment recognizes the expense into. See id on GET accounting/accounts/:id. It is taken from the schedule's installments, so it is absent on a schedule that has none yet. | | `expenseItems[].amortizationSchedules[].totalRecognized` | decimal | no | The amount recognized so far across the schedule's posted installments, in this item's currency. Zero until the first installment posts. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "expenseNumber": "", "status": "", "paymentStatus": "", "submittedDate": "2026-01-15", "reimbursementCurrency": "", "reimbursableTotal": "19.99", "reimbursableTotalNetAmount": "19.99", "amountPaid": "19.99", "openAmount": "19.99", "pendingAmount": "19.99", "containsBillableItem": true, "markAsPaid": true, "pendingPayment": true, "skipJournalEntry": true, "reviewMessage": "", "externalUrl": "", "externalExpenseReportId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "origin": "", "employeeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "employeeName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "departmentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "offsetAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "approvals": [ { "decision": "", "employeeName": "", "message": "", "decidedAt": "2026-01-15T09:30:00.000Z" } ], "expenseItems": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "itemNumber": "", "merchant": "", "billDate": "2026-01-15", "expenseType": "", "description": "", "amount": "19.99", "currency": "", "reimbursable": true, "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalExpenseItemId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "attendees": "", "attachments": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fileName": "", "fileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ], "amortizationSchedules": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookName": "", "status": "", "amortizationMethod": "", "amortizationStartDate": "2026-01-15", "amortizationEndDate": "2026-01-15", "term": "19.99", "description": "", "deferredAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "recognitionAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "totalRecognized": "19.99" } ] } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update an expense report Operation ID: `updateExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateExpenseReport) `PATCH /accounting/expense-reports/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates an expense report with the fields you send. A field you leave out keeps its current value. Use the report's id, not its expenseNumber. This is true on the report and on each item in updateExpenseItems. Expense items change through three separate arrays, applied in this order: deleteExpenseItems, updateExpenseItems, createExpenseItems. Leave out all three to keep the items as they are. On an updateExpenseItems item, amortization schedules change through createAmortizationSchedules, updateAmortizationSchedules, and deleteAmortizationSchedules. An attachments list you send on an item replaces that item's attachments, so include every attachment you want to keep. Leave attachments out to keep them. The report's main fields and its expense items can change in one request. One request can remove an item and add its replacement. updateExpenseItems and deleteExpenseItems name items by their id on GET accounting/expense-reports/:id. For an item of another report, the request fails with ITEM_NOT_FOUND. After the update, the report is reimbursable unless an item left on it sets reimbursable to false. The response is empty. Read the record with GET accounting/expense-reports/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense report you want to update. Example: `"c7d2e910-3b45-4f68-8a91-2d3e4f5a6b78"`. | ## Request body Required, `application/json`: updateExpenseReportInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `employeeId` | string | no | The employee the report belongs to, given as an id. See id on GET people/person/:id. Omit to leave unchanged. | | `entityId` | string | no | The legal entity the report is posted under, given as an id. See id on GET base/entities. Omit to leave unchanged. | | `reimbursementCurrency` | string | no | The currency the employee is reimbursed in. Format ISO 4217, for example 'USD'. Omit to leave unchanged. | | `submittedDate` | date | no | The date the report is submitted. Use format YYYY-MM-DD. Omit to leave unchanged. | | `departmentId` | string | no | The department the report is charged to, given as an id. See id on GET people/departments/:id. Omit to leave unchanged. | | `offsetAccountId` | string | no | The GL offset account for the reimbursement, given as an id. See id on GET accounting/accounts/:id. Omit to leave unchanged. | | `externalExpenseReportId` | string | no | The identifier this report has in the upstream system that produced it. Omit to leave unchanged. | | `externalUrl` | string | no | A link to the report in the upstream system that produced it. Omit to leave unchanged. | | `origin` | string | no | The source system this report came from, as free-form text. Omit to leave unchanged. | | `markAsPaid` | boolean | no | Whether the report is marked paid. Omit to leave unchanged. | | `createExpenseItems` | array | no | The expense items to add. Omit for no additions. Example: `[{"amount":"12.50","currency":"EUR","billDate":"2026-08-21","merchant":"Taxi","expenseType":"Pur"}]`. | | `createExpenseItems[].amount` | decimal | yes | The amount of the expense, as a decimal in this item's own currency. | | `createExpenseItems[].currency` | string | yes | The currency the expense was incurred in. Format ISO 4217, for example 'USD'. Governs this item's amount, and need not match the report's reimbursementCurrency. | | `createExpenseItems[].billDate` | date | yes | The date on the receipt. Use format YYYY-MM-DD. | | `createExpenseItems[].merchant` | string | yes | The merchant the expense was paid to, as printed on the receipt. | | `createExpenseItems[].expenseType` | string | yes | The kind of expense. Values include 'Pur' (purchase), 'Dis' (distance traveled) and 'PerDiem'. | | `createExpenseItems[].description` | string | no | A description of the expense. | | `createExpenseItems[].categoryId` | string | no | The expense category this item is booked under. See id on GET accounting/expense-categories/:id. | | `createExpenseItems[].accountId` | string | no | The general ledger account this item's journal entry uses. See id on GET accounting/accounts/:id. Omit to let the category decide. | | `createExpenseItems[].reimbursable` | boolean | no | Whether this item is reimbursed to the employee. Defaults to true. | | `createExpenseItems[].externalExpenseItemId` | string | no | The identifier this item has in the upstream system that produced it. | | `createExpenseItems[].origin` | string | no | The source system this item came from, as free-form text. | | `createExpenseItems[].attendees` | string | no | Who attended, when the item's category requires it. Free text. | | `createExpenseItems[].attachments` | array | no | Receipts for this item, each naming an already-uploaded file by its id. Required when the category sets requireAttachment. On an existing item, omitting this leaves its attachments untouched, and an empty array clears them. | | `createExpenseItems[].attachments[].fileName` | string | yes | The display name of the attached file. | | `createExpenseItems[].attachments[].fileId` | string | yes | The identifier of the already-uploaded file. There is no endpoint for it. | | `createExpenseItems[].amortizationSchedules` | array | no | The amortization schedules to defer this item over, one per accounting book. A new item has nothing to change or remove, so this is a plain array, the same shape POST accounting/expense-reports takes. | | `createExpenseItems[].amortizationSchedules[].bookId` | string | no | The accounting book this schedule posts into. See id on GET accounting/books/:id. Omit it on a single-book entity to use that entity's default transaction book. | | `createExpenseItems[].amortizationSchedules[].deferredAccountId` | string | no | The general ledger account the deferred balance sits on until it is recognized. See id on GET accounting/accounts/:id. Defaults to the item's own accountId. | | `createExpenseItems[].amortizationSchedules[].recognitionAccountId` | string | yes | The general ledger account each installment recognizes the expense into. See id on GET accounting/accounts/:id. | | `createExpenseItems[].amortizationSchedules[].amortizationMethod` | string | yes | How the schedule spreads its total. Values include 'exactDays', 'pointInTime', 'straightLine', 'straightLineProRateFirstLast' and 'usageBased'. | | `createExpenseItems[].amortizationSchedules[].amortizationStartDate` | date | yes | The first day the schedule amortizes over. Format YYYY-MM-DD. It must not be after amortizationEndDate. | | `createExpenseItems[].amortizationSchedules[].amortizationEndDate` | date | yes | The last day the schedule amortizes over, inclusive. Format YYYY-MM-DD. | | `createExpenseItems[].amortizationSchedules[].term` | decimal | yes | The number of months the schedule spreads over. A count, not an amount, so it carries no currency. | | `createExpenseItems[].amortizationSchedules[].description` | string | no | A note to record against the schedule. | | `updateExpenseItems` | array | no | The expense items to change, each addressed by the id the report publishes. Every field but the id is optional, and omitting one leaves it unchanged. Omit for no changes. | | `updateExpenseItems[].id` | string | yes | The unique identifier for the expense item to change. It must belong to this report. | | `updateExpenseItems[].amount` | decimal | no | The amount of the expense, as a decimal in this item's own currency. | | `updateExpenseItems[].currency` | string | no | The currency the expense was incurred in. Format ISO 4217, for example 'USD'. Governs this item's amount, and need not match the report's reimbursementCurrency. | | `updateExpenseItems[].billDate` | date | no | The date on the receipt. Use format YYYY-MM-DD. | | `updateExpenseItems[].merchant` | string | no | The merchant the expense was paid to, as printed on the receipt. | | `updateExpenseItems[].expenseType` | string | no | The kind of expense. Values include 'Pur' (purchase), 'Dis' (distance traveled) and 'PerDiem'. | | `updateExpenseItems[].description` | string | no | A description of the expense. | | `updateExpenseItems[].categoryId` | string | no | The expense category this item is booked under. See id on GET accounting/expense-categories/:id. | | `updateExpenseItems[].accountId` | string | no | The general ledger account this item's journal entry uses. See id on GET accounting/accounts/:id. Omit to let the category decide. | | `updateExpenseItems[].reimbursable` | boolean | no | Whether this item is reimbursed to the employee. Defaults to true. | | `updateExpenseItems[].externalExpenseItemId` | string | no | The identifier this item has in the upstream system that produced it. | | `updateExpenseItems[].origin` | string | no | The source system this item came from, as free-form text. | | `updateExpenseItems[].attendees` | string | no | Who attended, when the item's category requires it. Free text. | | `updateExpenseItems[].attachments` | array | no | Receipts for this item, each naming an already-uploaded file by its id. Required when the category sets requireAttachment. On an existing item, omitting this leaves its attachments untouched, and an empty array clears them. | | `updateExpenseItems[].attachments[].fileName` | string | yes | The display name of the attached file. | | `updateExpenseItems[].attachments[].fileId` | string | yes | The identifier of the already-uploaded file. There is no endpoint for it. | | `updateExpenseItems[].createAmortizationSchedules` | array | no | Amortization schedules to add to this item, each with no id. Omit for no additions. Send none of the three amortization arrays to leave this item's schedules exactly as they are. | | `updateExpenseItems[].createAmortizationSchedules[].bookId` | string | no | The accounting book this schedule posts into. See id on GET accounting/books/:id. Omit it on a single-book entity to use that entity's default transaction book. | | `updateExpenseItems[].createAmortizationSchedules[].deferredAccountId` | string | no | The general ledger account the deferred balance sits on until it is recognized. See id on GET accounting/accounts/:id. Defaults to the item's own accountId. | | `updateExpenseItems[].createAmortizationSchedules[].recognitionAccountId` | string | yes | The general ledger account each installment recognizes the expense into. See id on GET accounting/accounts/:id. | | `updateExpenseItems[].createAmortizationSchedules[].amortizationMethod` | string | yes | How the schedule spreads its total. Values include 'exactDays', 'pointInTime', 'straightLine', 'straightLineProRateFirstLast' and 'usageBased'. | | `updateExpenseItems[].createAmortizationSchedules[].amortizationStartDate` | date | yes | The first day the schedule amortizes over. Format YYYY-MM-DD. It must not be after amortizationEndDate. | | `updateExpenseItems[].createAmortizationSchedules[].amortizationEndDate` | date | yes | The last day the schedule amortizes over, inclusive. Format YYYY-MM-DD. | | `updateExpenseItems[].createAmortizationSchedules[].term` | decimal | yes | The number of months the schedule spreads over. A count, not an amount, so it carries no currency. | | `updateExpenseItems[].createAmortizationSchedules[].description` | string | no | A note to record against the schedule. | | `updateExpenseItems[].updateAmortizationSchedules` | array | no | Amortization schedules to change, each addressed by the id the report publishes. A schedule that has already started recognizing is refused with AMORTIZATION_SCHEDULE_POSTED. Omit for no changes. | | `updateExpenseItems[].updateAmortizationSchedules[].id` | string | yes | The identifier of the schedule to change. It must be one this item already carries. | | `updateExpenseItems[].updateAmortizationSchedules[].bookId` | string | no | The accounting book this schedule posts into. See id on GET accounting/books/:id. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].deferredAccountId` | string | no | The general ledger account the deferred balance sits on until it is recognized. See id on GET accounting/accounts/:id. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].recognitionAccountId` | string | no | The general ledger account each installment recognizes the expense into. See id on GET accounting/accounts/:id. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].amortizationMethod` | string | no | How the schedule spreads its total. Values include 'exactDays', 'pointInTime', 'straightLine', 'straightLineProRateFirstLast' and 'usageBased'. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].amortizationStartDate` | date | no | The first day the schedule amortizes over. Format YYYY-MM-DD. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].amortizationEndDate` | date | no | The last day the schedule amortizes over, inclusive. Format YYYY-MM-DD. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].term` | decimal | no | The number of months the schedule spreads over. Omit to leave unchanged. | | `updateExpenseItems[].updateAmortizationSchedules[].description` | string | no | A note to record against the schedule. Omit to leave unchanged. | | `updateExpenseItems[].deleteAmortizationSchedules` | array | no | The identifiers of amortization schedules to remove from this item. Each must be one this item carries, and one that has not started recognizing. Removing a schedule leaves this item's other books untouched. | | `deleteExpenseItems` | array | no | The ids of expense items to remove, applied before the updates and creates. Each must belong to this report. Omit for no deletions. Removing an item also removes the amortization schedules it carried. | ```json { "employeeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "reimbursementCurrency": "", "submittedDate": "2026-01-15", "departmentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "offsetAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalExpenseReportId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalUrl": "", "origin": "", "markAsPaid": true, "createExpenseItems": [ { "amount": "12.50", "currency": "EUR", "billDate": "2026-08-21", "merchant": "Taxi", "expenseType": "Pur" } ], "updateExpenseItems": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amount": "19.99", "currency": "", "billDate": "2026-01-15", "merchant": "", "expenseType": "", "description": "", "categoryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "reimbursable": true, "externalExpenseItemId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "origin": "", "attendees": "", "attachments": [ { "fileName": "", "fileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ], "createAmortizationSchedules": [ { "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "deferredAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "recognitionAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amortizationMethod": "", "amortizationStartDate": "2026-01-15", "amortizationEndDate": "2026-01-15", "term": "19.99", "description": "" } ], "updateAmortizationSchedules": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "deferredAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "recognitionAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amortizationMethod": "", "amortizationStartDate": "2026-01-15", "amortizationEndDate": "2026-01-15", "term": "19.99", "description": "" } ], "deleteAmortizationSchedules": [ "" ] } ], "deleteExpenseItems": [ "" ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `REFERENCE_NOT_FOUND` | 400 | | `ITEM_NOT_FOUND` | 400 | | `AMORTIZATION_SCHEDULE_NOT_FOUND` | 400 | | `AMORTIZATION_SCHEDULE_POSTED` | 400 | | `AMORTIZATION_NOT_ALLOWED_ON_LINE` | 400 | | `AMORTIZATION_DATE_RANGE_INVALID` | 400 | | `AMORTIZATION_BOOK_NOT_CONFIGURED` | 400 | | `DUPLICATE_AMORTIZATION_BOOK` | 400 | | `AMORTIZATION_TERM_MISMATCH` | 400 | | `AMORTIZATION_METHOD_INVALID` | 400 | | `TOO_MANY_LINES` | 400 | | `CATEGORY_REQUIREMENT_UNMET` | 400 | | `CATEGORY_INACTIVE` | 400 | | `ITEM_NEEDS_CATEGORY_OR_ACCOUNT` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, REFERENCE_NOT_FOUND, ITEM_NOT_FOUND, AMORTIZATION_SCHEDULE_NOT_FOUND, AMORTIZATION_SCHEDULE_POSTED, AMORTIZATION_NOT_ALLOWED_ON_LINE, AMORTIZATION_DATE_RANGE_INVALID, AMORTIZATION_BOOK_NOT_CONFIGURED, DUPLICATE_AMORTIZATION_BOOK, AMORTIZATION_TERM_MISMATCH, AMORTIZATION_METHOD_INVALID, TOO_MANY_LINES, CATEGORY_REQUIREMENT_UNMET, CATEGORY_INACTIVE, ITEM_NEEDS_CATEGORY_OR_ACCOUNT - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Delete an expense report Operation ID: `deleteExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/deleteExpenseReport) `DELETE /accounting/expense-reports/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Deletes an expense report. The report's expense items, their attachments, and any business-trip details are deleted with it. An approved report can't be deleted while the accounting period of its submitted date is closed or locked. It also can't be deleted while an amortization schedule on one of its items has created a journal entry. A report that isn't approved has neither check. To keep the report and its audit trail, reject it with POST accounting/expense-reports/:id/reject instead. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense report you want to delete. Example: `"c7d2e910-3b45-4f68-8a91-2d3e4f5a6b78"`. | ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `POSTING_PERIOD_CLOSED` | 400 | | `POSTED_AMORTIZATION` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, POSTING_PERIOD_CLOSED, POSTED_AMORTIZATION - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Approve an expense report Operation ID: `approveExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/approveExpenseReport) `POST /accounting/expense-reports/{id}/approve` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Approves an expense report. The approval goes through your tenant's approval policy, and the response is the status that results. With no approvers configured, the report goes straight to 'approved'. With a multi-step policy, it moves on one step. For a report that is already approved, the request fails with STATUS_CONFLICT. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense report you want to approve. Example: `"3f1a8c25-9d47-4e60-b892-0c5a7e3b1d84"`. | ## Response `200` Success Under `data` (approveExpenseReportOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | yes | The approval status after the operation. The approval workflow decides it, so it is read back. | ```json { "data": { "status": "" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `STATUS_CONFLICT` | 400 | | `APPROVAL_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, STATUS_CONFLICT, APPROVAL_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Cancel an expense report Operation ID: `cancelExpenseReportSubmission` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/cancelExpenseReportSubmission) `POST /accounting/expense-reports/{id}/cancel-submission` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Cancels an expense report's submission. Canceling deletes the report but keeps its expense items, their attachments, and its business-trip details, which remain without a report. On an approved report, amortization schedules that haven't created a journal entry are deleted. An approved report can't be canceled while the accounting period of its submitted date is closed or locked. It also can't be canceled while an amortization schedule on one of its items has created a journal entry. Any other report has neither check. To delete the report with its items, attachments, and business-trip details, use DELETE accounting/expense-reports/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense report whose submission you want to cancel. Example: `"3f1a8c25-9d47-4e60-b892-0c5a7e3b1d84"`. | ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `POSTING_PERIOD_CLOSED` | 400 | | `POSTED_AMORTIZATION` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, POSTING_PERIOD_CLOSED, POSTED_AMORTIZATION - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Reject an expense report Operation ID: `rejectExpenseReport` Group: Accounting › Expense Reports [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/rejectExpenseReport) `POST /accounting/expense-reports/{id}/reject` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Rejects an expense report. reviewMessage is recorded with the decision, and the response is the status that results. For a report that is already rejected, the request fails with STATUS_CONFLICT. A rejected report is kept, with its number and audit trail. To withdraw a submission and delete the report, use POST accounting/expense-reports/:id/cancel-submission. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the expense report you want to reject. Example: `"3f1a8c25-9d47-4e60-b892-0c5a7e3b1d84"`. | ## Request body Required, `application/json`: rejectExpenseReportInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `reviewMessage` | string | yes | Why the report is being rejected. It is recorded against the decision and shown to the submitter. Example: `"Missing receipt for the hotel line"`. | ```json { "reviewMessage": "Missing receipt for the hotel line" } ``` ## Response `200` Success Under `data` (rejectExpenseReportOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | string | yes | The approval status after the operation. The approval workflow decides it, so it is read back. | ```json { "data": { "status": "" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `STATUS_CONFLICT` | 400 | | `APPROVAL_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, STATUS_CONFLICT, APPROVAL_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Fiscal Calendars Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingFiscalCalendars) Fiscal calendars define how a year is divided into accounting periods. A fiscal calendar sets whether the year is divided into months or quarters, and when the year starts. Each period of a calendar, for each legal entity and accounting book, is an accounting period. To list them, use GET accounting/accounting-periods?filter[fiscalCalendarId][eq]=. No endpoint finds a calendar by name. List the calendars and match the name in your own code. ## Endpoints - [List fiscal calendars](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/fiscalCalendars.html): `GET /accounting/fiscal-calendars` - [Retrieve a fiscal calendar](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getFiscalCalendar.html): `GET /accounting/fiscal-calendars/{id}` --- # List fiscal calendars Operation ID: `fiscalCalendars` Group: Accounting › Fiscal Calendars [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/fiscalCalendars) `GET /accounting/fiscal-calendars` Read-only: Reads and returns; changes nothing. Lists fiscal calendars. To read a calendar's fiscal years and assigned legal entities, use GET accounting/fiscal-calendars/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `periodFormat` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fiscalYearsCount` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `anchorDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, calendarName, periodFormat, status, fiscalYearsCount, anchorDate, lastModifiedDate - Selectable: id, calendarName, description, periodFormat, status, fiscalYearsCount, anchorDate, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of fiscalCalendarsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the fiscal calendar. | | `calendarName` | string | no | no | yes | The name of the fiscal calendar, unique within the tenant. | | `description` | string \| null | no | no | no | A description of the fiscal calendar. Absent when none has been set. | | `periodFormat` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How the fiscal calendar divides a fiscal year into periods. Possible values: 'calendarMonths' (twelve monthly periods), '4-4-5', '4-5-4', '5-4-4' (retail quarters of thirteen weeks, split into periods of that many weeks). | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The lifecycle status of the fiscal calendar. Possible values: 'new', 'active', 'deactivated'. Absent when none has been set. | | `fiscalYearsCount` | number \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The number of distinct fiscal years on this calendar. Absent until the first fiscal year exists. | | `anchorDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The anchor reference date, set only for retail calendars (a '4-4-5', '4-5-4' or '5-4-4' periodFormat). It is the last day of the month before the first period starts. Format YYYY-MM-DD. Absent on a calendarMonths calendar. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the fiscal calendar last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "calendarName": "", "description": "", "periodFormat": "", "status": "", "fiscalYearsCount": 0, "anchorDate": "2026-01-15", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a fiscal calendar Operation ID: `getFiscalCalendar` Group: Accounting › Fiscal Calendars [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getFiscalCalendar) `GET /accounting/fiscal-calendars/{id}` Read-only: Reads and returns; changes nothing. Retrieves a fiscal calendar. The response includes the fiscal years the calendar spans and the names of the legal entities it is assigned to. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the fiscal calendar you want to retrieve. | ## Response `200` Success Under `data` (getFiscalCalendarOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the fiscal calendar. | | `calendarName` | string | yes | The name of the fiscal calendar, unique within the tenant. | | `description` | string | no | A description of the fiscal calendar. Absent when none has been set. | | `periodFormat` | string | yes | How the fiscal calendar divides a fiscal year into periods. Possible values: 'calendarMonths' (twelve monthly periods), '4-4-5', '4-5-4', '5-4-4' (retail quarters of thirteen weeks, split into periods of that many weeks). | | `status` | string | no | The lifecycle status of the fiscal calendar. Possible values: 'new', 'active', 'deactivated'. Absent when none has been set. | | `fiscalYearsCount` | number | no | The number of distinct fiscal years on this calendar. Absent until the first fiscal year exists. | | `fiscalYears` | array | no | The distinct fiscal years this calendar spans, taken from its accounting periods, in no guaranteed order. Each is a free-text label such as 'FY2024'. An empty array when the calendar has no fiscal years yet. | | `assignedEntities` | array | no | The names, not the identifiers, of the legal entities this calendar is assigned to, in no guaranteed order. An empty array when it is assigned to none. | | `anchorDate` | date | no | The anchor reference date, set only for retail calendars (a '4-4-5', '4-5-4' or '5-4-4' periodFormat). It is the last day of the month before the first period starts. Format YYYY-MM-DD. Absent on a calendarMonths calendar. | | `lastModifiedDate` | date-time | yes | The date and time the fiscal calendar last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "calendarName": "", "description": "", "periodFormat": "", "status": "", "fiscalYearsCount": 0, "fiscalYears": [ "" ], "assignedEntities": [ "" ], "anchorDate": "2026-01-15", "lastModifiedDate": "2026-01-15T09:30:00.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Fixed Assets Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/fixedAssets) Fixed assets are the assets a business capitalizes and depreciates. Depreciation policies set how they're depreciated, and depreciation schedules record each asset's depreciation. Recording depreciation creates journal entries in the general ledger. ## Groups - [Depreciation Schedules](https://docs.api.lensing.ai/reference/lensing/v1/groups/invDepreciationSchedules.html) --- # Depreciation Schedules Group: Accounting › Fixed Assets [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/invDepreciationSchedules) A depreciation schedule is the plan for depreciating one fixed asset over its useful life. A schedule is created when a fixed asset is saved. It uses the useful life from the depreciation rule in the fixed asset's policy. A schedule has a line for each depreciation period, such as each month. Each line has the amount to depreciate and its date. Recording depreciation for the lines creates their journal entries and marks the lines as recorded. A fixed asset has one schedule or none. A fixed asset whose `depreciationMethod` is 'Bypassed' has no schedule. The API returns schedules, not their lines. A schedule's `fixedAssetId` is a different kind of id from the `id` that the fixed asset endpoints use. If an asset is recorded in more than one book, each of its fixed assets has its own schedule. ## Endpoints - [List depreciation schedules](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/depreciationSchedules.html): `GET /fixed-assets/depreciation-schedules` - [Record depreciation](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/recordDepreciation.html): `POST /fixed-assets/depreciation-schedules/record` - [Search depreciation schedules](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchDepreciationSchedules.html): `POST /fixed-assets/depreciation-schedules/search` - [Retrieve a depreciation schedule](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getDepreciationSchedule.html): `GET /fixed-assets/depreciation-schedules/{id}` --- # List depreciation schedules Operation ID: `depreciationSchedules` Group: Accounting › Fixed Assets › Depreciation Schedules [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/depreciationSchedules) `GET /fixed-assets/depreciation-schedules` Read-only: Reads and returns; changes nothing. Lists depreciation schedules. Each schedule has the same fields as `GET fixed-assets/depreciation-schedules/:id`. Recording depreciation doesn't always update `lastModifiedDate`. It changes `depreciatedAmount` and `remainingValue`. It updates `lastModifiedDate` only when the schedule's `status` changes. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `scheduleNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fixedAssetId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `bookId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `depreciationRuleId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `acquisitionDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `startDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `endDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, scheduleNumber, fixedAssetId, status, acquisitionDate, startDate, endDate, lastModifiedDate - Selectable: id, scheduleNumber, fixedAssetId, entityId, bookId, depreciationRuleId, status, acquisitionDate, startDate, endDate, currency, acquisitionValue, depreciableAmount, depreciatedAmount, remainingValue, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of depreciationSchedulesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the depreciation schedule. Example: `"7c2e5a91-3f84-4b60-9d17-a5e8f2c04b39"`. | | `scheduleNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The depreciation schedule number. Unique within the tenant. Example: 'DS-0042'. Example: `"DS-0042"`. | | `fixedAssetId` | number | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The internal number of the fixed asset the schedule depreciates. It is not the `id` that the fixed asset endpoints use. Example: `3187`. | | `entityId` | number | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | The internal number of the legal entity that owns the fixed asset. It is not the `id` that the legal entity endpoints use. Example: `1`. | | `bookId` | number | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | The internal number of the accounting book the fixed asset is tracked in. It is not the `id` that the accounting book endpoints use, or the `bookId` on a depreciation policy. Example: `1`. | | `depreciationRuleId` | number | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | The internal number of the depreciation rule applied to the fixed asset. A rule links a depreciation policy to a fixed asset type and sets the useful life. Example: `12`. | | `status` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The status of the schedule. Possible values: 'NotStarted' (no line has been recorded), 'Ongoing' (some lines have been recorded), 'Completed' (every line has been recorded), 'Disposed' (the fixed asset was disposed of), 'Voided' (the fixed asset was voided). Example: `"Ongoing"`. | | `acquisitionDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the fixed asset was acquired. Format: YYYY-MM-DD. It can be earlier than startDate. Example: `"2024-01-15"`. | | `startDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date depreciation starts. Format: YYYY-MM-DD. Example: `"2024-02-01"`. | | `endDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The last day of depreciation. Format: YYYY-MM-DD. Example: `"2029-01-31"`. | | `currency` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The currency of the fixed asset. Format: ISO 4217, for example 'EUR'. Example: `"EUR"`. | | `acquisitionValue` | decimal | no | no | no | The acquisition cost of the fixed asset, in the `currency`. Example: `"24000.00"`. | | `depreciableAmount` | decimal | no | no | no | The total amount to depreciate over the schedule, in the `currency`. Formula: the fixed asset's depreciationStartValueAmount - salvageValueAmount, or when depreciationStartValueAmount is absent, acquisitionValue - salvageValueAmount. See depreciationStartValueAmount and salvageValueAmount on GET fixed-assets/:id. Example: `"21600.00"`. | | `depreciatedAmount` | decimal | no | no | no | The depreciation recorded on this schedule, in the `currency`. Formula: the sum of the recorded schedule lines. Lines that have not been recorded do not count, whatever their dates. Example: `"7560.00"`. | | `remainingValue` | decimal | no | no | no | The net book value of the fixed asset, in the `currency`. Formula: acquisitionValue - depreciatedAmount, or when the fixed asset has a depreciationStartValueAmount, depreciationStartValueAmount - depreciatedAmount. It is 0 once the fixed asset is voided, retired, or disposed of. Example: `"16440.00"`. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the depreciation schedule last changed, in UTC. Format: YYYY-MM-DDTHH:mm:ss.sssZ. Recording depreciation changes depreciatedAmount and remainingValue. It updates lastModifiedDate only when the schedule's status changes. Example: `"2026-08-14T09:12:33.000Z"`. | ```json { "data": [ { "id": "7c2e5a91-3f84-4b60-9d17-a5e8f2c04b39", "scheduleNumber": "DS-0042", "fixedAssetId": 3187, "entityId": 1, "bookId": 1, "depreciationRuleId": 12, "status": "Ongoing", "acquisitionDate": "2024-01-15", "startDate": "2024-02-01", "endDate": "2029-01-31", "currency": "EUR", "acquisitionValue": "24000.00", "depreciableAmount": "21600.00", "depreciatedAmount": "7560.00", "remainingValue": "16440.00", "lastModifiedDate": "2026-08-14T09:12:33.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Record depreciation Operation ID: `recordDepreciation` Group: Accounting › Fixed Assets › Depreciation Schedules [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/recordDepreciation) `POST /fixed-assets/depreciation-schedules/record` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Records depreciation for the depreciation schedule lines you send. It creates one journal entry for each schedule. Each journal entry is dated `postingDate`. It debits the depreciation expense account and credits the accumulated depreciation account of the fixed asset's depreciation policy. This endpoint records only the lines you send, whatever their dates. It does not find the lines that are due. If you send 35 line ids or fewer, the response comes back after the recording is finished. Either all the lines are recorded, or none are, and the response explains why. If you send more than 35 line ids, the response comes back straight away, with `asyncExecution` set to `true`, and the lines are recorded afterwards. The response cannot tell you about errors, such as a line that is already recorded or a closed accounting period. A very large recording is saved in parts. If it stops because of an error, the parts saved before the error stay recorded. To check how much was recorded, read `depreciatedAmount` on the schedules, or list journal entries with the type 'depreciation' on `GET accounting/journal-entries`. The request fails with `TASK_ALREADY_RUNNING` while another depreciation recording is running for the same legal entity and book. This includes recordings started in Lensing. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: recordDepreciationInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `lineIds` | array | yes | The internal numbers of the depreciation schedule lines to record. Send at least one. An id that matches no line is skipped, as long as at least one id matches. A line that is already recorded cannot be recorded again, and sending one fails the request. Example: `[101,102,103]`. | | `postingDate` | date | yes | The posting date of the journal entries that recording creates. Format: YYYY-MM-DD. The date must fall in an accounting period of the legal entity's book that is open for posting. It does not have to fall in the period the lines belong to. Example: `"2026-09-30"`. | ```json { "lineIds": [ 101, 102, 103 ], "postingDate": "2026-09-30" } ``` ## Response `200` Success Under `data` (recordDepreciationOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `asyncExecution` | boolean | yes | Whether the recording was queued to run in the background. True when you sent more than 35 line ids. When true, the journal entries do not exist yet, and a problem with the lines is not reported in the response. Example: `false`. | | `lineCount` | number | yes | The number of line ids you sent, including duplicates and ids that match no line. It can be more than the number of lines recorded. Example: `3`. | ```json { "data": { "asyncExecution": false, "lineCount": 3 }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NO_LINES` | 400 | | `LINES_NOT_FOUND` | 400 | | `INVALID_SCHEDULE_STATE` | 400 | | `TASK_ALREADY_RUNNING` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NO_LINES, LINES_NOT_FOUND, INVALID_SCHEDULE_STATE, TASK_ALREADY_RUNNING - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search depreciation schedules Operation ID: `searchDepreciationSchedules` Group: Accounting › Fixed Assets › Depreciation Schedules [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchDepreciationSchedules) `POST /fixed-assets/depreciation-schedules/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches depreciation schedules. Each schedule returned has the same fields as `GET fixed-assets/depreciation-schedules/:id`. To filter by depreciation rule, acquisition date, currency, or `lastModifiedDate`, use `GET fixed-assets/depreciation-schedules`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchDepreciationSchedulesInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A schedule must match every filter you send. Omit to return all depreciation schedules. If you send an empty list for a filter, that filter is ignored. Example: `{"status":"Ongoing","entityIds":[1],"startDateFrom":"2024-01-01","startDateTo":"2024-12-31"}`. | | `filters.id` | string | no | Limits results to the depreciation schedule with this id. The match is exact. A value that is not in the format of an id matches no schedule. | | `filters.scheduleNumber` | string | no | Limits results to the depreciation schedule with this schedule number. The match is exact and case-sensitive. Example: 'DS-0042'. | | `filters.fixedAssetIds` | array | no | Limits results to the schedules of any of these fixed assets. The match is exact. Send the internal numbers that schedules return in `fixedAssetId`, not the `id` that the fixed asset endpoints use. | | `filters.entityIds` | array | no | Limits results to schedules for fixed assets owned by any of these legal entities. The match is exact. Send the internal numbers that schedules return in `entityId`, not the `id` that the legal entity endpoints use. | | `filters.bookIds` | array | no | Limits results to schedules for fixed assets tracked in any of these accounting books. The match is exact. Send the internal numbers that schedules return in `bookId`, not the `id` that the accounting book endpoints use. | | `filters.status` | string | no | Limits results to schedules with this status. Accepted values: 'NotStarted', 'Ongoing', 'Completed', 'Disposed', 'Voided'. | | `filters.startDateFrom` | date | no | Limits results to schedules whose startDate is on or after this date. Format: YYYY-MM-DD. | | `filters.startDateTo` | date | no | Limits results to schedules whose startDate is on or before this date. Format: YYYY-MM-DD. | | `filters.endDateFrom` | date | no | Limits results to schedules whose endDate is on or after this date. Format: YYYY-MM-DD. | | `filters.endDateTo` | date | no | Limits results to schedules whose endDate is on or before this date. Format: YYYY-MM-DD. | | `page` | object | no | The page of results to return. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | The cursor from the previous response's `pagination.nextCursor`. Omit on the first request. | | `page.size` | number | no | The number of depreciation schedules per page, between 1 and 200. Defaults to 50. A value above 200 is treated as 200, and a value below 1 as 1, not rejected. | ```json { "filters": { "status": "Ongoing", "entityIds": [ 1 ], "startDateFrom": "2024-01-01", "startDateTo": "2024-12-31" }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchDepreciationSchedulesOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The depreciation schedules on this page, most recently created first. An empty array when nothing matches. Example: `[{"id":"7c2e5a91-3f84-4b60-9d17-a5e8f2c04b39","scheduleNumber":"DS-0042","fixedAssetId":3187,"entityId":1,"bookId":1,"depreciationRuleId":12,"status":"Ongoing","acquisitionDate":"2024-01-15","startDate":"2024-02-01","endDate":"2029-01-31","currency":"EUR","acquisitionValue":"24000.00","depreciableAmount":"21600.00","depreciatedAmount":"7560.00","remainingValue":"16440.00","lastModifiedDate":"2026-08-14T09:12:33.000Z"}]`. | | `items[].id` | string | yes | The unique identifier for the depreciation schedule. | | `items[].scheduleNumber` | string | no | The depreciation schedule number. Unique within the tenant. Example: 'DS-0042'. | | `items[].fixedAssetId` | number | yes | The internal number of the fixed asset the schedule depreciates. It is not the `id` that the fixed asset endpoints use. | | `items[].entityId` | number | yes | The internal number of the legal entity that owns the fixed asset. It is not the `id` that the legal entity endpoints use. | | `items[].bookId` | number | yes | The internal number of the accounting book the fixed asset is tracked in. It is not the `id` that the accounting book endpoints use, or the `bookId` on a depreciation policy. | | `items[].depreciationRuleId` | number | yes | The internal number of the depreciation rule applied to the fixed asset. A rule links a depreciation policy to a fixed asset type and sets the useful life. | | `items[].status` | string | yes | The status of the schedule. Possible values: 'NotStarted' (no line has been recorded), 'Ongoing' (some lines have been recorded), 'Completed' (every line has been recorded), 'Disposed' (the fixed asset was disposed of), 'Voided' (the fixed asset was voided). | | `items[].acquisitionDate` | date | yes | The date the fixed asset was acquired. Format: YYYY-MM-DD. It can be earlier than startDate. | | `items[].startDate` | date | yes | The date depreciation starts. Format: YYYY-MM-DD. | | `items[].endDate` | date | yes | The last day of depreciation. Format: YYYY-MM-DD. | | `items[].currency` | string | yes | The currency of the fixed asset. Format: ISO 4217, for example 'EUR'. | | `items[].acquisitionValue` | decimal | yes | The acquisition cost of the fixed asset, in the `currency`. | | `items[].depreciableAmount` | decimal | yes | The total amount to depreciate over the schedule, in the `currency`. Formula: the fixed asset's depreciationStartValueAmount - salvageValueAmount, or when depreciationStartValueAmount is absent, acquisitionValue - salvageValueAmount. See depreciationStartValueAmount and salvageValueAmount on GET fixed-assets/:id. | | `items[].depreciatedAmount` | decimal | yes | The depreciation recorded on this schedule, in the `currency`. Formula: the sum of the recorded schedule lines. Lines that have not been recorded do not count, whatever their dates. | | `items[].remainingValue` | decimal | yes | The net book value of the fixed asset, in the `currency`. Formula: acquisitionValue - depreciatedAmount, or when the fixed asset has a depreciationStartValueAmount, depreciationStartValueAmount - depreciatedAmount. It is 0 once the fixed asset is voided, retired, or disposed of. | | `items[].lastModifiedDate` | date-time | yes | The date and time the depreciation schedule last changed, in UTC. Format: YYYY-MM-DDTHH:mm:ss.sssZ. Recording depreciation changes depreciatedAmount and remainingValue. It updates lastModifiedDate only when the schedule's status changes. | | `pagination` | object | yes | Whether more depreciation schedules match, and the cursor for the next page. Example: `{"hasMore":false}`. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as `page.cursor`. Absent on the last page. | | `pagination.hasMore` | boolean | yes | Whether more depreciation schedules match after this page. | ```json { "data": { "items": [ { "id": "7c2e5a91-3f84-4b60-9d17-a5e8f2c04b39", "scheduleNumber": "DS-0042", "fixedAssetId": 3187, "entityId": 1, "bookId": 1, "depreciationRuleId": 12, "status": "Ongoing", "acquisitionDate": "2024-01-15", "startDate": "2024-02-01", "endDate": "2029-01-31", "currency": "EUR", "acquisitionValue": "24000.00", "depreciableAmount": "21600.00", "depreciatedAmount": "7560.00", "remainingValue": "16440.00", "lastModifiedDate": "2026-08-14T09:12:33.000Z" } ], "pagination": { "hasMore": false } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `INVALID_CURSOR` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_CURSOR - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a depreciation schedule Operation ID: `getDepreciationSchedule` Group: Accounting › Fixed Assets › Depreciation Schedules [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getDepreciationSchedule) `GET /fixed-assets/depreciation-schedules/{id}` Read-only: Reads and returns; changes nothing. Retrieves a depreciation schedule. To find a schedule by its schedule number, use `POST fixed-assets/depreciation-schedules/search`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the depreciation schedule you want to retrieve. | ## Response `200` Success Under `data` (getDepreciationScheduleOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the depreciation schedule. Example: `"7c2e5a91-3f84-4b60-9d17-a5e8f2c04b39"`. | | `scheduleNumber` | string | no | The depreciation schedule number. Unique within the tenant. Example: 'DS-0042'. Example: `"DS-0042"`. | | `fixedAssetId` | number | yes | The internal number of the fixed asset the schedule depreciates. It is not the `id` that the fixed asset endpoints use. Example: `3187`. | | `entityId` | number | yes | The internal number of the legal entity that owns the fixed asset. It is not the `id` that the legal entity endpoints use. Example: `1`. | | `bookId` | number | yes | The internal number of the accounting book the fixed asset is tracked in. It is not the `id` that the accounting book endpoints use, or the `bookId` on a depreciation policy. Example: `1`. | | `depreciationRuleId` | number | yes | The internal number of the depreciation rule applied to the fixed asset. A rule links a depreciation policy to a fixed asset type and sets the useful life. Example: `12`. | | `status` | string | yes | The status of the schedule. Possible values: 'NotStarted' (no line has been recorded), 'Ongoing' (some lines have been recorded), 'Completed' (every line has been recorded), 'Disposed' (the fixed asset was disposed of), 'Voided' (the fixed asset was voided). Example: `"Ongoing"`. | | `acquisitionDate` | date | yes | The date the fixed asset was acquired. Format: YYYY-MM-DD. It can be earlier than startDate. Example: `"2024-01-15"`. | | `startDate` | date | yes | The date depreciation starts. Format: YYYY-MM-DD. Example: `"2024-02-01"`. | | `endDate` | date | yes | The last day of depreciation. Format: YYYY-MM-DD. Example: `"2029-01-31"`. | | `currency` | string | yes | The currency of the fixed asset. Format: ISO 4217, for example 'EUR'. Example: `"EUR"`. | | `acquisitionValue` | decimal | yes | The acquisition cost of the fixed asset, in the `currency`. Example: `"24000.00"`. | | `depreciableAmount` | decimal | yes | The total amount to depreciate over the schedule, in the `currency`. Formula: the fixed asset's depreciationStartValueAmount - salvageValueAmount, or when depreciationStartValueAmount is absent, acquisitionValue - salvageValueAmount. See depreciationStartValueAmount and salvageValueAmount on GET fixed-assets/:id. Example: `"21600.00"`. | | `depreciatedAmount` | decimal | yes | The depreciation recorded on this schedule, in the `currency`. Formula: the sum of the recorded schedule lines. Lines that have not been recorded do not count, whatever their dates. Example: `"7560.00"`. | | `remainingValue` | decimal | yes | The net book value of the fixed asset, in the `currency`. Formula: acquisitionValue - depreciatedAmount, or when the fixed asset has a depreciationStartValueAmount, depreciationStartValueAmount - depreciatedAmount. It is 0 once the fixed asset is voided, retired, or disposed of. Example: `"16440.00"`. | | `lastModifiedDate` | date-time | yes | The date and time the depreciation schedule last changed, in UTC. Format: YYYY-MM-DDTHH:mm:ss.sssZ. Recording depreciation changes depreciatedAmount and remainingValue. It updates lastModifiedDate only when the schedule's status changes. Example: `"2026-08-14T09:12:33.000Z"`. | ```json { "data": { "id": "7c2e5a91-3f84-4b60-9d17-a5e8f2c04b39", "scheduleNumber": "DS-0042", "fixedAssetId": 3187, "entityId": 1, "bookId": 1, "depreciationRuleId": 12, "status": "Ongoing", "acquisitionDate": "2024-01-15", "startDate": "2024-02-01", "endDate": "2029-01-31", "currency": "EUR", "acquisitionValue": "24000.00", "depreciableAmount": "21600.00", "depreciatedAmount": "7560.00", "remainingValue": "16440.00", "lastModifiedDate": "2026-08-14T09:12:33.000Z" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Intercompany Bills Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingIntercompanyBills) An intercompany bill records a charge from one legal entity to another in the same tenant, such as a parent legal entity charging a subsidiary for shared services. Each bill has an issuing legal entity, a receiving legal entity, one currency for all its amounts, and an optional markup. The markup is a percentage of each line or a flat fee split across the lines. Each line has an amount and the general ledger accounts that each legal entity's side of the journal entry uses. The bill's intercompany process supplies the intercompany accounts that balance each legal entity's side of the journal entry. There is no endpoint to create, publish, or void an intercompany bill. ## Endpoints - [List intercompany bills](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/intercompanyBills.html): `GET /accounting/intercompany-bills` - [Search intercompany bills](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchIntercompanyBills.html): `POST /accounting/intercompany-bills/search` - [Retrieve an intercompany bill](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getIntercompanyBill.html): `GET /accounting/intercompany-bills/{id}` --- # List intercompany bills Operation ID: `intercompanyBills` Group: Accounting › Intercompany Bills [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/intercompanyBills) `GET /accounting/intercompany-bills` Read-only: Reads and returns; changes nothing. Lists intercompany bills. Each intercompany bill is returned without its lines. To get the lines, use GET accounting/intercompany-bills/:id or POST accounting/intercompany-bills/search. To filter by intercompanyBillNumber, name, description, exchangeRate, markupAmount, or eitherEntityId, use POST accounting/intercompany-bills/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `fromEntityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `toEntityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `intercompanyProcessId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `billMarkup` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `billDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `paymentTerms` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `dueDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `serviceDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `type` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `postingDirection` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: billDate, dueDate, serviceDate, status, type, billMarkup, postingDirection, currency, paymentTerms, lastModifiedDate - Selectable: id, intercompanyBillNumber, name, description, fromEntityId, fromEntityName, toEntityId, toEntityName, intercompanyProcessId, intercompanyProcessName, currency, exchangeRate, billMarkup, markupAmount, billDate, paymentTerms, dueDate, serviceDate, status, voidReason, type, postingDirection, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of intercompanyBillsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The unique identifier for the intercompany bill. | | `intercompanyBillNumber` | string \| null | no | no | no | The intercompany bill number. Unique within the tenant. Example: 'ICB-1'. | | `name` | string \| null | no | no | no | The name of the intercompany bill. | | `description` | string \| null | no | no | no | A description of the intercompany bill. | | `fromEntityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The legal entity that issues the intercompany bill and is owed the amount. See id on GET base/entities. | | `fromEntityName` | string \| null | no | no | no | The name of the issuing legal entity. See entityName on GET base/entities. | | `toEntityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The legal entity that receives the intercompany bill and is charged for it. See id on GET base/entities. | | `toEntityName` | string \| null | no | no | no | The name of the receiving legal entity. See entityName on GET base/entities. | | `intercompanyProcessId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The intercompany process whose intercompany receivable and payable accounts balance each legal entity's side of the bill's journal entry. Bills created by a system process that don't name one use the default 'Intercompany Transaction' process. No endpoint returns intercompany processes. | | `intercompanyProcessName` | string \| null | no | no | no | The name of the intercompany process. | | `currency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The currency of the intercompany bill. Every amount on the bill and its lines is in this currency. Format ISO 4217, for example 'USD'. | | `exchangeRate` | decimal \| null | no | no | no | The rate used to convert amounts from the intercompany bill's currency to each legal entity's functional currency. The same rate is used for both legal entities. | | `billMarkup` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How markupAmount is applied to the lines. Possible values: 'none' (no markup: markupAmount and each line's markup are 0), 'percentage' (each line's markup is its amount multiplied by markupAmount), 'flatFee' (markupAmount is a total split evenly across the lines). | | `markupAmount` | decimal \| null | no | no | no | The markup on the lines. How to read markupAmount depends on billMarkup. When billMarkup is 'percentage', markupAmount is a decimal fraction of each line's amount, so 0.1 is 10%. When billMarkup is 'flatFee', markupAmount is a total in the intercompany bill's currency, split evenly across the lines and rounded to 2 decimal places, with any remainder on the first line. 0 when billMarkup is 'none'. | | `billDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date of the intercompany bill. Format YYYY-MM-DD. | | `paymentTerms` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The payment term of the intercompany bill. Possible values: 'net30', 'net60', 'net90', 'dueOnReceipt', 'net15', 'custom'. | | `dueDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the intercompany bill falls due. Format YYYY-MM-DD. | | `serviceDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The start date of the service period the intercompany bill covers. Format YYYY-MM-DD. | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The status of the intercompany bill. Possible values: 'draft', 'open', 'partiallySettled', 'settled', 'voided'. | | `voidReason` | string \| null | no | no | no | The reason given when the intercompany bill was voided. Absent when status is not 'voided'. | | `type` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How the intercompany bill was created. Possible values: 'manual' (in the application), 'system' (by a system process, such as an allocation run). | | `postingDirection` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The direction of the intercompany bill's journal entry postings. Possible values: 'normal', 'reverse'. When postingDirection is 'reverse', the debit and credit accounts of the intercompany process are swapped. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the intercompany bill last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyBillNumber": "", "name": "", "description": "", "fromEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fromEntityName": "", "toEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toEntityName": "", "intercompanyProcessId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyProcessName": "", "currency": "", "exchangeRate": "19.99", "billMarkup": "", "markupAmount": "19.99", "billDate": "2026-01-15", "paymentTerms": "", "dueDate": "2026-01-15", "serviceDate": "2026-01-15", "status": "", "voidReason": "", "type": "", "postingDirection": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search intercompany bills Operation ID: `searchIntercompanyBills` Group: Accounting › Intercompany Bills [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchIntercompanyBills) `POST /accounting/intercompany-bills/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches intercompany bills. Each intercompany bill is returned in full, including its lines. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchIntercompanyBillsInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. An intercompany bill must match every filter you send. Omit to return all intercompany bills. | | `filters.id` | string | no | Limits results to the intercompany bill with this identifier. | | `filters.intercompanyBillNumber` | string | no | Limits results to the intercompany bill with this intercompany bill number. The match is exact. Example: 'ICB-1'. | | `filters.name` | string | no | Limits results to intercompany bills whose name contains this text. The match is case-insensitive. A blank value is ignored. | | `filters.description` | string | no | Limits results to intercompany bills whose description contains this text. The match is case-insensitive. A blank value is ignored. | | `filters.fromEntityId` | string | no | Limits results to intercompany bills issued by this legal entity. See id on GET base/entities. | | `filters.toEntityId` | string | no | Limits results to intercompany bills issued to this legal entity. See id on GET base/entities. | | `filters.eitherEntityId` | string | no | Limits results to intercompany bills where this legal entity is either fromEntityId or toEntityId. See id on GET base/entities. | | `filters.status` | array | no | Limits results to intercompany bills in any of these statuses. Accepted values: 'draft', 'open', 'partiallySettled', 'settled', 'voided'. Any other value matches no intercompany bill. | | `filters.type` | array | no | Limits results to intercompany bills of any of these types. Accepted values: 'manual', 'system'. Any other value matches no intercompany bill. | | `filters.billMarkup` | array | no | Limits results to intercompany bills with any of these markup types. Accepted values: 'none', 'percentage', 'flatFee'. Any other value matches no intercompany bill. | | `filters.postingDirection` | array | no | Limits results to intercompany bills with any of these posting directions. Accepted values: 'normal', 'reverse'. Any other value matches no intercompany bill. | | `filters.currency` | string | no | Limits results to intercompany bills in this currency. The match is exact. Format ISO 4217, for example 'USD'. | | `filters.paymentTerms` | string | no | Limits results to intercompany bills with this payment term. Accepted values: 'net30', 'net60', 'net90', 'dueOnReceipt', 'net15', 'custom'. Any other value matches no intercompany bill. | | `filters.exchangeRate` | object | no | Limits results to intercompany bills whose exchangeRate is in this range. Omit either bound to leave that side open. | | `filters.exchangeRate.min` | decimal | no | The inclusive lower bound. | | `filters.exchangeRate.max` | decimal | no | The inclusive upper bound. | | `filters.markupAmount` | object | no | Limits results to intercompany bills whose markupAmount is in this range. Omit either bound to leave that side open. | | `filters.markupAmount.min` | decimal | no | The inclusive lower bound. | | `filters.markupAmount.max` | decimal | no | The inclusive upper bound. | | `filters.billDate` | object | no | Limits results to intercompany bills dated in this range. Omit either bound to leave that side open. | | `filters.billDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.billDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.dueDate` | object | no | Limits results to intercompany bills falling due in this range. Omit either bound to leave that side open. | | `filters.dueDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.dueDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.serviceDate` | object | no | Limits results to intercompany bills whose serviceDate is in this range. Omit either bound to leave that side open. | | `filters.serviceDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.serviceDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.lastModifiedDate` | object | no | Limits results to intercompany bills last changed in this range. Omit either bound to leave that side open. | | `filters.lastModifiedDate.start` | date-time | no | The inclusive lower bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.lastModifiedDate.end` | date-time | no | The inclusive upper bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `page` | object | no | The pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit on the first request. | | `page.size` | number | no | The number of intercompany bills per page, between 1 and 200. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyBillNumber": "", "name": "", "description": "", "fromEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "eitherEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "status": [ "" ], "type": [ "" ], "billMarkup": [ "" ], "postingDirection": [ "" ], "currency": "", "paymentTerms": "", "exchangeRate": { "min": "19.99", "max": "19.99" }, "markupAmount": { "min": "19.99", "max": "19.99" }, "billDate": { "start": "2026-01-15", "end": "2026-01-15" }, "dueDate": { "start": "2026-01-15", "end": "2026-01-15" }, "serviceDate": { "start": "2026-01-15", "end": "2026-01-15" }, "lastModifiedDate": { "start": "2026-01-15T09:30:00.000Z", "end": "2026-01-15T09:30:00.000Z" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchIntercompanyBillsOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The intercompany bills on this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the intercompany bill. | | `items[].intercompanyBillNumber` | string | no | The intercompany bill number. Unique within the tenant. Example: 'ICB-1'. | | `items[].name` | string | no | The name of the intercompany bill. | | `items[].description` | string | no | A description of the intercompany bill. | | `items[].fromEntityId` | string | no | The legal entity that issues the intercompany bill and is owed the amount. See id on GET base/entities. | | `items[].fromEntityName` | string | no | The name of the issuing legal entity. See entityName on GET base/entities. | | `items[].toEntityId` | string | no | The legal entity that receives the intercompany bill and is charged for it. See id on GET base/entities. | | `items[].toEntityName` | string | no | The name of the receiving legal entity. See entityName on GET base/entities. | | `items[].intercompanyProcessId` | string | no | The intercompany process whose intercompany receivable and payable accounts balance each legal entity's side of the bill's journal entry. Bills created by a system process that don't name one use the default 'Intercompany Transaction' process. No endpoint returns intercompany processes. | | `items[].intercompanyProcessName` | string | no | The name of the intercompany process. | | `items[].currency` | string | no | The currency of the intercompany bill. Every amount on the bill and its lines is in this currency. Format ISO 4217, for example 'USD'. | | `items[].exchangeRate` | decimal | no | The rate used to convert amounts from the intercompany bill's currency to each legal entity's functional currency. The same rate is used for both legal entities. | | `items[].billMarkup` | string | no | How markupAmount is applied to the lines. Possible values: 'none' (no markup: markupAmount and each line's markup are 0), 'percentage' (each line's markup is its amount multiplied by markupAmount), 'flatFee' (markupAmount is a total split evenly across the lines). | | `items[].markupAmount` | decimal | no | The markup on the lines. How to read markupAmount depends on billMarkup. When billMarkup is 'percentage', markupAmount is a decimal fraction of each line's amount, so 0.1 is 10%. When billMarkup is 'flatFee', markupAmount is a total in the intercompany bill's currency, split evenly across the lines and rounded to 2 decimal places, with any remainder on the first line. 0 when billMarkup is 'none'. | | `items[].billDate` | date | no | The date of the intercompany bill. Format YYYY-MM-DD. | | `items[].paymentTerms` | string | no | The payment term of the intercompany bill. Possible values: 'net30', 'net60', 'net90', 'dueOnReceipt', 'net15', 'custom'. | | `items[].dueDate` | date | no | The date the intercompany bill falls due. Format YYYY-MM-DD. | | `items[].serviceDate` | date | no | The start date of the service period the intercompany bill covers. Format YYYY-MM-DD. | | `items[].status` | string | no | The status of the intercompany bill. Possible values: 'draft', 'open', 'partiallySettled', 'settled', 'voided'. | | `items[].voidReason` | string | no | The reason given when the intercompany bill was voided. Absent when status is not 'voided'. | | `items[].type` | string | no | How the intercompany bill was created. Possible values: 'manual' (in the application), 'system' (by a system process, such as an allocation run). | | `items[].postingDirection` | string | no | The direction of the intercompany bill's journal entry postings. Possible values: 'normal', 'reverse'. When postingDirection is 'reverse', the debit and credit accounts of the intercompany process are swapped. | | `items[].lastModifiedDate` | date-time | yes | The date and time the intercompany bill last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].lines` | array | no | The lines of the intercompany bill. An empty array when the bill has no lines. | | `items[].lines[].id` | string | yes | The unique identifier for the line. | | `items[].lines[].description` | string | no | A description of the line. | | `items[].lines[].amount` | decimal | no | The amount of the line before markup, in the intercompany bill's currency. | | `items[].lines[].fromAccountId` | string | no | The account the issuing legal entity credits with the line's amount, typically where it records the income or recharge. Markup is credited to the issuing entity's markup account instead. See id on GET accounting/accounts/:id. | | `items[].lines[].toAccountId` | string | no | The account the receiving legal entity debits with the line's amount plus markup, typically where it records the cost or asset. See id on GET accounting/accounts/:id. | | `items[].lines[].fromValueAllocationId` | string | no | The numeric id of the value accounting tags on the issuing legal entity's side, not a UUID. No endpoint returns value accounting tags. Absent when value accounting tags are not in use. | | `items[].lines[].toValueAllocationId` | string | no | The numeric id of the value accounting tags on the receiving legal entity's side, not a UUID. No endpoint returns value accounting tags. Absent when value accounting tags are not in use. | | `items[].lines[].markupAmount` | decimal | no | The markup on the line, in the intercompany bill's currency, calculated from the bill's billMarkup and markupAmount. | | `items[].lines[].fromTaxAmount` | decimal | no | The tax the issuing legal entity charges on the line (output tax), in the intercompany bill's currency. It is the rate of the issuing entity's tax configuration multiplied by the line's amount plus markup. Absent when no tax applies. | | `items[].lines[].toTaxAmount` | decimal | no | The tax the receiving legal entity pays on the line and records as recoverable (input tax), in the intercompany bill's currency. It is the rate of the receiving entity's tax configuration multiplied by the line's amount plus markup. Absent when no tax applies. | | `pagination` | object | yes | The pagination details for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | Whether more intercompany bills remain after this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyBillNumber": "", "name": "", "description": "", "fromEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fromEntityName": "", "toEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toEntityName": "", "intercompanyProcessId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyProcessName": "", "currency": "", "exchangeRate": "19.99", "billMarkup": "", "markupAmount": "19.99", "billDate": "2026-01-15", "paymentTerms": "", "dueDate": "2026-01-15", "serviceDate": "2026-01-15", "status": "", "voidReason": "", "type": "", "postingDirection": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "", "amount": "19.99", "fromAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fromValueAllocationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toValueAllocationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "markupAmount": "19.99", "fromTaxAmount": "19.99", "toTaxAmount": "19.99" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an intercompany bill Operation ID: `getIntercompanyBill` Group: Accounting › Intercompany Bills [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getIntercompanyBill) `GET /accounting/intercompany-bills/{id}` Read-only: Reads and returns; changes nothing. Retrieves an intercompany bill. Use the intercompany bill's id, not its intercompanyBillNumber. The response includes the bill's lines. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the intercompany bill you want to retrieve. | ## Response `200` Success Under `data` (getIntercompanyBillOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the intercompany bill. | | `intercompanyBillNumber` | string | no | The intercompany bill number. Unique within the tenant. Example: 'ICB-1'. | | `name` | string | no | The name of the intercompany bill. | | `description` | string | no | A description of the intercompany bill. | | `fromEntityId` | string | no | The legal entity that issues the intercompany bill and is owed the amount. See id on GET base/entities. | | `fromEntityName` | string | no | The name of the issuing legal entity. See entityName on GET base/entities. | | `toEntityId` | string | no | The legal entity that receives the intercompany bill and is charged for it. See id on GET base/entities. | | `toEntityName` | string | no | The name of the receiving legal entity. See entityName on GET base/entities. | | `intercompanyProcessId` | string | no | The intercompany process whose intercompany receivable and payable accounts balance each legal entity's side of the bill's journal entry. Bills created by a system process that don't name one use the default 'Intercompany Transaction' process. No endpoint returns intercompany processes. | | `intercompanyProcessName` | string | no | The name of the intercompany process. | | `currency` | string | no | The currency of the intercompany bill. Every amount on the bill and its lines is in this currency. Format ISO 4217, for example 'USD'. | | `exchangeRate` | decimal | no | The rate used to convert amounts from the intercompany bill's currency to each legal entity's functional currency. The same rate is used for both legal entities. | | `billMarkup` | string | no | How markupAmount is applied to the lines. Possible values: 'none' (no markup: markupAmount and each line's markup are 0), 'percentage' (each line's markup is its amount multiplied by markupAmount), 'flatFee' (markupAmount is a total split evenly across the lines). | | `markupAmount` | decimal | no | The markup on the lines. How to read markupAmount depends on billMarkup. When billMarkup is 'percentage', markupAmount is a decimal fraction of each line's amount, so 0.1 is 10%. When billMarkup is 'flatFee', markupAmount is a total in the intercompany bill's currency, split evenly across the lines and rounded to 2 decimal places, with any remainder on the first line. 0 when billMarkup is 'none'. | | `billDate` | date | no | The date of the intercompany bill. Format YYYY-MM-DD. | | `paymentTerms` | string | no | The payment term of the intercompany bill. Possible values: 'net30', 'net60', 'net90', 'dueOnReceipt', 'net15', 'custom'. | | `dueDate` | date | no | The date the intercompany bill falls due. Format YYYY-MM-DD. | | `serviceDate` | date | no | The start date of the service period the intercompany bill covers. Format YYYY-MM-DD. | | `status` | string | no | The status of the intercompany bill. Possible values: 'draft', 'open', 'partiallySettled', 'settled', 'voided'. | | `voidReason` | string | no | The reason given when the intercompany bill was voided. Absent when status is not 'voided'. | | `type` | string | no | How the intercompany bill was created. Possible values: 'manual' (in the application), 'system' (by a system process, such as an allocation run). | | `postingDirection` | string | no | The direction of the intercompany bill's journal entry postings. Possible values: 'normal', 'reverse'. When postingDirection is 'reverse', the debit and credit accounts of the intercompany process are swapped. | | `lastModifiedDate` | date-time | yes | The date and time the intercompany bill last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `lines` | array | no | The lines of the intercompany bill. An empty array when the bill has no lines. | | `lines[].id` | string | yes | The unique identifier for the line. | | `lines[].description` | string | no | A description of the line. | | `lines[].amount` | decimal | no | The amount of the line before markup, in the intercompany bill's currency. | | `lines[].fromAccountId` | string | no | The account the issuing legal entity credits with the line's amount, typically where it records the income or recharge. Markup is credited to the issuing entity's markup account instead. See id on GET accounting/accounts/:id. | | `lines[].toAccountId` | string | no | The account the receiving legal entity debits with the line's amount plus markup, typically where it records the cost or asset. See id on GET accounting/accounts/:id. | | `lines[].fromValueAllocationId` | string | no | The numeric id of the value accounting tags on the issuing legal entity's side, not a UUID. No endpoint returns value accounting tags. Absent when value accounting tags are not in use. | | `lines[].toValueAllocationId` | string | no | The numeric id of the value accounting tags on the receiving legal entity's side, not a UUID. No endpoint returns value accounting tags. Absent when value accounting tags are not in use. | | `lines[].markupAmount` | decimal | no | The markup on the line, in the intercompany bill's currency, calculated from the bill's billMarkup and markupAmount. | | `lines[].fromTaxAmount` | decimal | no | The tax the issuing legal entity charges on the line (output tax), in the intercompany bill's currency. It is the rate of the issuing entity's tax configuration multiplied by the line's amount plus markup. Absent when no tax applies. | | `lines[].toTaxAmount` | decimal | no | The tax the receiving legal entity pays on the line and records as recoverable (input tax), in the intercompany bill's currency. It is the rate of the receiving entity's tax configuration multiplied by the line's amount plus markup. Absent when no tax applies. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyBillNumber": "", "name": "", "description": "", "fromEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fromEntityName": "", "toEntityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toEntityName": "", "intercompanyProcessId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyProcessName": "", "currency": "", "exchangeRate": "19.99", "billMarkup": "", "markupAmount": "19.99", "billDate": "2026-01-15", "paymentTerms": "", "dueDate": "2026-01-15", "serviceDate": "2026-01-15", "status": "", "voidReason": "", "type": "", "postingDirection": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "", "amount": "19.99", "fromAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "fromValueAllocationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "toValueAllocationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "markupAmount": "19.99", "fromTaxAmount": "19.99", "toTaxAmount": "19.99" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Invoice Frequency Templates Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingInvoiceFrequencyTemplates) An invoice frequency template is a reusable invoicing schedule, written as an RFC 5545 recurrence rule, that a sales order product line can use instead of a fixed invoice frequency. ## Endpoints - [List invoice frequency templates](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/invoiceFrequencyTemplates.html): `GET /accounting/invoice-frequency-templates` --- # List invoice frequency templates Operation ID: `invoiceFrequencyTemplates` Group: Accounting › Invoice Frequency Templates [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/invoiceFrequencyTemplates) `GET /accounting/invoice-frequency-templates` Read-only: Reads and returns; changes nothing. Lists invoice frequency templates. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `name` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `isEvenInvoicing` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, name, isEvenInvoicing, lastModifiedDate - Selectable: id, name, rule, isEvenInvoicing, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of invoiceFrequencyTemplatesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the invoice frequency template. | | `name` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The name of the invoice frequency template. Example: 'Twice Monthly'. | | `rule` | string | no | no | no | The rule that sets the template's invoice dates. Format RFC 5545 RRULE. BYMONTHDAY=-1 means the last day of the month, whatever its length. Example: 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15,-1', for the 15th and the last day of every month. | | `isEvenInvoicing` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the amount for a billing period is split evenly across the period's invoices. When false, the amount is split by the number of days each invoice covers. A partial period is split by days either way. The setting applies to recurring product lines, not usage lines. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the invoice frequency template last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "rule": "", "isEvenInvoicing": true, "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Invoice Runs Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingInvoiceRuns) An invoice run invoices the pending lines of one sales order, customer, or legal entity, and records the invoices it created and the lines that failed. ## Endpoints - [List invoice runs](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/invoiceRuns.html): `GET /accounting/invoice-runs` - [Start an invoice run](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/startInvoiceRun.html): `POST /accounting/invoice-runs` - [Retrieve an invoice run](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getInvoiceRun.html): `GET /accounting/invoice-runs/{id}` - [Retry an invoice run's failed lines](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/rerunInvoiceRun.html): `POST /accounting/invoice-runs/{id}/rerun` --- # List invoice runs Operation ID: `invoiceRuns` Group: Accounting › Invoice Runs [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/invoiceRuns) `GET /accounting/invoice-runs` Read-only: Reads and returns; changes nothing. Lists invoice runs. The collection doesn't include customerId, invoices, errors, or progress. To see a run's invoices and errors, use GET accounting/invoice-runs/:id. If a run shows 'running' for much longer than a run usually takes, read the run with GET accounting/invoice-runs/:id to get its corrected 'status'. 'scope' here is one text value, where GET accounting/invoice-runs/:id returns an array. To find a customer's invoices, use GET accounting/invoices filtered by customerId. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `invoiceRunNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `trigger` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `salesOrderId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `startedAt` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `finishedAt` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: invoiceRunNumber, startedAt, finishedAt, lastModifiedDate - Selectable: id, invoiceRunNumber, status, invoicesCreated, trigger, scope, entityId, salesOrderId, startedAt, finishedAt, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of invoiceRunsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The unique identifier for the invoice run. Example: `"1c9d6b73-4a20-4e58-8f31-7b2e0c4a9d15"`. | | `invoiceRunNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The invoice run number, for example 'ISR-1042'. Example: `"ISR-1042"`. | | `status` | string \| null | no | no | no | The execution status of the run, as stored rather than corrected, so a run whose worker was killed or lost reads 'running' without end. GET accounting/invoice-runs/:id reports 'error' for such a run instead. Possible values: 'created', 'running', 'completed', 'error', 'completeAndError'. Example: `"completed"`. | | `invoicesCreated` | number \| null | no | no | no | The number of invoices the run created. Example: `3`. | | `trigger` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | What started the run. Possible values: 'immediately', 'daily', 'monthly'. Example: `"immediately"`. | | `scope` | string \| null | no | no | no | The line types billed, as the raw stored text: the billed line types joined by semicolons into one string, such as 'all'. GET accounting/invoice-runs/:id publishes this as an array instead. Example: `"all"`. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The legal entity billed. See id on GET base/entities. Example: `"6f1b2a84-3d97-4c25-8e60-9a4f0b7d1c53"`. | | `salesOrderId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The sales order billed. Absent on a customer- or entity-scoped run. See id on GET accounting/sales-orders/:id. Example: `"8f14e45f-ceea-467a-9f6b-2c1d0e5a7b39"`. | | `startedAt` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the run was created, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Example: `"2026-09-30T08:14:22.000Z"`. | | `finishedAt` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the run finished, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Absent while the run is still working, and on a run whose worker stopped without recording one. Example: `"2026-09-30T08:14:41.000Z"`. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the invoice run record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Example: `"2026-09-30T08:14:41.000Z"`. | ```json { "data": [ { "id": "1c9d6b73-4a20-4e58-8f31-7b2e0c4a9d15", "invoiceRunNumber": "ISR-1042", "status": "completed", "invoicesCreated": 3, "trigger": "immediately", "scope": "all", "entityId": "6f1b2a84-3d97-4c25-8e60-9a4f0b7d1c53", "salesOrderId": "8f14e45f-ceea-467a-9f6b-2c1d0e5a7b39", "startedAt": "2026-09-30T08:14:22.000Z", "finishedAt": "2026-09-30T08:14:41.000Z", "lastModifiedDate": "2026-09-30T08:14:41.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Start an invoice run Operation ID: `startInvoiceRun` Group: Accounting › Invoice Runs [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/startInvoiceRun) `POST /accounting/invoice-runs` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Starts an invoice run for a sales order, customer, or legal entity. A run links each invoice it creates to itself and saves an error for each line that fails. The response contains the run's id. Send exactly one of 'salesOrderId', 'customerId', or 'entityId'. The date field you send depends on that choice. A sales order run takes 'billingDates', the exact planned dates to invoice, and a customer or legal entity run needs 'billUpTo', which invoices everything planned on or before that date. By default the request returns while the run is working, and you poll GET accounting/invoice-runs/:id. Set 'sync' to true to wait for the final status instead. A customer or legal entity run can take minutes, so use 'sync' for a single sales order. A new run leaves out lines that failed in an earlier run. For a sales order run with 'sync' set to true, if that leaves nothing to invoice, the response includes 'unresolvedRunId'. Send POST accounting/invoice-runs/:id/rerun for that run instead of repeating this request. While an asynchronous run is working, another request with the same target, 'scope', dates, and 'invoiceDate' joins that run and doesn't invoice the same lines twice. The response then has 'deduplicated' set to true, so you can retry a request that timed out. After the run finishes, the same request starts a new run. This protection doesn't apply when 'sync' is true, so two identical requests with 'sync' set to true can both invoice the same lines. For a sales order that another system invoices, the request fails with SALES_ORDER_EXTERNALISED. Import its invoices with POST accounting/invoices/import. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: startInvoiceRunInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `salesOrderId` | string | no | The sales order to bill. Give exactly one of 'salesOrderId', 'customerId' or 'entityId'. Alone, it bills every pending line on the order. See id on GET accounting/sales-orders/:id. Example: `"8f14e45f-ceea-467a-9f6b-2c1d0e5a7b39"`. | | `customerId` | string | no | The customer to bill, across all their sales orders. Give exactly one of the three targets. Requires 'billUpTo'. See id on GET accounting/customers/:id. | | `entityId` | string | no | The legal entity to bill, across every approved order in it. This is the scope the scheduled nightly run uses, and the broadest this API offers. Give exactly one of the three targets. Requires 'billUpTo'. See id on GET base/entities. | | `billingDates` | array | no | The exact planned dates to bill, for sales-order runs only. Use format YYYY-MM-DD. Only planned invoicing lines on these dates are billed. Omit to bill every pending line on the order. Not accepted on a customer or entity run, which uses billUpTo instead. | | `billUpTo` | date | no | Customer and entity runs only. Bills everything planned on or before this date, an upper bound rather than an exact match. Use format YYYY-MM-DD. Required on both. Rejected on a sales-order run, which names exact dates instead. | | `invoiceDate` | date | no | The date stamped on the invoices created. Use format YYYY-MM-DD. Defaults to today. Must fall in an open accounting period. Example: `"2026-09-30"`. | | `scope` | array | no | The line types to bill. Defaults to all line types. 'oneTimeAndRecurring' covers one-time, recurring and commit lines. 'billableProducts' covers billable time, travel and expense, and pass-through. | | `sync` | boolean | no | Whether to block until the run has settled and report its final status and count. Defaults to false, which returns immediately with 'status' of 'running' for you to poll. Use it for one sales order, not for a customer-wide run. Example: `true`. | ```json { "salesOrderId": "8f14e45f-ceea-467a-9f6b-2c1d0e5a7b39", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "billingDates": [ "2026-01-15" ], "billUpTo": "2026-01-15", "invoiceDate": "2026-09-30", "scope": [ "all" ], "sync": true } ``` ## Response `200` Success Under `data` (startInvoiceRunOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `data` | object | yes | The invoice run that was started. Example: `{"id":"1c9d6b73-4a20-4e58-8f31-7b2e0c4a9d15","invoiceRunNumber":"ISR-1042","status":"completed","invoicesCreated":3}`. | | `data.id` | string | yes | The unique identifier for the created invoice run. | | `data.invoiceRunNumber` | string | yes | The invoice run number, for example 'ISR-1042'. | | `data.status` | string | yes | The execution status of the run. On an async call it is 'created' or 'running'. With 'sync' set to true it is settled: 'completed', 'error', or 'completeAndError' when some invoices were created and some lines failed. | | `data.invoicesCreated` | number | yes | The number of invoices the run has created so far. Meaningful once the run has settled. 0 on an async call that has not finished. | | `data.unresolvedRunId` | string | no | The unique identifier for an earlier failed run, present only when this run could produce nothing because that run's failed lines are held back. Rerun that one rather than repeating this call. | | `data.deduplicated` | boolean | no | True when an identical run was already in flight and this call joined it instead of starting a second. The id above is that run's. Absent when this call started the run. | ```json { "data": { "data": { "id": "1c9d6b73-4a20-4e58-8f31-7b2e0c4a9d15", "invoiceRunNumber": "ISR-1042", "status": "completed", "invoicesCreated": 3 } }, "diagnostics": [ { "code": "INVOICE_RUN_STARTED", "message": "", "severity": "error", "path": [ "" ], "details": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "status": "" } } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NO_TARGET_GIVEN` | 400 | | `MULTIPLE_TARGETS_GIVEN` | 400 | | `SALES_ORDER_NOT_FOUND` | 400 | | `CUSTOMER_NOT_FOUND` | 400 | | `ENTITY_NOT_FOUND` | 400 | | `SALES_ORDER_EXTERNALISED` | 400 | | `SALES_ORDER_NOT_APPROVED` | 400 | | `BILL_UP_TO_NOT_APPLICABLE` | 400 | | `BILLING_DATES_NOT_APPLICABLE` | 400 | | `BILL_UP_TO_REQUIRED` | 400 | | `ACCOUNTING_PERIOD_CLOSED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Advisories Warnings and information a successful response can carry under `diagnostics`: - `INVOICE_RUN_STARTED` - `INVOICE_RUN_HELD_BACK` - `INVOICE_RUN_ALREADY_RUNNING` ## Statuses - `400` — Invalid request or business failure. Codes: NO_TARGET_GIVEN, MULTIPLE_TARGETS_GIVEN, SALES_ORDER_NOT_FOUND, CUSTOMER_NOT_FOUND, ENTITY_NOT_FOUND, SALES_ORDER_EXTERNALISED, SALES_ORDER_NOT_APPROVED, BILL_UP_TO_NOT_APPLICABLE, BILLING_DATES_NOT_APPLICABLE, BILL_UP_TO_REQUIRED, ACCOUNTING_PERIOD_CLOSED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an invoice run Operation ID: `getInvoiceRun` Group: Accounting › Invoice Runs [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getInvoiceRun) `GET /accounting/invoice-runs/{id}` Read-only: Reads and returns; changes nothing. Retrieves an invoice run. Use the invoice run's id, not its invoiceRunNumber. The response shows what the run covered, its status, the invoices it created and the lines that failed. 'completed' means every selected line was invoiced. 'error' means the run failed, and 'errors' lists the lines that failed. Check 'status' rather than 'invoicesCreated', because a 'completeAndError' run has created invoices and also has failed lines. If the run's job stops without finishing, for example because it was canceled or lost, this endpoint reports 'error'. The saved status stays 'running', and GET accounting/invoice-runs returns that saved status. To start a run, use POST accounting/invoice-runs. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the invoice run you want to retrieve. | ## Response `200` Success Under `data` (getInvoiceRunOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `data` | object | yes | The requested invoice run. Example: `{"id":"1c9d6b73-4a20-4e58-8f31-7b2e0c4a9d15","invoiceRunNumber":"ISR-1042","status":"completeAndError","invoicesCreated":2,"startedAt":"2026-09-30T08:14:22.000Z","finishedAt":"2026-09-30T08:15:07.000Z","trigger":"immediately","scope":["all"],"entityId":"6f1b2a84-3d97-4c25-8e60-9a4f0b7d1c53","salesOrderId":"8f14e45f-ceea-467a-9f6b-2c1d0e5a7b39","customerId":"d41d8cd9-8f00-4b20-a204-9800998ecf84","invoices":[{"id":"a2c5e881-4f37-4b09-9d16-8e3b0c7a5d24","invoiceNumber":"INV-20841","totalAmount":"1210.00","currency":"EUR"}],"errors":[{"type":"taxCode","detail":"No tax code resolves for product line SO-114-3 in DE.","customerId":"d41d8cd9-8f00-4b20-a204-9800998ecf84"}]}`. | | `data.id` | string | yes | The unique identifier for the invoice run. | | `data.invoiceRunNumber` | string | no | The invoice run number, for example 'ISR-1042'. Absent while none has been assigned. | | `data.status` | string | yes | The execution status of the run. Possible values: 'created', 'running', 'completed', 'error', 'completeAndError'. Keep polling on 'created' and 'running'. 'completeAndError' means some invoices were created and some lines failed. | | `data.invoicesCreated` | number | yes | The number of invoices the run has created. | | `data.startedAt` | string | no | The date and time the run was created, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `data.finishedAt` | string | no | The date and time the run finished, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. Absent while the run is still working, and on a run whose worker stopped without recording one. | | `data.lastModifiedDate` | string | no | The date and time the invoice run record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `data.trigger` | string | no | What started the run. Possible values: 'immediately', 'daily', 'monthly'. 'immediately' is an ad-hoc run. 'daily' and 'monthly' are scheduled. | | `data.scope` | array | yes | The line types the run billed. | | `data.entityId` | string | no | The legal entity billed. See id on GET base/entities. | | `data.salesOrderId` | string | no | The sales order billed. Present on a sales-order-scoped run. See id on GET accounting/sales-orders/:id. | | `data.customerId` | string | no | The customer billed. Absent on a run that created no invoice and recorded no error. See id on GET accounting/customers/:id. | | `data.progress` | object | no | The run's own progress reporting while it works. It is advisory: absent before the run starts reporting, and never a substitute for 'status'. | | `data.progress.title` | string | no | The phase the run is in, for example preparing data or creating invoices. | | `data.progress.message` | string | no | Further detail on the current phase, when the run supplies any. | | `data.progress.progress` | number | no | The number of items processed so far in the current phase. | | `data.progress.total` | number | no | The number of items to process in the current phase. | | `data.invoices` | array | yes | The invoices the run created. Each has its own currency, so there is no summed run total. | | `data.invoices[].id` | string | yes | The unique identifier for the invoice. See id on GET accounting/invoices/:id. | | `data.invoices[].invoiceNumber` | string | no | The invoice number, for example 'INV-20841'. | | `data.invoices[].totalAmount` | decimal | no | The total amount of the invoice including tax, in the currency named by 'currency'. | | `data.invoices[].currency` | string | no | The currency of the invoice. Format ISO 4217, for example 'EUR'. | | `data.errors` | array | yes | The lines the run could not bill, one entry each. A non-empty array with a non-zero 'invoicesCreated' is the 'completeAndError' outcome. | | `data.errors[].type` | string | yes | Why the line could not be billed. Possible values: 'system', 'avalaraAddress', 'taxCode', 'none', 'taxCalculation', 'vatEntity', 'accountConfig', 'vatCustomer', 'invoiceSetupIncomplete', 'invoiceDelivery'. | | `data.errors[].detail` | string | no | A description of why the line failed. | | `data.errors[].customerId` | string | no | The customer the failing line belongs to. See id on GET accounting/customers/:id. | ```json { "data": { "data": { "id": "1c9d6b73-4a20-4e58-8f31-7b2e0c4a9d15", "invoiceRunNumber": "ISR-1042", "status": "completeAndError", "invoicesCreated": 2, "startedAt": "2026-09-30T08:14:22.000Z", "finishedAt": "2026-09-30T08:15:07.000Z", "trigger": "immediately", "scope": [ "all" ], "entityId": "6f1b2a84-3d97-4c25-8e60-9a4f0b7d1c53", "salesOrderId": "8f14e45f-ceea-467a-9f6b-2c1d0e5a7b39", "customerId": "d41d8cd9-8f00-4b20-a204-9800998ecf84", "invoices": [ { "id": "a2c5e881-4f37-4b09-9d16-8e3b0c7a5d24", "invoiceNumber": "INV-20841", "totalAmount": "1210.00", "currency": "EUR" } ], "errors": [ { "type": "taxCode", "detail": "No tax code resolves for product line SO-114-3 in DE.", "customerId": "d41d8cd9-8f00-4b20-a204-9800998ecf84" } ] } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retry an invoice run's failed lines Operation ID: `rerunInvoiceRun` Group: Accounting › Invoice Runs [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/rerunInvoiceRun) `POST /accounting/invoice-runs/{id}/rerun` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Retries the failed lines of an invoice run. The rerun selects the lines that failed in this run, removes their old errors, and tries to invoice them again. Only a run whose saved status is 'error' or 'completeAndError' can be rerun. A run that GET accounting/invoice-runs/:id reports as 'error' only because its job stopped fails with RUN_NOT_FAILED. Starting a new run doesn't retry these lines. A new run leaves out any line that already failed, so repeating POST accounting/invoice-runs after a failure invoices nothing. The request returns once the rerun has finished, so you don't need to poll. A successful rerun sets the run's status to 'completed'. If a line fails again, the request fails with an error. Fix the cause, such as a missing tax code or an incomplete customer setup, and then send the rerun request again. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the invoice run you want to retry. | ## Response `200` Success Under `data` (rerunInvoiceRunOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the invoice run retried. It is the same run, not a new one. Example: `"3f8b2d47-1c69-4a05-9e83-7d4a0f6b2e91"`. | | `status` | string | yes | The status the run settled at after the retry. Possible values: 'completed', 'error', 'completeAndError'. 'error' or 'completeAndError' means the retry hit the same problem, and the run can be rerun again once the cause is fixed. Example: `"completed"`. | | `invoicesCreated` | number | yes | The total number of invoices the run has created, including those from before the retry. Example: `2`. | ```json { "data": { "id": "3f8b2d47-1c69-4a05-9e83-7d4a0f6b2e91", "status": "completed", "invoicesCreated": 2 }, "diagnostics": [ { "code": "INVOICE_RUN_RERUN", "message": "", "severity": "error", "path": [ "" ], "details": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "status": "" } } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `RUN_NOT_FOUND` | 400 | | `RUN_NOT_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Advisories Warnings and information a successful response can carry under `diagnostics`: - `INVOICE_RUN_RERUN` ## Statuses - `400` — Invalid request or business failure. Codes: RUN_NOT_FOUND, RUN_NOT_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Invoices Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingInvoices) Invoices are what a legal entity has billed a customer for, and what is still owed on each one. ## Endpoints - [List invoices](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/invoices.html): `GET /accounting/invoices` - [Import invoices](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/importInvoices.html): `POST /accounting/invoices/import` - [Search invoices](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchInvoice.html): `POST /accounting/invoices/search` - [Retrieve an invoice](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getInvoice.html): `GET /accounting/invoices/{id}` - [Update an invoice](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateInvoice.html): `PATCH /accounting/invoices/{id}` - [Record a payment against an invoice](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/payInvoice.html): `POST /accounting/invoices/{id}/pay` ## Events - [An invoice was issued.](https://docs.api.lensing.ai/reference/lensing/v1/events/accounting_invoices_created.html) - [An invoice changed after it was issued.](https://docs.api.lensing.ai/reference/lensing/v1/events/accounting_invoices_updated.html) --- # List invoices Operation ID: `invoices` Group: Accounting › Invoices [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/invoices) `GET /accounting/invoices` Read-only: Reads and returns; changes nothing. Lists invoices. The collection doesn't include lines, billToAddress, soldToAddress, purchaseOrders, or externalIdentifiers. To get these fields, use GET accounting/invoices/:id or POST accounting/invoices/search. To filter by invoiceNumber, customerName, customerNumber, entityName, salesOrderNumber, billToAddress, soldToAddress, invoiceEmail, purchaseOrderOverride, externalInvoiceId, customerMessage, or externalIdentifier, use POST accounting/invoices/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `invoiceDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `dueDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastPaidDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `invoiceStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `approvalStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `deliveryStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `disputeStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `dataSource` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `currency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `paymentTermDays` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `exchangeRate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `excludeFromTaxEngine` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `customerId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `entityId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `salesOrderId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `subtotal` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `totalTax` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `totalDutiesAndCustoms` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `totalAmount` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `totalAmountDue` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `totalCredited` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `totalLateFeeAmount` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lineDiscounts` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `oneTimeDiscount` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `paymentsApplied` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `adjustments` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, invoiceDate, dueDate, lastPaidDate, invoiceStatus, approvalStatus, deliveryStatus, disputeStatus, dataSource, currency, customerId, entityId, salesOrderId, paymentTermDays, exchangeRate, excludeFromTaxEngine, subtotal, totalTax, totalDutiesAndCustoms, totalAmount, totalAmountDue, totalCredited, totalLateFeeAmount, lineDiscounts, oneTimeDiscount, paymentsApplied, adjustments, lastModifiedDate - Selectable: id, invoiceNumber, invoiceDate, dueDate, lastPaidDate, invoiceStatus, approvalStatus, deliveryStatus, disputeStatus, dataSource, currency, customerMessage, externalInvoiceId, invoiceEmail, purchaseOrderOverride, paymentTermDays, exchangeRate, excludeFromTaxEngine, customerId, customerName, customerNumber, entityId, entityName, salesOrderId, salesOrderNumber, subtotal, totalTax, totalDutiesAndCustoms, totalAmount, totalAmountDue, totalCredited, totalLateFeeAmount, lineDiscounts, oneTimeDiscount, paymentsApplied, adjustments, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of invoicesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the invoice. | | `invoiceNumber` | string \| null | no | no | no | The invoice number, for example 'INV-1', unique within the tenant. Absent while none has been assigned. | | `invoiceDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the invoice was issued. Format YYYY-MM-DD. Payment terms and the general ledger impact are both based on this date. | | `dueDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date payment falls due, the invoice date plus the payment term. Format YYYY-MM-DD. Absent when no term has been resolved. | | `lastPaidDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the most recent payment was applied. Format YYYY-MM-DD. Absent on an invoice that has never been paid against. | | `invoiceStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The settlement status of the invoice. Possible values: 'unpaid', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'isOverdue', 'disputed'. | | `approvalStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The finance approval status of the invoice. Possible values: 'draft', 'pendingApproval', 'approved', 'revise', 'rejected'. | | `deliveryStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How far the invoice has got in being sent to the customer. Possible values: 'created', 'toBeSent', 'delivered', 'read', 'failed'. | | `disputeStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The dispute status of the invoice, set only when payments against it are under dispute. Possible values: 'disputed', 'chargedBack'. | | `dataSource` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The system the invoice record was imported from, for invoices that arrived through an integration or migration rather than being created in this system. For example 'NetSuite', 'Quickbooks', 'Stripe' or 'MigrationFile'. Absent on invoices created in this system. | | `currency` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The currency of the invoice. Format ISO 4217, for example 'USD'. It applies to every amount on the record, on the header and on every line. | | `customerMessage` | string \| null | no | no | no | A free-form message printed on the invoice for the customer. | | `externalInvoiceId` | string \| null | no | no | no | The invoice's identifier in an external source system, kept for reconciliation. For identifiers owned by your other systems, prefer externalIdentifiers below. | | `invoiceEmail` | string \| null | no | no | no | The email address of the business contact that receives this invoice. | | `purchaseOrderOverride` | string \| null | no | no | no | A manually entered PO number that overrides the sales-order-derived values in purchaseOrders. | | `paymentTermDays` | number \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The number of days after the invoice date that payment falls due, resolved from the invoice's payment term. The term record itself is not published, only its day count. | | `exchangeRate` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The rate used to convert the invoice's currency to the entity's functional currency. Absent when no conversion applies. | | `excludeFromTaxEngine` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | Whether the invoice is left out of the tax engine. Derived from the sales order. | | `customerId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The customer the invoice is issued to. See id on GET accounting/customers/:id. | | `customerName` | string \| null | no | no | no | The display name of the customer, as it appears on the document. | | `customerNumber` | string \| null | no | no | no | The customer number of the customer, unique within the tenant. | | `entityId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The legal entity issuing the invoice. See id on GET base/entities. | | `entityName` | string \| null | no | no | no | The name of the issuing legal entity. | | `salesOrderId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The sales order the invoice was billed from. Absent on invoices created directly. See id on GET accounting/sales-orders/:id. | | `salesOrderNumber` | string \| null | no | no | no | The number of the source sales order. Present whenever salesOrderId is. | | `subtotal` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The amount before taxes and discounts, in the invoice currency. Absent when no subtotal has been computed. | | `totalTax` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The total tax across all lines, in the invoice currency. | | `totalDutiesAndCustoms` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The total duties and customs across all lines, in the invoice currency. | | `totalAmount` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The total invoiced, including taxes and duties, in the invoice currency. | | `totalAmountDue` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The amount that was due when the invoice was created, in the invoice currency. | | `totalCredited` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The total credited back against this invoice by credit memos, in the invoice currency. | | `totalLateFeeAmount` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The total late fees charged on this invoice, in the invoice currency. | | `lineDiscounts` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The sum of discount amounts across all invoice lines, in the invoice currency. | | `oneTimeDiscount` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The one-time discount applied to the invoice as a whole, in the invoice currency. | | `paymentsApplied` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The amount settled by payments so far, in the invoice currency. | | `adjustments` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The sum of small-balance write-offs and early-payment discounts recorded against this invoice, in the invoice currency. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the invoice record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "invoiceDate": "2026-01-15", "dueDate": "2026-01-15", "lastPaidDate": "2026-01-15", "invoiceStatus": "", "approvalStatus": "", "deliveryStatus": "", "disputeStatus": "", "dataSource": "", "currency": "", "customerMessage": "", "externalInvoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceEmail": "", "purchaseOrderOverride": "", "paymentTermDays": 0, "exchangeRate": "19.99", "excludeFromTaxEngine": true, "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "salesOrderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesOrderNumber": "", "subtotal": "19.99", "totalTax": "19.99", "totalDutiesAndCustoms": "19.99", "totalAmount": "19.99", "totalAmountDue": "19.99", "totalCredited": "19.99", "totalLateFeeAmount": "19.99", "lineDiscounts": "19.99", "oneTimeDiscount": "19.99", "paymentsApplied": "19.99", "adjustments": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Import invoices Operation ID: `importInvoices` Group: Accounting › Invoices [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/importInvoices) `POST /accounting/invoices/import` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Imports invoices from another system. Use it for sales orders that another system invoices, where externalizedInvoicing is true on GET accounting/sales-orders/:id. Each invoice is created exactly as you send it. It doesn't group lines into invoices, calculate tax, or apply one-time discounts for these orders. For each line, send its total and its tax. For a tax-exempt line, send an empty taxLines array. Include any one-time discount in the line's totalPrice. Each line is matched to its planned invoicing line on the sales order by salesOrderProductLineId and plannedDate. If the sales order has no planned line for that date and its invoicing schedule is still open, one is created. If any invoice can't be imported, none are. The response lists the new invoices' ids in the order you sent them. To read one, use GET accounting/invoices/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: importInvoicesInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `salesOrderId` | string | yes | The sales order these invoices bill. It must be flagged for externalized billing. One whose billing is scheduled in this system is refused with SALES_ORDER_NOT_EXTERNALISED. See id on GET accounting/sales-orders/:id. Example: `"8c1f4a92-3e77-4d21-9b05-6a2f1c8e4d33"`. | | `invoices` | array | yes | The invoices to record, one entry each, exactly as the source system grouped them. Every entry yields one invoice, and the ids come back in this order. Example: `[{"externalInvoiceId":"NS-INV-88213","invoiceDate":"2026-09-01","lines":[{"salesOrderProductLineId":"b2d7f309-6c14-4a85-9f37-0e8c5d1b7a26","plannedDate":"2026-09-01","serviceEndDate":"2026-09-30","totalPrice":"1000.00","taxAmount":"210.00","taxCode":"NL-VAT-STD","taxMode":"TaxExclusive","taxLines":[{"name":"NL VAT","amount":"210.00","rate":"0.21","taxableAmount":"1000.00","taxType":"VAT","rateType":"Standard","taxTreatment":"domesticStandardRate"}]}]}]`. | | `invoices[].externalInvoiceId` | string | no | The id of this invoice in the source system, stamped on the created invoice for reconciliation. | | `invoices[].invoiceDate` | date | yes | The date to stamp on this invoice. Format YYYY-MM-DD. | | `invoices[].lines` | array | yes | The lines of this invoice. Each resolves to one invoice-plan line by salesOrderProductLineId and plannedDate. | | `invoices[].lines[].salesOrderProductLineId` | string | yes | The unique identifier for the sales-order product line this invoice line bills against. There is no endpoint that returns a single sales-order product line. | | `invoices[].lines[].plannedDate` | date | yes | The planned billing date of the target invoice-plan line. Format YYYY-MM-DD. When no plan line exists for this date and the schedule is still open, one is created using this as both its service start date and its invoice date. | | `invoices[].lines[].serviceEndDate` | date | no | The last day of the service period, for a plan line created on an open schedule. Format YYYY-MM-DD. Ignored when the line matches an existing plan line. | | `invoices[].lines[].totalPrice` | decimal | yes | The pre-tax line total, in the sales order's currency. It must match the resolved plan line's total price. Any one-time discount must already be netted in. | | `invoices[].lines[].dutiesAndCustomsAmount` | decimal | no | The duties and customs charged on this line, in the sales order's currency. Requires the entity's Duties and Customs feature to be enabled. | | `invoices[].lines[].taxAmount` | decimal | no | The total tax on this line, in the sales order's currency. When given, it must equal the sum of taxLines[].amount. When omitted, it is computed from that sum. | | `invoices[].lines[].taxCode` | string | no | The line-level tax code, as the source tax engine issued it. | | `invoices[].lines[].taxDate` | date | no | The date the line's tax was determined on. Format YYYY-MM-DD. Typically the service end date or the invoice date. | | `invoices[].lines[].taxExemptCode` | string | no | The exempt code applied to this line. | | `invoices[].lines[].exceptionNumber` | string | no | The exception number for the tax exemption, as the source tax engine issued it. | | `invoices[].lines[].taxMode` | any | no | Whether totalPrice already includes tax. One of: `"TaxInclusive"`, `"TaxExclusive"`. | | `invoices[].lines[].taxLines` | array | yes | The per-jurisdiction tax for this line. You supply it, because no tax engine is called on this path. Send an empty array for a tax-exempt line. | | `invoices[].lines[].taxLines[].name` | string | yes | The name of the tax as it should appear on the document, for example 'CA Sales Tax' or 'VAT'. | | `invoices[].lines[].taxLines[].amount` | decimal | yes | The tax amount for this jurisdiction, in the sales order's currency. | | `invoices[].lines[].taxLines[].rate` | decimal | no | The tax rate as a decimal fraction. For example, 0.21 is 21%. A rate, not an amount, so it has no currency. | | `invoices[].lines[].taxLines[].taxableAmount` | decimal | no | The pre-tax amount the rate was applied to, in the sales order's currency. | | `invoices[].lines[].taxLines[].exemptAmount` | decimal | no | The portion of the taxable base that was exempted, in the sales order's currency. | | `invoices[].lines[].taxLines[].taxType` | string | no | The engine classification of the tax, for example 'Sales' or 'VAT'. | | `invoices[].lines[].taxLines[].rateType` | string | no | The VAT rate classification, for example 'Standard', 'Reduced' or 'Zero'. | | `invoices[].lines[].taxLines[].vatCode` | string | no | The VAT code for this jurisdiction. | | `invoices[].lines[].taxLines[].isItemTaxable` | boolean | no | Whether the item is taxable in this jurisdiction. | | `invoices[].lines[].taxLines[].taxTreatment` | any | no | The VAT treatment for this jurisdiction. When supplied, the built-in VAT determination is bypassed. One of: `"salesTax"`, `"domesticStandardRate"`, `"domesticReducedRate"`, `"domesticSalesZeroRated"`, `"domesticSalesExempt"`, `"vatIntraEuStandardRate"`, `"vatIntraEuReducedRate"`, `"nonEuExempt"`, `"reverseCharge"`. | ```json { "salesOrderId": "8c1f4a92-3e77-4d21-9b05-6a2f1c8e4d33", "invoices": [ { "externalInvoiceId": "NS-INV-88213", "invoiceDate": "2026-09-01", "lines": [ { "salesOrderProductLineId": "b2d7f309-6c14-4a85-9f37-0e8c5d1b7a26", "plannedDate": "2026-09-01", "serviceEndDate": "2026-09-30", "totalPrice": "1000.00", "taxAmount": "210.00", "taxCode": "NL-VAT-STD", "taxMode": "TaxExclusive", "taxLines": [ { "name": "NL VAT", "amount": "210.00", "rate": "0.21", "taxableAmount": "1000.00", "taxType": "VAT", "rateType": "Standard", "taxTreatment": "domesticStandardRate" } ] } ] } ] } ``` ## Response `200` Success Under `data` (importInvoicesOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `ids` | array | yes | The ids of the invoices recorded, one per requested entry and in the same order. Read each back with GET accounting/invoices/:id. Example: `["f3b9c1d4-2a68-4e07-8c15-9d0b7e5a3f62"]`. | ```json { "data": { "ids": [ "f3b9c1d4-2a68-4e07-8c15-9d0b7e5a3f62" ] }, "diagnostics": [ { "code": "INVOICES_IMPORTED", "message": "", "severity": "error", "path": [ "" ], "details": { "count": 0 } } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `SALES_ORDER_NOT_FOUND` | 400 | | `SALES_ORDER_NOT_EXTERNALISED` | 400 | | `SALES_ORDER_NOT_APPROVED` | 400 | | `NO_INVOICES_REQUESTED` | 400 | | `NO_LINES` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Advisories Warnings and information a successful response can carry under `diagnostics`: - `INVOICES_IMPORTED` ## Statuses - `400` — Invalid request or business failure. Codes: SALES_ORDER_NOT_FOUND, SALES_ORDER_NOT_EXTERNALISED, SALES_ORDER_NOT_APPROVED, NO_INVOICES_REQUESTED, NO_LINES - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search invoices Operation ID: `searchInvoice` Group: Accounting › Invoices [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchInvoice) `POST /accounting/invoices/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches invoices. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchInvoiceInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. An invoice must match every filter you send. Omit to return all invoices. | | `filters.id` | string | no | Limits results to the invoice with this id. | | `filters.invoiceNumber` | string | no | Limits results to the invoice with this invoice number, for example 'INV-1'. The match is exact. | | `filters.customerId` | string | no | Limits results to invoices issued to this customer, given as the customer id. An id that matches no customer matches no invoice. | | `filters.customerName` | string | no | Limits results to invoices whose customer name contains this text. The match is case-insensitive. | | `filters.customerNumber` | string | no | Limits results to invoices issued to the customer with this customer number. The match is exact. | | `filters.entityId` | string | no | Limits results to invoices issued by this legal entity, given as the entity id. An id that matches no entity matches no invoice. | | `filters.entityName` | string | no | Limits results to invoices whose issuing legal entity's name contains this text. The match is case-insensitive. | | `filters.salesOrderId` | string | no | Limits results to invoices billed from this sales order, given as the sales order id. An id that matches no sales order matches no invoice. | | `filters.salesOrderNumber` | string | no | Limits results to invoices billed from the sales order with this number. The match is exact. | | `filters.invoiceStatus` | array | no | Limits results to invoices in any of these settlement statuses. Accepted values: 'unpaid', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'isOverdue', 'disputed'. Any other value matches no invoice. An empty array does not constrain. | | `filters.approvalStatus` | array | no | Limits results to invoices in any of these approval statuses. Accepted values: 'draft', 'pendingApproval', 'approved', 'revise', 'rejected'. Any other value matches no invoice. An empty array does not constrain. | | `filters.deliveryStatus` | array | no | Limits results to invoices in any of these delivery statuses. Accepted values: 'created', 'toBeSent', 'delivered', 'read', 'failed'. Any other value matches no invoice. An empty array does not constrain. | | `filters.disputeStatus` | array | no | Limits results to invoices in any of these dispute statuses. Accepted values: 'disputed', 'chargedBack'. Any other value matches no invoice. An empty array does not constrain. | | `filters.customerMessage` | string | no | Limits results to invoices whose customer message contains this text. The match is case-insensitive. | | `filters.billToAddress` | string | no | Limits results to invoices whose bill-to address contains this text across line1, line2, city, stateProvince and zipCode. The match is case-insensitive. Country is excluded, because it is an ISO code and a short term would match every address in the country. | | `filters.soldToAddress` | string | no | Limits results to invoices whose sold-to address contains this text across line1, line2, city, stateProvince and zipCode. The match is case-insensitive. Country is excluded, as with billToAddress. | | `filters.invoiceEmail` | string | no | Limits results to invoices with this billing email. The match is exact and case-sensitive. | | `filters.purchaseOrderOverride` | string | no | Limits results to invoices with this PO override. The match is exact. | | `filters.externalInvoiceId` | string | no | Limits results to invoices with this identifier in an external source system. The match is exact. | | `filters.invoiceDate` | object | no | Limits results to invoices whose invoiceDate falls within these bounds. Omit either bound to leave that side open. | | `filters.invoiceDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.invoiceDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.dueDate` | object | no | Limits results to invoices whose dueDate falls within these bounds. Omit either bound to leave that side open. An aging report is this filter with an open lower bound. | | `filters.dueDate.start` | date | no | The inclusive lower bound. Format YYYY-MM-DD. | | `filters.dueDate.end` | date | no | The inclusive upper bound. Format YYYY-MM-DD. | | `filters.lastModifiedDate` | object | no | Limits results to invoices changed within this window, for polling an incremental sync. Omit either bound to leave that side open. | | `filters.lastModifiedDate.start` | string | no | The inclusive lower bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.lastModifiedDate.end` | string | no | The inclusive upper bound, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `filters.externalIdentifier` | object | no | Matches one identifier owned by another system. Both members are required, because a fieldName on its own would match every value stored under that key. Several invoices may share one value, so this returns the items array, not a single record. | | `filters.externalIdentifier.fieldName` | string | yes | The configured key of the external identifier, a stable camelCase slug such as 'netsuiteInvoiceId', not the human-readable label. It must match an active external-identifier configuration for invoices, or the request fails with UNKNOWN_EXTERNAL_IDENTIFIER. | | `filters.externalIdentifier.value` | string | yes | The identifier as the source system stores it. The match is exact and case-sensitive. | | `page` | object | no | Pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit for the first request. | | `page.size` | number | no | The number of invoices per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "salesOrderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesOrderNumber": "", "invoiceStatus": [ "" ], "approvalStatus": [ "" ], "deliveryStatus": [ "" ], "disputeStatus": [ "" ], "customerMessage": "", "billToAddress": "", "soldToAddress": "", "invoiceEmail": "", "purchaseOrderOverride": "", "externalInvoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceDate": { "start": "2026-01-15", "end": "2026-01-15" }, "dueDate": { "start": "2026-01-15", "end": "2026-01-15" }, "lastModifiedDate": { "start": "", "end": "" }, "externalIdentifier": { "fieldName": "", "value": "" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchInvoiceOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The invoices on this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the invoice. | | `items[].invoiceNumber` | string | no | The invoice number, for example 'INV-1', unique within the tenant. Absent while none has been assigned. | | `items[].invoiceDate` | date | yes | The date the invoice was issued. Format YYYY-MM-DD. Payment terms and the general ledger impact are both based on this date. | | `items[].dueDate` | date | no | The date payment falls due, the invoice date plus the payment term. Format YYYY-MM-DD. Absent when no term has been resolved. | | `items[].lastPaidDate` | date | no | The date the most recent payment was applied. Format YYYY-MM-DD. Absent on an invoice that has never been paid against. | | `items[].invoiceStatus` | string | no | The settlement status of the invoice. Possible values: 'unpaid', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'isOverdue', 'disputed'. | | `items[].approvalStatus` | string | no | The finance approval status of the invoice. Possible values: 'draft', 'pendingApproval', 'approved', 'revise', 'rejected'. | | `items[].deliveryStatus` | string | no | How far the invoice has got in being sent to the customer. Possible values: 'created', 'toBeSent', 'delivered', 'read', 'failed'. | | `items[].disputeStatus` | string | no | The dispute status of the invoice, set only when payments against it are under dispute. Possible values: 'disputed', 'chargedBack'. | | `items[].dataSource` | string | no | The system the invoice record was imported from, for invoices that arrived through an integration or migration rather than being created in this system. For example 'NetSuite', 'Quickbooks', 'Stripe' or 'MigrationFile'. Absent on invoices created in this system. | | `items[].currency` | string | yes | The currency of the invoice. Format ISO 4217, for example 'USD'. It applies to every amount on the record, on the header and on every line. | | `items[].customerMessage` | string | no | A free-form message printed on the invoice for the customer. | | `items[].externalInvoiceId` | string | no | The invoice's identifier in an external source system, kept for reconciliation. For identifiers owned by your other systems, prefer externalIdentifiers below. | | `items[].customerId` | string | yes | The customer the invoice is issued to. See id on GET accounting/customers/:id. | | `items[].customerName` | string | no | The display name of the customer, as it appears on the document. | | `items[].customerNumber` | string | no | The customer number of the customer, unique within the tenant. | | `items[].entityId` | string | yes | The legal entity issuing the invoice. See id on GET base/entities. | | `items[].entityName` | string | no | The name of the issuing legal entity. | | `items[].salesOrderId` | string | no | The sales order the invoice was billed from. Absent on invoices created directly. See id on GET accounting/sales-orders/:id. | | `items[].salesOrderNumber` | string | no | The number of the source sales order. Present whenever salesOrderId is. | | `items[].billToAddress` | object | no | The bill-to address the invoice is sent to. Absent when the invoice references no bill-to record. | | `items[].billToAddress.addressId` | string | no | The unique identifier for the address record. Absent when none is set. | | `items[].billToAddress.line1` | string | yes | The first address line: street number and name, or PO box. | | `items[].billToAddress.line2` | string | no | The second address line: apartment, suite, unit, building or floor. | | `items[].billToAddress.city` | string | yes | The city or locality. | | `items[].billToAddress.stateProvince` | string | no | The state, province or region. | | `items[].billToAddress.country` | string | yes | The country, as a name or ISO 3166-1 code. | | `items[].billToAddress.zipCode` | string | yes | The postal or ZIP code. | | `items[].soldToAddress` | object | no | The sold-to address the product or service was delivered to, used for tax determination. Absent when the invoice references no sold-to record. | | `items[].soldToAddress.addressId` | string | no | The unique identifier for the address record. Absent when none is set. | | `items[].soldToAddress.line1` | string | yes | The first address line: street number and name, or PO box. | | `items[].soldToAddress.line2` | string | no | The second address line: apartment, suite, unit, building or floor. | | `items[].soldToAddress.city` | string | yes | The city or locality. | | `items[].soldToAddress.stateProvince` | string | no | The state, province or region. | | `items[].soldToAddress.country` | string | yes | The country, as a name or ISO 3166-1 code. | | `items[].soldToAddress.zipCode` | string | yes | The postal or ZIP code. | | `items[].invoiceEmail` | string | no | The email address of the business contact that receives this invoice. | | `items[].purchaseOrders` | array | no | The purchase order references the customer supplied, derived from the sales order. These are the customer's own document numbers. An empty array when none were supplied. | | `items[].purchaseOrderOverride` | string | no | A manually entered PO number that overrides the sales-order-derived values in purchaseOrders. | | `items[].paymentTermDays` | number | no | The number of days after the invoice date that payment falls due, resolved from the invoice's payment term. The term record itself is not published, only its day count. | | `items[].exchangeRate` | decimal | no | The rate used to convert the invoice's currency to the entity's functional currency. Absent when no conversion applies. | | `items[].excludeFromTaxEngine` | boolean | no | Whether the invoice is left out of the tax engine. Derived from the sales order. | | `items[].subtotal` | decimal | no | The amount before taxes and discounts, in the invoice currency. Absent when no subtotal has been computed. | | `items[].totalTax` | decimal | no | The total tax across all lines, in the invoice currency. | | `items[].totalDutiesAndCustoms` | decimal | no | The total duties and customs across all lines, in the invoice currency. | | `items[].totalAmount` | decimal | no | The total invoiced, including taxes and duties, in the invoice currency. | | `items[].totalAmountDue` | decimal | no | The amount that was due when the invoice was created, in the invoice currency. | | `items[].totalCredited` | decimal | no | The total credited back against this invoice by credit memos, in the invoice currency. | | `items[].totalLateFeeAmount` | decimal | no | The total late fees charged on this invoice, in the invoice currency. | | `items[].lineDiscounts` | decimal | no | The sum of discount amounts across all invoice lines, in the invoice currency. | | `items[].oneTimeDiscount` | decimal | no | The one-time discount applied to the invoice as a whole, in the invoice currency. | | `items[].paymentsApplied` | decimal | no | The amount settled by payments so far, in the invoice currency. | | `items[].adjustments` | decimal | no | The sum of small-balance write-offs and early-payment discounts recorded against this invoice, in the invoice currency. | | `items[].lastModifiedDate` | date-time | yes | The date and time the invoice record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].lines` | array | no | The lines of the invoice, each billing one product or free-text item. An empty array when the invoice has no lines. All line amounts are in the invoice currency. | | `items[].lines[].id` | string | yes | The unique identifier for the invoice line. | | `items[].lines[].invoiceLineNumber` | string | no | The sequential line number within the invoice. | | `items[].lines[].description` | string | no | The line description, as shown to the customer. | | `items[].lines[].lineType` | string | no | The kind of charge the line is. Possible values: 'recurring', 'oneTime', 'oneTimeDiscount', 'usageOverage', 'overage', 'payAsYouGo', 'commit', 'lateFee', 'creditCardProcessingFee', 'billableTime', 'billablePassThrough', 'billableTravelExpense'. | | `items[].lines[].productId` | string | no | The billed product. Set only on lines created from a product reference, absent on free-text lines. See id on GET accounting/products/:id. | | `items[].lines[].productCode` | string | no | The product code as it stood when the line was created. A snapshot, so it does not follow later changes to the product. | | `items[].lines[].productDescription` | string | no | The product description as it stood when the line was created. A snapshot, as with productCode. | | `items[].lines[].quantity` | decimal | no | The quantity billed. A count, not an amount, so it has no currency. | | `items[].lines[].unitOfMeasure` | string | no | The unit the quantity is measured in, for example 'each', 'hour' or 'gigabyte'. | | `items[].lines[].unitPrice` | decimal | no | The price per unit actually charged, in the invoice currency. | | `items[].lines[].listPrice` | decimal | no | The undiscounted list price per unit, in the invoice currency. | | `items[].lines[].totalPrice` | decimal | no | The line total excluding tax and duties, in the invoice currency. | | `items[].lines[].discountAmount` | decimal | no | The discount granted on the line, in the invoice currency. | | `items[].lines[].discountPercentage` | decimal | no | The discount granted on the line as a percentage. A rate, not an amount, so it has no currency. | | `items[].lines[].taxAmount` | decimal | no | The tax on the line, in the invoice currency. | | `items[].lines[].dutiesAndCustomsAmount` | decimal | no | The duties and customs charged on the line, in the invoice currency. | | `items[].lines[].amount` | decimal | no | The total billed for the line, including tax and duties, in the invoice currency. | | `items[].lines[].totalAmountDue` | decimal | no | The amount of the line still due, in the invoice currency. | | `items[].lines[].taxCode` | string | no | The tax code applied to the line. | | `items[].lines[].taxDate` | date | no | The date the line's tax was determined on. Format YYYY-MM-DD. | | `items[].lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `items[].lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | | `items[].externalIdentifiers` | array | no | External identifiers owned by your other systems. At most one entry per fieldName. An empty array when the invoice has none. | | `items[].externalIdentifiers[].fieldName` | string | yes | The configured key of the external identifier, a stable camelCase slug such as 'netsuiteInvoiceId', not the human-readable label. It must match an active external-identifier configuration for invoices. | | `items[].externalIdentifiers[].value` | string | yes | The identifier as the source system stores it. | | `items[].externalIdentifiers[].externalUrl` | string | no | An absolute http or https link to the object in the originating system. Absent when none is set. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "invoiceDate": "2026-01-15", "dueDate": "2026-01-15", "lastPaidDate": "2026-01-15", "invoiceStatus": "", "approvalStatus": "", "deliveryStatus": "", "disputeStatus": "", "dataSource": "", "currency": "", "customerMessage": "", "externalInvoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "salesOrderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesOrderNumber": "", "billToAddress": { "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" }, "soldToAddress": { "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" }, "invoiceEmail": "", "purchaseOrders": [ "" ], "purchaseOrderOverride": "", "paymentTermDays": 0, "exchangeRate": "19.99", "excludeFromTaxEngine": true, "subtotal": "19.99", "totalTax": "19.99", "totalDutiesAndCustoms": "19.99", "totalAmount": "19.99", "totalAmountDue": "19.99", "totalCredited": "19.99", "totalLateFeeAmount": "19.99", "lineDiscounts": "19.99", "oneTimeDiscount": "19.99", "paymentsApplied": "19.99", "adjustments": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceLineNumber": "", "description": "", "lineType": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "productCode": "", "productDescription": "", "quantity": "19.99", "unitOfMeasure": "", "unitPrice": "19.99", "listPrice": "19.99", "totalPrice": "19.99", "discountAmount": "19.99", "discountPercentage": "19.99", "taxAmount": "19.99", "dutiesAndCustomsAmount": "19.99", "amount": "19.99", "totalAmountDue": "19.99", "taxCode": "", "taxDate": "2026-01-15", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ], "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `UNKNOWN_EXTERNAL_IDENTIFIER` | 400 | | `INVALID_TIMESTAMP` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: UNKNOWN_EXTERNAL_IDENTIFIER, INVALID_TIMESTAMP - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an invoice Operation ID: `getInvoice` Group: Accounting › Invoices [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getInvoice) `GET /accounting/invoices/{id}` Read-only: Reads and returns; changes nothing. Retrieves an invoice. Use the invoice's id, not its invoiceNumber. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the invoice you want to retrieve. | ## Response `200` Success Under `data` (getInvoiceOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the invoice. | | `invoiceNumber` | string | no | The invoice number, for example 'INV-1', unique within the tenant. Absent while none has been assigned. | | `invoiceDate` | date | yes | The date the invoice was issued. Format YYYY-MM-DD. Payment terms and the general ledger impact are both based on this date. | | `dueDate` | date | no | The date payment falls due, the invoice date plus the payment term. Format YYYY-MM-DD. Absent when no term has been resolved. | | `lastPaidDate` | date | no | The date the most recent payment was applied. Format YYYY-MM-DD. Absent on an invoice that has never been paid against. | | `invoiceStatus` | string | no | The settlement status of the invoice. Possible values: 'unpaid', 'partiallyPaid', 'paidInFull', 'creditedInFull', 'isOverdue', 'disputed'. | | `approvalStatus` | string | no | The finance approval status of the invoice. Possible values: 'draft', 'pendingApproval', 'approved', 'revise', 'rejected'. | | `deliveryStatus` | string | no | How far the invoice has got in being sent to the customer. Possible values: 'created', 'toBeSent', 'delivered', 'read', 'failed'. | | `disputeStatus` | string | no | The dispute status of the invoice, set only when payments against it are under dispute. Possible values: 'disputed', 'chargedBack'. | | `dataSource` | string | no | The system the invoice record was imported from, for invoices that arrived through an integration or migration rather than being created in this system. For example 'NetSuite', 'Quickbooks', 'Stripe' or 'MigrationFile'. Absent on invoices created in this system. | | `currency` | string | yes | The currency of the invoice. Format ISO 4217, for example 'USD'. It applies to every amount on the record, on the header and on every line. | | `customerMessage` | string | no | A free-form message printed on the invoice for the customer. | | `externalInvoiceId` | string | no | The invoice's identifier in an external source system, kept for reconciliation. For identifiers owned by your other systems, prefer externalIdentifiers below. | | `customerId` | string | yes | The customer the invoice is issued to. See id on GET accounting/customers/:id. | | `customerName` | string | no | The display name of the customer, as it appears on the document. | | `customerNumber` | string | no | The customer number of the customer, unique within the tenant. | | `entityId` | string | yes | The legal entity issuing the invoice. See id on GET base/entities. | | `entityName` | string | no | The name of the issuing legal entity. | | `salesOrderId` | string | no | The sales order the invoice was billed from. Absent on invoices created directly. See id on GET accounting/sales-orders/:id. | | `salesOrderNumber` | string | no | The number of the source sales order. Present whenever salesOrderId is. | | `billToAddress` | object | no | The bill-to address the invoice is sent to. Absent when the invoice references no bill-to record. | | `billToAddress.addressId` | string | no | The unique identifier for the address record. Absent when none is set. | | `billToAddress.line1` | string | yes | The first address line: street number and name, or PO box. | | `billToAddress.line2` | string | no | The second address line: apartment, suite, unit, building or floor. | | `billToAddress.city` | string | yes | The city or locality. | | `billToAddress.stateProvince` | string | no | The state, province or region. | | `billToAddress.country` | string | yes | The country, as a name or ISO 3166-1 code. | | `billToAddress.zipCode` | string | yes | The postal or ZIP code. | | `soldToAddress` | object | no | The sold-to address the product or service was delivered to, used for tax determination. Absent when the invoice references no sold-to record. | | `soldToAddress.addressId` | string | no | The unique identifier for the address record. Absent when none is set. | | `soldToAddress.line1` | string | yes | The first address line: street number and name, or PO box. | | `soldToAddress.line2` | string | no | The second address line: apartment, suite, unit, building or floor. | | `soldToAddress.city` | string | yes | The city or locality. | | `soldToAddress.stateProvince` | string | no | The state, province or region. | | `soldToAddress.country` | string | yes | The country, as a name or ISO 3166-1 code. | | `soldToAddress.zipCode` | string | yes | The postal or ZIP code. | | `invoiceEmail` | string | no | The email address of the business contact that receives this invoice. | | `purchaseOrders` | array | no | The purchase order references the customer supplied, derived from the sales order. These are the customer's own document numbers. An empty array when none were supplied. | | `purchaseOrderOverride` | string | no | A manually entered PO number that overrides the sales-order-derived values in purchaseOrders. | | `paymentTermDays` | number | no | The number of days after the invoice date that payment falls due, resolved from the invoice's payment term. The term record itself is not published, only its day count. | | `exchangeRate` | decimal | no | The rate used to convert the invoice's currency to the entity's functional currency. Absent when no conversion applies. | | `excludeFromTaxEngine` | boolean | no | Whether the invoice is left out of the tax engine. Derived from the sales order. | | `subtotal` | decimal | no | The amount before taxes and discounts, in the invoice currency. Absent when no subtotal has been computed. | | `totalTax` | decimal | no | The total tax across all lines, in the invoice currency. | | `totalDutiesAndCustoms` | decimal | no | The total duties and customs across all lines, in the invoice currency. | | `totalAmount` | decimal | no | The total invoiced, including taxes and duties, in the invoice currency. | | `totalAmountDue` | decimal | no | The amount that was due when the invoice was created, in the invoice currency. | | `totalCredited` | decimal | no | The total credited back against this invoice by credit memos, in the invoice currency. | | `totalLateFeeAmount` | decimal | no | The total late fees charged on this invoice, in the invoice currency. | | `lineDiscounts` | decimal | no | The sum of discount amounts across all invoice lines, in the invoice currency. | | `oneTimeDiscount` | decimal | no | The one-time discount applied to the invoice as a whole, in the invoice currency. | | `paymentsApplied` | decimal | no | The amount settled by payments so far, in the invoice currency. | | `adjustments` | decimal | no | The sum of small-balance write-offs and early-payment discounts recorded against this invoice, in the invoice currency. | | `lastModifiedDate` | date-time | yes | The date and time the invoice record last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `lines` | array | no | The lines of the invoice, each billing one product or free-text item. An empty array when the invoice has no lines. All line amounts are in the invoice currency. | | `lines[].id` | string | yes | The unique identifier for the invoice line. | | `lines[].invoiceLineNumber` | string | no | The sequential line number within the invoice. | | `lines[].description` | string | no | The line description, as shown to the customer. | | `lines[].lineType` | string | no | The kind of charge the line is. Possible values: 'recurring', 'oneTime', 'oneTimeDiscount', 'usageOverage', 'overage', 'payAsYouGo', 'commit', 'lateFee', 'creditCardProcessingFee', 'billableTime', 'billablePassThrough', 'billableTravelExpense'. | | `lines[].productId` | string | no | The billed product. Set only on lines created from a product reference, absent on free-text lines. See id on GET accounting/products/:id. | | `lines[].productCode` | string | no | The product code as it stood when the line was created. A snapshot, so it does not follow later changes to the product. | | `lines[].productDescription` | string | no | The product description as it stood when the line was created. A snapshot, as with productCode. | | `lines[].quantity` | decimal | no | The quantity billed. A count, not an amount, so it has no currency. | | `lines[].unitOfMeasure` | string | no | The unit the quantity is measured in, for example 'each', 'hour' or 'gigabyte'. | | `lines[].unitPrice` | decimal | no | The price per unit actually charged, in the invoice currency. | | `lines[].listPrice` | decimal | no | The undiscounted list price per unit, in the invoice currency. | | `lines[].totalPrice` | decimal | no | The line total excluding tax and duties, in the invoice currency. | | `lines[].discountAmount` | decimal | no | The discount granted on the line, in the invoice currency. | | `lines[].discountPercentage` | decimal | no | The discount granted on the line as a percentage. A rate, not an amount, so it has no currency. | | `lines[].taxAmount` | decimal | no | The tax on the line, in the invoice currency. | | `lines[].dutiesAndCustomsAmount` | decimal | no | The duties and customs charged on the line, in the invoice currency. | | `lines[].amount` | decimal | no | The total billed for the line, including tax and duties, in the invoice currency. | | `lines[].totalAmountDue` | decimal | no | The amount of the line still due, in the invoice currency. | | `lines[].taxCode` | string | no | The tax code applied to the line. | | `lines[].taxDate` | date | no | The date the line's tax was determined on. Format YYYY-MM-DD. | | `lines[].serviceStartDate` | date | no | The first day of the service period the line covers. Format YYYY-MM-DD. | | `lines[].serviceEndDate` | date | no | The last day of the service period the line covers, inclusive. Format YYYY-MM-DD. | | `externalIdentifiers` | array | no | External identifiers owned by your other systems. At most one entry per fieldName. An empty array when the invoice has none. | | `externalIdentifiers[].fieldName` | string | yes | The configured key of the external identifier, a stable camelCase slug such as 'netsuiteInvoiceId', not the human-readable label. It must match an active external-identifier configuration for invoices. | | `externalIdentifiers[].value` | string | yes | The identifier as the source system stores it. | | `externalIdentifiers[].externalUrl` | string | no | An absolute http or https link to the object in the originating system. Absent when none is set. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceNumber": "", "invoiceDate": "2026-01-15", "dueDate": "2026-01-15", "lastPaidDate": "2026-01-15", "invoiceStatus": "", "approvalStatus": "", "deliveryStatus": "", "disputeStatus": "", "dataSource": "", "currency": "", "customerMessage": "", "externalInvoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customerName": "", "customerNumber": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "salesOrderId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "salesOrderNumber": "", "billToAddress": { "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" }, "soldToAddress": { "addressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "line1": "", "line2": "", "city": "", "stateProvince": "", "country": "", "zipCode": "" }, "invoiceEmail": "", "purchaseOrders": [ "" ], "purchaseOrderOverride": "", "paymentTermDays": 0, "exchangeRate": "19.99", "excludeFromTaxEngine": true, "subtotal": "19.99", "totalTax": "19.99", "totalDutiesAndCustoms": "19.99", "totalAmount": "19.99", "totalAmountDue": "19.99", "totalCredited": "19.99", "totalLateFeeAmount": "19.99", "lineDiscounts": "19.99", "oneTimeDiscount": "19.99", "paymentsApplied": "19.99", "adjustments": "19.99", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "invoiceLineNumber": "", "description": "", "lineType": "", "productId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "productCode": "", "productDescription": "", "quantity": "19.99", "unitOfMeasure": "", "unitPrice": "19.99", "listPrice": "19.99", "totalPrice": "19.99", "discountAmount": "19.99", "discountPercentage": "19.99", "taxAmount": "19.99", "dutiesAndCustomsAmount": "19.99", "amount": "19.99", "totalAmountDue": "19.99", "taxCode": "", "taxDate": "2026-01-15", "serviceStartDate": "2026-01-15", "serviceEndDate": "2026-01-15" } ], "externalIdentifiers": [ { "fieldName": "", "value": "", "externalUrl": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update an invoice Operation ID: `updateInvoice` Group: Accounting › Invoices [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateInvoice) `PATCH /accounting/invoices/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates an invoice with the fields you send. A field you leave out keeps its current value. Use the invoice's id, not its invoiceNumber. You can change invoiceDate, paymentTerm, customerMessage, and purchaseOrderOverride on the invoice, and description and dutiesAndCustomsAmount on each line. Amounts, tax, the set of lines, deliveryStatus, the addresses and invoiceEmail can't be changed. To clear purchaseOrderOverride, send an empty string, and the sales order's PO references apply again. An empty paymentTerm, customerMessage, or invoiceDate is ignored, so customerMessage can't be cleared. Each item in updateLines changes one existing line of this invoice, identified by its id. Lines you leave out don't change, and you can't add or remove lines. The response contains only the invoice's id. To see the recalculated totals, use GET accounting/invoices/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the invoice you want to update. | ## Request body Required, `application/json`: updateInvoiceInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `invoiceDate` | date | no | The date the invoice was issued. Format YYYY-MM-DD. Omit to leave unchanged. Changing it re-derives the due date from the payment term and moves which tax period the invoice falls in. Example: `"2026-09-05"`. | | `paymentTerm` | string | no | The payment term governing when payment falls due, as the term's configured code. Omit to leave unchanged. An empty string is ignored rather than clearing it. This field is write-only: the read endpoints publish the resolved paymentTermDays instead of the term itself. | | `customerMessage` | string | no | A free-form message printed on the invoice for the customer. Omit to leave unchanged. It cannot be cleared through this endpoint. An empty string is ignored, not stored. Example: `"Thank you for your business."`. | | `purchaseOrderOverride` | string | no | A manually entered PO number that overrides the references derived from the sales order. Omit to leave unchanged. Unlike the other fields here, pass an empty string to clear it and fall back to the sales order's own PO references. | | `updateLines` | array | no | The line edits to apply, each addressing one existing line of this invoice by its id. Omit to leave the lines untouched. Lines cannot be added or removed here, because the set of lines comes from the sales order's invoice plan. Example: `[{"id":"5e2a7c81-9d34-4b06-8f19-2c7e4a0d3b58","description":"Platform subscription — September 2026","dutiesAndCustomsAmount":"45.00"}]`. | | `updateLines[].id` | string | yes | The unique identifier for the invoice line to edit, as returned by GET accounting/invoices/:id. It must be a line of this invoice, and may appear at most once in the request. | | `updateLines[].description` | string | no | The new line description, as shown to the customer. Omit to leave unchanged. | | `updateLines[].dutiesAndCustomsAmount` | decimal | no | The duties and customs charged on this line, in the invoice's currency. Requires the entity's Duties and Customs feature to be enabled, and setting it recalculates the invoice's journal entry. Omit to leave unchanged. | ```json { "invoiceDate": "2026-09-05", "paymentTerm": "", "customerMessage": "Thank you for your business.", "purchaseOrderOverride": "", "updateLines": [ { "id": "5e2a7c81-9d34-4b06-8f19-2c7e4a0d3b58", "description": "Platform subscription — September 2026", "dutiesAndCustomsAmount": "45.00" } ] } ``` ## Response `200` Success Under `data` (updateInvoiceOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the updated invoice. Read the invoice back with GET accounting/invoices/:id to see the recalculated totals. Example: `"f3b9c1d4-2a68-4e07-8c15-9d0b7e5a3f62"`. | ```json { "data": { "id": "f3b9c1d4-2a68-4e07-8c15-9d0b7e5a3f62" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `INVOICE_NOT_FOUND` | 400 | | `LINE_NOT_ON_INVOICE` | 400 | | `DUPLICATE_LINE` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: INVOICE_NOT_FOUND, LINE_NOT_ON_INVOICE, DUPLICATE_LINE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Record a payment against an invoice Operation ID: `payInvoice` Group: Accounting › Invoices [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/payInvoice) `POST /accounting/invoices/{id}/pay` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Records a payment against an invoice. The payment is created for the invoice's customer and legal entity, using the entity's default electronic payment account. The payment is applied to the invoice and creates a journal entry in the general ledger. The response contains the new payment's id. You can pay part of the amount due. For installments, send one request for each payment. Send an Idempotency-Key header. If you retry with the same key and body, you get the original response and no second payment is created. Without the header, retrying a request that succeeded creates a second payment. To see the reduced totalAmountDue and the increased paymentsApplied, use GET accounting/invoices/:id. To read the payment, use GET accounting/payments/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the invoice you want to settle. | ## Request body Required, `application/json`: payInvoiceInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `amount` | decimal | yes | The amount paid, in the invoice's currency. Must be greater than zero and may not exceed the amount still due. Example: `"1210.00"`. | | `paymentDate` | date | yes | The date the payment was received or settled. Format YYYY-MM-DD. It drives the ledger posting and the reporting period. Example: `"2026-09-15"`. | | `paymentType` | any | yes | How the payment was tendered. One of: `"check"`, `"ach"`, `"wire"`, `"creditCard"`, `"directDebit"`, `"sepaCreditTransfer"`. Example: `"wire"`. | | `paymentNote` | string | no | A free-text note recorded on the payment, for example a bank reference or check number. Example: `"Remittance ref 88213"`. | | `bankFeeAmount` | decimal | no | The fee the bank or processor deducted, in the invoice's currency. Recorded on the payment rather than subtracted from it. Send the gross amount in amount. Example: `"12.50"`. | ```json { "amount": "1210.00", "paymentDate": "2026-09-15", "paymentType": "wire", "paymentNote": "Remittance ref 88213", "bankFeeAmount": "12.50" } ``` ## Response `200` Success Under `data` (payInvoiceOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `paymentId` | string | yes | The unique identifier for the payment created. Read it back with GET accounting/payments/:id, or re-read the invoice for its updated balance. Example: `"7d41e9b2-58c6-4a03-9e17-2b8f0c5d6a39"`. | ```json { "data": { "paymentId": "7d41e9b2-58c6-4a03-9e17-2b8f0c5d6a39" }, "diagnostics": [ { "code": "PAYMENT_RECORDED", "message": "", "severity": "error", "path": [ "" ], "details": { "paymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `INVOICE_NOT_FOUND` | 400 | | `INVALID_AMOUNT` | 400 | | `AMOUNT_EXCEEDS_DUE` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Advisories Warnings and information a successful response can carry under `diagnostics`: - `PAYMENT_RECORDED` ## Statuses - `400` — Invalid request or business failure. Codes: INVOICE_NOT_FOUND, INVALID_AMOUNT, AMOUNT_EXCEEDS_DUE - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Journal Entries Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingJournalEntries) A journal entry is a set of debit and credit lines for the general ledger. ## Endpoints - [List journal entries](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/journalEntries.html): `GET /accounting/journal-entries` - [Create a journal entry](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createJournalEntry.html): `POST /accounting/journal-entries` - [Search journal entries](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchJournalEntries.html): `POST /accounting/journal-entries/search` - [Retrieve a journal entry](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getJournalEntry.html): `GET /accounting/journal-entries/{id}` - [Update a journal entry](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateJournalEntry.html): `PATCH /accounting/journal-entries/{id}` - [Reverse a journal entry](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/reverseJournalEntry.html): `POST /accounting/journal-entries/{id}/reverse` - [Void a journal entry](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/voidJournalEntry.html): `POST /accounting/journal-entries/{id}/void` ## Events - [A journal entry was created.](https://docs.api.lensing.ai/reference/lensing/v1/events/accounting_journalEntries_created.html) - [A journal entry was deleted.](https://docs.api.lensing.ai/reference/lensing/v1/events/accounting_journalEntries_deleted.html) - [A journal entry changed.](https://docs.api.lensing.ai/reference/lensing/v1/events/accounting_journalEntries_updated.html) --- # List journal entries Operation ID: `journalEntries` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/journalEntries) `GET /accounting/journal-entries` Read-only: Reads and returns; changes nothing. Lists journal entries. Each journal entry in the list has no lines. To read a journal entry with its lines, use GET accounting/journal-entries/:id or POST accounting/journal-entries/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `type` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `postingDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `bookName` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, status, type, postingDate, bookName, lastModifiedDate - Selectable: id, name, status, type, approvalStatus, postingDate, reversalDate, reversalReason, reversalDescription, description, url, bookName, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of journalEntriesRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the journal entry. | | `name` | string \| null | no | no | no | The display name of the journal entry. | | `status` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The posting status of the journal entry. Possible values: 'posted', 'unposted', 'voided'. | | `type` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How the entry originated, for example 'manual', 'invoice', 'revaluation' or 'reversal'. A system-assigned value. | | `approvalStatus` | string \| null | no | no | no | The approval status of the journal entry. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Absent on entries that never entered the approval workflow. | | `postingDate` | date | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the entry posts to the general ledger. Format YYYY-MM-DD. | | `reversalDate` | date \| null | no | no | no | The reversal date recorded on the entry. Format YYYY-MM-DD. Set only on a reversed entry or a reversal entry. | | `reversalReason` | string \| null | no | no | no | The reason recorded for the reversal. Possible values: 'accrualReversal', 'revaluationReversal', 'estimate', 'rebook', 'reversingSFD', 'other'. | | `reversalDescription` | string \| null | no | no | no | The description recorded on the reversal. | | `description` | string \| null | no | no | no | A free-form description of the journal entry. | | `url` | string \| null | no | no | no | A link to the source document this entry comes from. | | `bookName` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The name of the accounting book this entry posts to. See name on GET accounting/books/:id. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the journal entry last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "", "status": "", "type": "", "approvalStatus": "", "postingDate": "2026-01-15", "reversalDate": "2026-01-15", "reversalReason": "", "reversalDescription": "", "description": "", "url": "", "bookName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create a journal entry Operation ID: `createJournalEntry` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createJournalEntry) `POST /accounting/journal-entries` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates a manual journal entry. The journal entry is created with its debit and credit lines, unposted and pending approval. The journal entry is then approved at once if no approval policy applies, or if your approval completes the approvals the policy requires. Approval posts the journal entry to the general ledger. Until then, the journal entry stays unposted. The response contains only the new journal entry's id. Read the journal entry with GET accounting/journal-entries/:id. A journal entry an accounting rule rejects, for example one whose debits and credits do not balance, is not created. The request fails with VALIDATION_FAILED, with the rule's message in reason and, where known, the rule's name in rule. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createJournalEntryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | The display name of the journal entry. | | `description` | string | no | A free-form description of the journal entry. | | `postingDate` | date | yes | The date the entry posts to the general ledger. Use format YYYY-MM-DD. Each line takes this as its transaction date. | | `reversalDate` | date | no | The reversal date recorded on the header. Use format YYYY-MM-DD. This does not post a reversal. To post one, use POST accounting/journal-entries/:id/reverse. | | `reversalReason` | string | no | The reason recorded on the header for a later reversal. Accepted values: 'accrualReversal', 'revaluationReversal', 'estimate', 'rebook', 'reversingSFD', 'other'. Any other value fails with INVALID_REVERSAL_REASON. | | `reversalDescription` | string | no | A free-form reversal description recorded on the header. | | `url` | string | no | A link to the source document this entry comes from. | | `bookId` | string | yes | The accounting book this entry posts to. See id on GET accounting/books/:id. | | `deriveToAllBooks` | boolean | no | Whether to replicate the entry to every accounting book. Defaults to false. Has no effect unless multi-book accounting is enabled. | | `lines` | array | yes | The debit and credit lines of the entry. Each line is a debit or a credit in its transactionalCurrency. | | `lines[].accountId` | string | yes | The general ledger account this line posts to. See id on GET accounting/accounts/:id. | | `lines[].entityId` | string | yes | The legal entity this line posts to. See id on GET base/entities. | | `lines[].transactionalCurrency` | string | yes | The currency of this line's debit or credit amount. Format ISO 4217, for example 'USD'. | | `lines[].description` | string | no | A free-form description of the line. | | `lines[].intercompanyPartnerId` | string | no | The intercompany counterparty for this line. Set on intercompany lines. | | `lines[].debit` | decimal | no | The debit amount for this line, in its transactionalCurrency. A line sets either debit or credit, not both. | | `lines[].credit` | decimal | no | The credit amount for this line, in its transactionalCurrency. A line sets either debit or credit, not both. | | `lines[].exchangeRate` | decimal | no | The exchange rate from the line's transactionalCurrency to the entity's functional currency. It applies only when the two currencies differ. For a same-currency line it is 1 and any value you send is ignored. When you omit it on a foreign-currency line, it defaults to the latest rate on or before the posting date. | | `lines[].reportingTags` | array | no | The reporting tags for this line, one per reporting dimension. Each tag pairs a dimensionCode with a modelId. A line has at most one tag per dimensionCode. The referenced record must exist and be active, or the request fails with REFERENCED_RECORD_NOT_FOUND. Omit or send an empty array for an untagged line. | | `lines[].reportingTags[].dimensionCode` | string | yes | The reporting dimension this tag is for. It determines which model modelId points to. Accepted values: 'department', 'businessUnit', 'team', 'location', 'region', 'employee', 'vendor', 'customer', 'valueCenter', 'custom'. | | `lines[].reportingTags[].modelId` | string | yes | The uuid of the dimension record this tag points to, on the model that dimensionCode names. For example, a department record when dimensionCode is 'department'. It must be an existing, active record. For a custom dimension, send the value's modelUuid from listCustomDimensionValues, not its integer modelId. | ```json { "name": "", "description": "", "postingDate": "2026-01-15", "reversalDate": "2026-01-15", "reversalReason": "", "reversalDescription": "", "url": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "deriveToAllBooks": true, "lines": [ { "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "transactionalCurrency": "", "description": "", "intercompanyPartnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "debit": "19.99", "credit": "19.99", "exchangeRate": "19.99", "reportingTags": [ { "dimensionCode": "", "modelId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ] } ] } ``` ## Response `200` Success Under `data` (createJournalEntryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created journal entry. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `INVALID_REVERSAL_REASON` | 400 | | `REFERENCED_RECORD_NOT_FOUND` | 400 | | `VALIDATION_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_REVERSAL_REASON, REFERENCED_RECORD_NOT_FOUND, VALIDATION_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search journal entries Operation ID: `searchJournalEntries` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchJournalEntries) `POST /accounting/journal-entries/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches journal entries. Each journal entry in the response includes its lines. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchJournalEntriesInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. A journal entry must match every filter you send. Omit to return all journal entries. | | `filters.id` | string | no | Limits results to the journal entry with this identifier. | | `filters.bookId` | string | no | Limits results to entries in this accounting book. See id on GET accounting/books/:id. | | `filters.status` | string | no | Limits results to entries with this posting status. Accepted values: 'posted', 'unposted', 'voided'. Any other value matches no entry. | | `filters.type` | string | no | Limits results to entries of this origin type, for example 'manual' or 'invoice'. | | `filters.name` | string | no | Limits results to entries whose name contains this text. The match is case-insensitive. | | `filters.description` | string | no | Limits results to entries whose description contains this text. The match is case-insensitive. | | `filters.postingDate` | object | no | A window on the posting date. Both bounds are inclusive. Omit a bound to leave that side open. | | `filters.postingDate.start` | date | no | The inclusive start of the posting-date window. Use format YYYY-MM-DD. | | `filters.postingDate.end` | date | no | The inclusive end of the posting-date window. Use format YYYY-MM-DD. | | `page` | object | no | The pagination controls. Omit for the first page. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit for the first page. | | `page.size` | number | no | The number of entries per page. Defaults to 50. A value above 200 is treated as 200, not rejected. | ```json { "filters": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "status": "", "type": "", "name": "", "description": "", "postingDate": { "start": "2026-01-15", "end": "2026-01-15" } }, "page": { "cursor": "", "size": 0 } } ``` ## Response `200` Success Under `data` (searchJournalEntriesOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The journal entries that match, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the journal entry. | | `items[].approvalStatus` | string | no | The approval status of the journal entry. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Absent on entries that never entered the approval workflow. | | `items[].bookId` | string | no | The accounting book this entry posts to. See id on GET accounting/books/:id. | | `items[].description` | string | no | A free-form description of the journal entry. | | `items[].name` | string | no | The display name of the journal entry. | | `items[].postingDate` | date | yes | The date the entry posts to the general ledger. Format YYYY-MM-DD. | | `items[].reversalDate` | date | no | The reversal date recorded on the entry. Format YYYY-MM-DD. Set only on a reversed entry or a reversal entry. | | `items[].reversalDescription` | string | no | The description recorded on the reversal. | | `items[].reversalReason` | string | no | The reason recorded for the reversal. Possible values: 'accrualReversal', 'revaluationReversal', 'estimate', 'rebook', 'reversingSFD', 'other'. | | `items[].status` | string | yes | The posting status of the journal entry. Possible values: 'posted', 'unposted', 'voided'. | | `items[].type` | string | no | How the entry originated, for example 'manual', 'invoice', 'revaluation' or 'reversal'. A system-assigned value. | | `items[].url` | string | no | A link to the source document this entry comes from. | | `items[].lastModifiedDate` | date-time | yes | The date and time the journal entry last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].lines` | array | yes | The debit and credit lines of the entry. | | `items[].lines[].id` | string | yes | The unique identifier for the line. | | `items[].lines[].accountId` | string | yes | The general ledger account this line posts to. See id on GET accounting/accounts/:id. | | `items[].lines[].credit` | decimal | no | The credit amount for this line, in its transactionalCurrency. A line has either a debit or a credit, not both. | | `items[].lines[].debit` | decimal | no | The debit amount for this line, in its transactionalCurrency. A line has either a debit or a credit, not both. | | `items[].lines[].description` | string | no | A free-form description of the line. | | `items[].lines[].entityId` | string | yes | The legal entity this line posts to. See id on GET base/entities. | | `items[].lines[].intercompanyPartnerId` | string | no | The intercompany counterparty for this line. Set on intercompany lines. | | `items[].lines[].exchangeRate` | decimal | no | The exchange rate used to convert the line's transactional amount to its functional amount. | | `items[].lines[].functionalCredit` | decimal | no | The credit amount for this line, in the functional currency. | | `items[].lines[].functionalCurrency` | string | no | The functional currency of the posting entity. Format ISO 4217, for example 'USD'. | | `items[].lines[].functionalDebit` | decimal | no | The debit amount for this line, in the functional currency. | | `items[].lines[].journalEntryId` | string | no | The journal entry this line belongs to. See id on GET accounting/journal-entries/:id. | | `items[].lines[].lineNumber` | number | no | The line's sequential number within the entry. | | `items[].lines[].lineType` | string | no | The classification of the line, for example 'standard', 'tax', 'offset' or 'fxRoundingDifference'. A system-assigned value. Absent when the line has no classification. | | `items[].lines[].postingPeriodId` | string | no | The accounting period this line posts to. See id on GET accounting/accounting-periods/:id. | | `items[].lines[].revaluation` | boolean | no | Whether a currency-revaluation process created this line. | | `items[].lines[].sourceTransaction` | string | no | The identifier of the source transaction that generated this line, for example an invoice or payment number. | | `items[].lines[].transactionalCurrency` | string | no | The currency of the line's transactional amounts. Format ISO 4217, for example 'USD'. | | `items[].lines[].transactionDate` | date | no | The date of the underlying transaction. Format YYYY-MM-DD. It can differ from the posting date. | | `items[].lines[].reportingTags` | array | no | The reporting tags assigned to this line. Set them with create or update. | | `items[].lines[].reportingTags[].dimensionCode` | string | yes | The reporting dimension this tag is for. Possible values: 'department', 'businessUnit', 'team', 'location', 'region', 'employee', 'vendor', 'customer', 'valueCenter', 'custom'. | | `items[].lines[].reportingTags[].modelId` | string | yes | The dimension record this tag points to, on the model that dimensionCode names. | | `items[].lines[].reportingTags[].customDimensionId` | string | no | The custom dimension this tag belongs to. Present only for a custom-dimension tag. See id on GET base/custom-dimensions. | | `items[].lines[].reportingTags[].isActive` | boolean | yes | Whether the referenced dimension record is currently active. A historical entry can have tags that point at records since made inactive. | | `pagination` | object | yes | The pagination cursor for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more entries remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "approvalStatus": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "", "name": "", "postingDate": "2026-01-15", "reversalDate": "2026-01-15", "reversalDescription": "", "reversalReason": "", "status": "", "type": "", "url": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "credit": "19.99", "debit": "19.99", "description": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyPartnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "exchangeRate": "19.99", "functionalCredit": "19.99", "functionalCurrency": "", "functionalDebit": "19.99", "journalEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": 0, "lineType": "", "postingPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "revaluation": true, "sourceTransaction": "", "transactionalCurrency": "", "transactionDate": "2026-01-15", "reportingTags": [ null ] } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve a journal entry Operation ID: `getJournalEntry` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getJournalEntry) `GET /accounting/journal-entries/{id}` Read-only: Reads and returns; changes nothing. Retrieves a journal entry. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier for the journal entry to return. | ## Response `200` Success Under `data` (getJournalEntryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the journal entry. | | `approvalStatus` | string | no | The approval status of the journal entry. Possible values: 'draft', 'pendingApproval', 'revise', 'approved', 'rejected'. Absent on entries that never entered the approval workflow. | | `bookId` | string | no | The accounting book this entry posts to. See id on GET accounting/books/:id. | | `description` | string | no | A free-form description of the journal entry. | | `name` | string | no | The display name of the journal entry. | | `postingDate` | date | yes | The date the entry posts to the general ledger. Format YYYY-MM-DD. | | `reversalDate` | date | no | The reversal date recorded on the entry. Format YYYY-MM-DD. Set only on a reversed entry or a reversal entry. | | `reversalDescription` | string | no | The description recorded on the reversal. | | `reversalReason` | string | no | The reason recorded for the reversal. Possible values: 'accrualReversal', 'revaluationReversal', 'estimate', 'rebook', 'reversingSFD', 'other'. | | `status` | string | yes | The posting status of the journal entry. Possible values: 'posted', 'unposted', 'voided'. | | `type` | string | no | How the entry originated, for example 'manual', 'invoice', 'revaluation' or 'reversal'. A system-assigned value. | | `url` | string | no | A link to the source document this entry comes from. | | `lastModifiedDate` | date-time | yes | The date and time the journal entry last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `lines` | array | yes | The debit and credit lines of the entry. | | `lines[].id` | string | yes | The unique identifier for the line. | | `lines[].accountId` | string | yes | The general ledger account this line posts to. See id on GET accounting/accounts/:id. | | `lines[].credit` | decimal | no | The credit amount for this line, in its transactionalCurrency. A line has either a debit or a credit, not both. | | `lines[].debit` | decimal | no | The debit amount for this line, in its transactionalCurrency. A line has either a debit or a credit, not both. | | `lines[].description` | string | no | A free-form description of the line. | | `lines[].entityId` | string | yes | The legal entity this line posts to. See id on GET base/entities. | | `lines[].intercompanyPartnerId` | string | no | The intercompany counterparty for this line. Set on intercompany lines. | | `lines[].exchangeRate` | decimal | no | The exchange rate used to convert the line's transactional amount to its functional amount. | | `lines[].functionalCredit` | decimal | no | The credit amount for this line, in the functional currency. | | `lines[].functionalCurrency` | string | no | The functional currency of the posting entity. Format ISO 4217, for example 'USD'. | | `lines[].functionalDebit` | decimal | no | The debit amount for this line, in the functional currency. | | `lines[].journalEntryId` | string | no | The journal entry this line belongs to. See id on GET accounting/journal-entries/:id. | | `lines[].lineNumber` | number | no | The line's sequential number within the entry. | | `lines[].lineType` | string | no | The classification of the line, for example 'standard', 'tax', 'offset' or 'fxRoundingDifference'. A system-assigned value. Absent when the line has no classification. | | `lines[].postingPeriodId` | string | no | The accounting period this line posts to. See id on GET accounting/accounting-periods/:id. | | `lines[].revaluation` | boolean | no | Whether a currency-revaluation process created this line. | | `lines[].sourceTransaction` | string | no | The identifier of the source transaction that generated this line, for example an invoice or payment number. | | `lines[].transactionalCurrency` | string | no | The currency of the line's transactional amounts. Format ISO 4217, for example 'USD'. | | `lines[].transactionDate` | date | no | The date of the underlying transaction. Format YYYY-MM-DD. It can differ from the posting date. | | `lines[].reportingTags` | array | no | The reporting tags assigned to this line. Set them with create or update. | | `lines[].reportingTags[].dimensionCode` | string | yes | The reporting dimension this tag is for. Possible values: 'department', 'businessUnit', 'team', 'location', 'region', 'employee', 'vendor', 'customer', 'valueCenter', 'custom'. | | `lines[].reportingTags[].modelId` | string | yes | The dimension record this tag points to, on the model that dimensionCode names. | | `lines[].reportingTags[].customDimensionId` | string | no | The custom dimension this tag belongs to. Present only for a custom-dimension tag. See id on GET base/custom-dimensions. | | `lines[].reportingTags[].isActive` | boolean | yes | Whether the referenced dimension record is currently active. A historical entry can have tags that point at records since made inactive. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "approvalStatus": "", "bookId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "", "name": "", "postingDate": "2026-01-15", "reversalDate": "2026-01-15", "reversalDescription": "", "reversalReason": "", "status": "", "type": "", "url": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "credit": "19.99", "debit": "19.99", "description": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyPartnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "exchangeRate": "19.99", "functionalCredit": "19.99", "functionalCurrency": "", "functionalDebit": "19.99", "journalEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": 0, "lineType": "", "postingPeriodId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "revaluation": true, "sourceTransaction": "", "transactionalCurrency": "", "transactionDate": "2026-01-15", "reportingTags": [ { "dimensionCode": "", "modelId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "customDimensionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "isActive": true } ] } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update a journal entry Operation ID: `updateJournalEntry` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateJournalEntry) `PATCH /accounting/journal-entries/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates a journal entry with the fields you send. A field you leave out keeps its current value. Lines change through three lists: createJournalEntryLines adds lines, updateJournalEntryLines changes lines by id, and deleteJournalEntryLines deletes lines. An update sets the journal entry back to unposted and pending approval, including a journal entry that was posted. The journal entry is then approved at once if no approval policy applies, or if your approval completes the approvals the policy requires. Approval posts the journal entry to the general ledger, and no new journal entry is created. Only a manual journal entry can be updated. The request fails if the journal entry is pending approval and has a pending reversal, if it has amortizations that have started, or if its accounting period is closed. A request an accounting rule rejects answers VALIDATION_FAILED, with the rule's message in reason and, where known, the rule's name in rule. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier for the journal entry to update. | ## Request body Required, `application/json`: updateJournalEntryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | The display name of the journal entry. | | `description` | string | no | A free-form description of the journal entry. | | `postingDate` | date | no | The date the entry posts to the general ledger. Use format YYYY-MM-DD. | | `reversalDate` | date | no | The reversal date recorded on the header. Use format YYYY-MM-DD. | | `reversalReason` | string | no | The reason recorded on the header for a later reversal. Accepted values: 'accrualReversal', 'revaluationReversal', 'estimate', 'rebook', 'reversingSFD', 'other'. Any other value fails with INVALID_REVERSAL_REASON. | | `reversalDescription` | string | no | A free-form reversal description recorded on the header. | | `url` | string | no | A link to the source document this entry comes from. | | `createJournalEntryLines` | array | no | The lines to add to the entry. Each is a new line with no id. | | `createJournalEntryLines[].accountId` | string | yes | The general ledger account this line posts to. See id on GET accounting/accounts/:id. | | `createJournalEntryLines[].entityId` | string | yes | The legal entity this line posts to. See id on GET base/entities. | | `createJournalEntryLines[].transactionalCurrency` | string | yes | The currency of this line's amount. Format ISO 4217, for example 'USD'. | | `createJournalEntryLines[].description` | string | no | A free-form description of the line. | | `createJournalEntryLines[].intercompanyPartnerId` | string | no | The intercompany counterparty for this line. Set on intercompany lines. | | `createJournalEntryLines[].debit` | decimal | no | The debit amount for this line, in its transactionalCurrency. A line sets either debit or credit, not both. | | `createJournalEntryLines[].credit` | decimal | no | The credit amount for this line, in its transactionalCurrency. A line sets either debit or credit, not both. | | `createJournalEntryLines[].exchangeRate` | decimal | no | The exchange rate from the line's transactionalCurrency to the entity's functional currency. It applies only when the two currencies differ. For a same-currency line it is 1 and any value you send is ignored. | | `createJournalEntryLines[].reportingTags` | array | no | The reporting tags for this new line, one per reporting dimension. Each tag pairs a dimensionCode with a modelId, and the referenced record must exist and be active. A line has at most one tag per dimensionCode. Omit or send an empty array for an untagged line. | | `createJournalEntryLines[].reportingTags[].dimensionCode` | string | yes | The reporting dimension this tag is for. It determines which model modelId points to. Accepted values: 'department', 'businessUnit', 'team', 'location', 'region', 'employee', 'vendor', 'customer', 'valueCenter', 'custom'. | | `createJournalEntryLines[].reportingTags[].modelId` | string | yes | The uuid of the dimension record this tag points to, on the model that dimensionCode names. For example, a department record when dimensionCode is 'department'. It must be an existing, active record. For a custom dimension, send the value's modelUuid from listCustomDimensionValues, not its integer modelId. | | `updateJournalEntryLines` | array | no | The existing lines to change, each matched by its id. Only the fields you send on a line change. | | `updateJournalEntryLines[].id` | string | yes | The line to change. It must belong to this entry. | | `updateJournalEntryLines[].accountId` | string | no | The general ledger account this line posts to. See id on GET accounting/accounts/:id. | | `updateJournalEntryLines[].entityId` | string | no | The legal entity this line posts to. See id on GET base/entities. | | `updateJournalEntryLines[].intercompanyPartnerId` | string | no | The intercompany counterparty for this line. Set on intercompany lines. | | `updateJournalEntryLines[].description` | string | no | A free-form description of the line. | | `updateJournalEntryLines[].debit` | decimal | no | The debit amount for this line, in its transactionalCurrency. A line sets either debit or credit, not both. | | `updateJournalEntryLines[].credit` | decimal | no | The credit amount for this line, in its transactionalCurrency. A line sets either debit or credit, not both. | | `updateJournalEntryLines[].transactionalCurrency` | string | no | The currency of this line's amount. Format ISO 4217, for example 'USD'. | | `updateJournalEntryLines[].exchangeRate` | decimal | no | The exchange rate from the line's transactionalCurrency to the entity's functional currency. It applies only when the two currencies differ. For a same-currency line it is 1 and any value you send is ignored. | | `updateJournalEntryLines[].reportingTags` | array | no | The reporting tags for the line, one per reporting dimension. Each tag pairs a dimensionCode with a modelId referencing an existing, active record, at most one per dimensionCode. Omit to keep the current tags. Send an empty array to clear them. Send a list to replace them. | | `updateJournalEntryLines[].reportingTags[].dimensionCode` | string | yes | The reporting dimension this tag is for. It determines which model modelId points to. Accepted values: 'department', 'businessUnit', 'team', 'location', 'region', 'employee', 'vendor', 'customer', 'valueCenter', 'custom'. | | `updateJournalEntryLines[].reportingTags[].modelId` | string | yes | The uuid of the dimension record this tag points to, on the model that dimensionCode names. For example, a department record when dimensionCode is 'department'. It must be an existing, active record. For a custom dimension, send the value's modelUuid from listCustomDimensionValues, not its integer modelId. | | `deleteJournalEntryLines` | array | no | The lines to delete from the entry, each by its id. Deleting a line also deletes its reporting tags. | ```json { "name": "", "description": "", "postingDate": "2026-01-15", "reversalDate": "2026-01-15", "reversalReason": "", "reversalDescription": "", "url": "", "createJournalEntryLines": [ { "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "transactionalCurrency": "", "description": "", "intercompanyPartnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "debit": "19.99", "credit": "19.99", "exchangeRate": "19.99", "reportingTags": [ { "dimensionCode": "", "modelId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ] } ], "updateJournalEntryLines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "intercompanyPartnerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "description": "", "debit": "19.99", "credit": "19.99", "transactionalCurrency": "", "exchangeRate": "19.99", "reportingTags": [ { "dimensionCode": "", "modelId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ] } ], "deleteJournalEntryLines": [ "" ] } ``` ## Response `200` Success Under `data` (updateJournalEntryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the updated journal entry. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `INVALID_REVERSAL_REASON` | 400 | | `NOT_FOUND` | 400 | | `REFERENCED_RECORD_NOT_FOUND` | 400 | | `VALIDATION_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: INVALID_REVERSAL_REASON, NOT_FOUND, REFERENCED_RECORD_NOT_FOUND, VALIDATION_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Reverse a journal entry Operation ID: `reverseJournalEntry` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/reverseJournalEntry) `POST /accounting/journal-entries/{id}/reverse` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Reverses a journal entry. The request creates a new reversal journal entry with the original's debits and credits swapped, dated reversalDate. The reversal journal entry starts unposted and pending approval. If no approval policy applies, the reversal journal entry is approved at once and posted to the general ledger. Otherwise the reversal journal entry is posted when it is approved. Only a manual journal entry can be reversed, and only once. A journal entry created by a revaluation, or one that is itself a reversal, is not manual and can't be reversed. Reversing a journal entry a second time fails with ALREADY_REVERSED. Reversing a journal entry that is not manual, such as a reversal or a revaluation's entry, fails with VALIDATION_FAILED, with the rule's message in reason and rule 'systemEntryNotReversible'. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier for the journal entry to reverse. | ## Request body Required, `application/json`: reverseJournalEntryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `reversalDate` | date | yes | The posting date of the reversal entry. Use format YYYY-MM-DD. It must be on or after the original entry's posting date. | | `reversalReason` | string | yes | The reason for the reversal. Accepted values: 'accrualReversal', 'revaluationReversal', 'estimate', 'rebook', 'reversingSFD', 'other'. Any other value fails with INVALID_REVERSAL_REASON. | | `reversalDescription` | string | no | A free-form reversal description recorded on the original entry's header. Required unless reversalReason is 'other', which fails with REVERSAL_DESCRIPTION_REQUIRED. | ```json { "reversalDate": "2026-01-15", "reversalReason": "", "reversalDescription": "" } ``` ## Response `200` Success Under `data` (reverseJournalEntryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `reversalJournalEntryId` | string | yes | The unique identifier for the created reversal journal entry. | ```json { "data": { "reversalJournalEntryId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `REVERSAL_DESCRIPTION_REQUIRED` | 400 | | `ALREADY_REVERSED` | 400 | | `INVALID_REVERSAL_REASON` | 400 | | `VALIDATION_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, REVERSAL_DESCRIPTION_REQUIRED, ALREADY_REVERSED, INVALID_REVERSAL_REASON, VALIDATION_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Void a journal entry Operation ID: `voidJournalEntry` Group: Accounting › Journal Entries [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/voidJournalEntry) `POST /accounting/journal-entries/{id}/void` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Voids a journal entry. Voiding sets the journal entry's status to 'voided' and doesn't create a new journal entry. If the journal entry was reversed, its reversal journal entry is voided too. Voiding also removes the journal entry's link to its source document and makes the journal entry a manual one. Voiding a journal entry that is already voided fails with ALREADY_VOIDED. The request fails with VALIDATION_FAILED, with the rule's message in reason, if its accounting period is closed. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier for the journal entry to void. | ## Request body Required, `application/json`: voidJournalEntryInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `voidReason` | string | yes | The reason recorded on the voided entry. | ```json { "voidReason": "" } ``` ## Response `200` Success Under `data` (voidJournalEntryOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the voided journal entry. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `ALREADY_VOIDED` | 400 | | `VALIDATION_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, ALREADY_VOIDED, VALIDATION_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Outbound Payments Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingOutboundPayments) An outbound payment is money paid to a vendor or employee for their vendor bills or expense reports. A payment has one or more payment items, and each item pays one vendor bill or expense report. GET and search return the items in lines. A payment that hasn't been executed can be changed or deleted. A payment that is 'successful', 'partiallySuccessful', or 'processing' can only be voided. Deleting or voiding a payment makes the documents it paid open again. Payroll payments and standalone payments are outbound payments too, with their own endpoints. ## Endpoints - [List outbound payments](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/outboundPayments.html): `GET /accounting/outbound-payments` - [Create an outbound payment](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/createOutboundPayment.html): `POST /accounting/outbound-payments` - [Search outbound payments](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/searchOutboundPayment.html): `POST /accounting/outbound-payments/search` - [Retrieve an outbound payment](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/getOutboundPayment.html): `GET /accounting/outbound-payments/{id}` - [Update an outbound payment](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/updateOutboundPayment.html): `PATCH /accounting/outbound-payments/{id}` - [Delete an outbound payment](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/deleteOutboundPayment.html): `DELETE /accounting/outbound-payments/{id}` - [Void an outbound payment](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/voidOutboundPayment.html): `POST /accounting/outbound-payments/{id}/void` --- # List outbound payments Operation ID: `outboundPayments` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/outboundPayments) `GET /accounting/outbound-payments` Read-only: Reads and returns; changes nothing. Lists outbound payments, without their lines. To get the lines, use GET accounting/outbound-payments/:id or POST accounting/outbound-payments/search. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `outboundPaymentNumber` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `paymentDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `paymentCurrency` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `paymentType` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `status` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `approvalStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `applicationStatus` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `externalId` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `origin` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `singlePayment` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, outboundPaymentNumber, paymentDate, paymentCurrency, paymentType, status, approvalStatus, applicationStatus, lastModifiedDate - Selectable: id, outboundPaymentNumber, description, paymentDate, paymentCurrency, paymentType, status, approvalStatus, applicationStatus, paymentId, externalId, origin, singlePayment, paymentAccountName, entityName, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of outboundPaymentsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the outbound payment. | | `outboundPaymentNumber` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The outbound payment number. Absent until a number has been assigned. | | `description` | string \| null | no | no | no | A description of the payment. | | `paymentDate` | date \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date the payment was made. Format YYYY-MM-DD. | | `paymentCurrency` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The currency the payment is denominated in. Format ISO 4217, for example 'USD'. | | `paymentType` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How the money was sent, for example 'ach', 'wire' or 'check'. | | `status` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The processing status of the payment. | | `approvalStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | Where the payment sits in the approval workflow. | | `applicationStatus` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | How much of the payment has been applied to bills. Values include 'unapplied', 'partiallyApplied' and 'applied'. | | `paymentId` | string \| null | no | no | no | The payment reference assigned by the bank or payment provider. It is not filterable. Match on it through POST accounting/outbound-payments/search. | | `externalId` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The identifier this payment has in the upstream system that produced it. It is not unique. | | `origin` | string \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | no | The source system this payment came from. Recorded when an integration created the payment. | | `singlePayment` | boolean \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | no | Whether the payment covers a single bill rather than a batch. | | `paymentAccountName` | string \| null | no | no | no | The name of the bank general ledger account the money left. It is not filterable. To scope by account, use paymentAccountId on POST accounting/outbound-payments/search. | | `entityName` | string \| null | no | no | no | The name of the legal entity that made the payment. It is not filterable. To scope by entity, use entityId on POST accounting/outbound-payments/search. | | `lastModifiedDate` | date-time \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the payment last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "outboundPaymentNumber": "", "description": "", "paymentDate": "2026-01-15", "paymentCurrency": "", "paymentType": "", "status": "", "approvalStatus": "", "applicationStatus": "", "paymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "origin": "", "singlePayment": true, "paymentAccountName": "", "entityName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z" } ], "pagination": { "hasMore": true, "nextCursor": "" } } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Create an outbound payment Operation ID: `createOutboundPayment` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/createOutboundPayment) `POST /accounting/outbound-payments` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Creates an outbound payment. The payment pays vendor bills or expense reports. No money is sent, and the payment starts in status 'manuallyRecorded'. Each payment item pays one document. documentType sets the kind of document, and every item in the payment pays that kind. Every document must be approved and not voided, every item on an expense report must be reimbursable, all documents must be in one currency, and no payment item can pay more than its document's open amount. The response contains only the new payment's id. To see the payment, use GET accounting/outbound-payments/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | ## Request body Required, `application/json`: createOutboundPaymentInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `accountId` | string | yes | The bank or cash account the payment is made from. See id on GET accounting/accounts/:id. Example: `"6d1e4b27-8a90-4c31-b5f2-7e8a9c0d1b23"`. | | `entityId` | string | yes | The legal entity the payment is posted under. See id on GET base/entities. Example: `"8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e"`. | | `paymentDate` | date | yes | The date the payment is made. Use format YYYY-MM-DD. Example: `"2026-08-27"`. | | `paymentCurrency` | string | yes | The currency of the payment. Format ISO 4217, for example 'USD'. Governs each item's amount unless the item names its own currency. Example: `"USD"`. | | `documentType` | string | yes | The one kind of document every item pays, either 'VendorBillHeaderBase' or 'ExpenseReportBase'. A payment pays one kind, not a mixture. Example: `"VendorBillHeaderBase"`. | | `singlePayment` | boolean | no | Whether this is a single payment rather than a batch. | | `commonDescription` | string | no | A description applied to the payment as a whole. | | `skipJournalEntry` | boolean | no | Whether to skip journal-entry creation for this payment. Defaults to false. | | `externalUrl` | string | no | A link back to the payment in the upstream system. | | `origin` | string | no | The source system this payment came from. | | `senderAccountId` | string | no | A bank account identifier for the sender, supplied by an integration. It is free-form text, not a reference to an account record. | | `actualExchangeRate` | decimal | no | The exchange rate the bank actually applied, as a decimal. | | `systemExchangeRate` | decimal | no | The exchange rate the system derived, as a decimal. | | `paymentItems` | array | yes | The documents this payment settles. At least one is required. Example: `[{"documentId":"c41d0b58-2e97-4f6a-8d13-7b5e9c204af6","entityId":"8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e","amount":"1200.00","description":"Invoice 5512"}]`. | | `paymentItems[].documentId` | string | yes | The vendor bill or expense report being paid, of the kind named by documentType. See id on GET accounting/vendor-bills/:id or GET accounting/expense-reports/:id. | | `paymentItems[].entityId` | string | yes | The legal entity the document is posted under. See id on GET base/entities. | | `paymentItems[].amount` | decimal | yes | The amount applied to this document, as a decimal. | | `paymentItems[].currency` | string | no | The currency for this item. Format ISO 4217, for example 'USD'. Omit to use the payment's currency. | | `paymentItems[].description` | string | yes | What this item pays for. Required. | | `attachments` | array | no | Files to attach to the payment. Each names an already-uploaded file by its id. | | `attachments[].fileName` | string | yes | The display name of the attached file. | | `attachments[].fileId` | string | yes | The identifier of the already-uploaded file. There is no endpoint for it. | ```json { "accountId": "6d1e4b27-8a90-4c31-b5f2-7e8a9c0d1b23", "entityId": "8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e", "paymentDate": "2026-08-27", "paymentCurrency": "USD", "documentType": "VendorBillHeaderBase", "singlePayment": true, "commonDescription": "", "skipJournalEntry": true, "externalUrl": "", "origin": "", "senderAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "actualExchangeRate": "19.99", "systemExchangeRate": "19.99", "paymentItems": [ { "documentId": "c41d0b58-2e97-4f6a-8d13-7b5e9c204af6", "entityId": "8f2c1d40-5a6b-4c7d-9e80-1f2a3b4c5d6e", "amount": "1200.00", "description": "Invoice 5512" } ], "attachments": [ { "fileName": "", "fileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } ] } ``` ## Response `200` Success Under `data` (createOutboundPaymentOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the created payment. Example: `"e93b7d15-2c48-4a60-9f31-5b7c8d9e0a12"`. | ```json { "data": { "id": "e93b7d15-2c48-4a60-9f31-5b7c8d9e0a12" }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `REFERENCE_NOT_FOUND` | 400 | | `DOCUMENT_NOT_PAYABLE` | 400 | | `PAYMENT_AMOUNT_EXCEEDS_OPEN_AMOUNT` | 400 | | `DOCUMENT_CURRENCY_MISMATCH` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: REFERENCE_NOT_FOUND, DOCUMENT_NOT_PAYABLE, PAYMENT_AMOUNT_EXCEEDS_OPEN_AMOUNT, DOCUMENT_CURRENCY_MISMATCH - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Search outbound payments Operation ID: `searchOutboundPayment` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/searchOutboundPayment) `POST /accounting/outbound-payments/search` Read-only: A read that takes its filters in the body. Changes nothing, and ignores Idempotency-Key. Searches outbound payments, with their lines. Each match is the same record that GET accounting/outbound-payments/:id returns. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Request body Required, `application/json`: searchOutboundPaymentInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `filters` | object | no | The filters to apply. An outbound payment must match every filter you send. Omit to return all outbound payments. Example: `{"applicationStatus":"unapplied"}`. | | `filters.id` | string | no | Limits results to the outbound payment with this identifier. | | `filters.outboundPaymentNumber` | string | no | Limits results to the payment with this number. | | `filters.paymentId` | string | no | Limits results to payments with this bank or provider reference. | | `filters.status` | string | no | Limits results to payments in this processing status. Any other value matches no payment. | | `filters.approvalStatus` | string | no | Limits results to payments in this approval state. Any other value matches no payment. | | `filters.applicationStatus` | string | no | Limits results by how much of the payment has been applied. Values include 'unapplied', 'partiallyApplied' and 'applied'. | | `filters.paymentType` | string | no | Limits results to payments sent this way, for example 'ach', 'wire' or 'check'. | | `filters.paymentCurrency` | string | no | Limits results to payments denominated in this currency. Format ISO 4217, for example 'USD'. | | `filters.paymentAccountId` | string | no | Limits results to payments made from this bank general ledger account. See id on GET accounting/accounts/:id. | | `filters.entityId` | string | no | Limits results to payments made by this legal entity. See id on GET base/entities. | | `filters.entityName` | string | no | Limits results to payments made by the legal entity with this name. The match is exact. Use entityId when you already have the id. | | `filters.origin` | string | no | Limits results to payments created by this source system. The match is exact. | | `filters.externalId` | string | no | Limits results to payments with this upstream identifier. It is not unique, so this can match several payments. | | `page` | object | no | Pagination controls. Omit for the first page. Example: `{"size":50}`. | | `page.cursor` | string | no | The cursor from the previous response's pagination.nextCursor. Omit on the first request. | | `page.size` | number | no | The number of payments per page. Defaults to 50, and a value above 200 is treated as 200, not rejected. | ```json { "filters": { "applicationStatus": "unapplied" }, "page": { "size": 50 } } ``` ## Response `200` Success Under `data` (searchOutboundPaymentOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `items` | array | yes | The outbound payments on this page, most recently created first. An empty array when nothing matches. | | `items[].id` | string | yes | The unique identifier for the outbound payment. | | `items[].outboundPaymentNumber` | string | no | The outbound payment number. Absent until a number has been assigned. | | `items[].description` | string | no | A description of the payment. | | `items[].paymentDate` | date | yes | The date the payment was made. Format YYYY-MM-DD. | | `items[].paymentCurrency` | string | yes | The currency the payment is denominated in. Format ISO 4217, for example 'USD'. Governs totalPaid, appliedAmount and unappliedAmount. | | `items[].paymentType` | string | yes | How the money was sent, for example 'ach', 'wire' or 'check'. | | `items[].status` | string | yes | The processing status of the payment. | | `items[].approvalStatus` | string | no | Where the payment sits in the approval workflow. | | `items[].applicationStatus` | string | no | How much of the payment has been applied to bills. Values include 'unapplied', 'partiallyApplied' and 'applied'. | | `items[].totalPaid` | decimal | no | The total amount paid, in the payment currency. | | `items[].appliedAmount` | decimal | no | The portion already applied against vendor bills, in the payment currency. | | `items[].unappliedAmount` | decimal | no | The portion not yet applied to any bill, in the payment currency. | | `items[].bankCurrency` | string | no | The currency of the bank account the money left, when it differs from the payment currency. Format ISO 4217, for example 'USD'. | | `items[].paymentId` | string | no | The payment reference assigned by the bank or payment provider. | | `items[].externalId` | string | no | The identifier this payment has in the upstream system that produced it. It is not unique. | | `items[].externalUrl` | string | no | A link to the payment in an external system, when one is stored. | | `items[].origin` | string | no | The source system this payment came from. Recorded when an integration created the payment. | | `items[].singlePayment` | boolean | no | Whether the payment covers a single bill rather than a batch. | | `items[].paymentAccountId` | string | no | The bank general ledger account the money left. See id on GET accounting/accounts/:id. | | `items[].paymentAccountName` | string | no | The name of the bank general ledger account the money left. | | `items[].entityId` | string | no | The legal entity that made the payment. See id on GET base/entities. | | `items[].entityName` | string | no | The name of the legal entity that made the payment. | | `items[].lastModifiedDate` | date-time | yes | The date and time the payment last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `items[].lines` | array | no | The payment lines, one per payee or bill covered. An empty array when the payment has none. | | `items[].lines[].id` | string | yes | The unique identifier for the line. | | `items[].lines[].lineNumber` | string | no | The line number. | | `items[].lines[].description` | string | yes | A description of the line. | | `items[].lines[].amount` | decimal | no | The amount paid on this line, in the line currency. | | `items[].lines[].currency` | string | yes | The currency for the line. Format ISO 4217, for example 'USD'. | | `items[].lines[].paymentDate` | date | no | The date this line was paid, when it differs from the header. Format YYYY-MM-DD. | | `items[].lines[].paymentType` | string | no | How this line was sent, for example 'ach', 'wire' or 'check'. | | `items[].lines[].receiverName` | string | yes | The name of the payee. | | `items[].lines[].receiverAccount` | string | no | The payee bank account number. It is returned here and on search but never on the collection endpoint and never filterable. | | `items[].lines[].receiverBankCode` | string | no | The payee sort or routing code. It is returned here and on search but never on the collection endpoint and never filterable. | | `items[].lines[].customReference` | string | no | A reference you supplied, carried on the line. | | `pagination` | object | yes | The pagination details for this page. | | `pagination.nextCursor` | string | no | The cursor for the next page. Pass it as page.cursor. Absent on the last page. | | `pagination.hasMore` | boolean | yes | True when more matches remain beyond this page. | ```json { "data": { "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "outboundPaymentNumber": "", "description": "", "paymentDate": "2026-01-15", "paymentCurrency": "", "paymentType": "", "status": "", "approvalStatus": "", "applicationStatus": "", "totalPaid": "19.99", "appliedAmount": "19.99", "unappliedAmount": "19.99", "bankCurrency": "", "paymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalUrl": "", "origin": "", "singlePayment": true, "paymentAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "paymentAccountName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": "", "description": "", "amount": "19.99", "currency": "", "paymentDate": "2026-01-15", "paymentType": "", "receiverName": "", "receiverAccount": "", "receiverBankCode": "", "customReference": "" } ] } ], "pagination": { "nextCursor": "", "hasMore": true } }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Statuses - `400` — Invalid request or business failure - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Retrieve an outbound payment Operation ID: `getOutboundPayment` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/getOutboundPayment) `GET /accounting/outbound-payments/{id}` Read-only: Reads and returns; changes nothing. Retrieves an outbound payment. Use the outbound payment's id, not its outboundPaymentNumber. An outbound payment has one or more lines. The response includes the lines, which GET accounting/outbound-payments doesn't return. POST accounting/outbound-payments/search returns the same record for each match. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `id` | path | string | yes | The unique identifier of the outbound payment you want to retrieve. Example: `"3b7d5e9c-1a24-4f80-9c6d-2e8f70b13a45"`. | ## Response `200` Success Under `data` (getOutboundPaymentOutput): | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | string | yes | The unique identifier for the outbound payment. | | `outboundPaymentNumber` | string | no | The outbound payment number. Absent until a number has been assigned. | | `description` | string | no | A description of the payment. | | `paymentDate` | date | yes | The date the payment was made. Format YYYY-MM-DD. | | `paymentCurrency` | string | yes | The currency the payment is denominated in. Format ISO 4217, for example 'USD'. Governs totalPaid, appliedAmount and unappliedAmount. | | `paymentType` | string | yes | How the money was sent, for example 'ach', 'wire' or 'check'. | | `status` | string | yes | The processing status of the payment. | | `approvalStatus` | string | no | Where the payment sits in the approval workflow. | | `applicationStatus` | string | no | How much of the payment has been applied to bills. Values include 'unapplied', 'partiallyApplied' and 'applied'. | | `totalPaid` | decimal | no | The total amount paid, in the payment currency. | | `appliedAmount` | decimal | no | The portion already applied against vendor bills, in the payment currency. | | `unappliedAmount` | decimal | no | The portion not yet applied to any bill, in the payment currency. | | `bankCurrency` | string | no | The currency of the bank account the money left, when it differs from the payment currency. Format ISO 4217, for example 'USD'. | | `paymentId` | string | no | The payment reference assigned by the bank or payment provider. | | `externalId` | string | no | The identifier this payment has in the upstream system that produced it. It is not unique. | | `externalUrl` | string | no | A link to the payment in an external system, when one is stored. | | `origin` | string | no | The source system this payment came from. Recorded when an integration created the payment. | | `singlePayment` | boolean | no | Whether the payment covers a single bill rather than a batch. | | `paymentAccountId` | string | no | The bank general ledger account the money left. See id on GET accounting/accounts/:id. | | `paymentAccountName` | string | no | The name of the bank general ledger account the money left. | | `entityId` | string | no | The legal entity that made the payment. See id on GET base/entities. | | `entityName` | string | no | The name of the legal entity that made the payment. | | `lastModifiedDate` | date-time | yes | The date and time the payment last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | | `lines` | array | no | The payment lines, one per payee or bill covered. An empty array when the payment has none. | | `lines[].id` | string | yes | The unique identifier for the line. | | `lines[].lineNumber` | string | no | The line number. | | `lines[].description` | string | yes | A description of the line. | | `lines[].amount` | decimal | no | The amount paid on this line, in the line currency. | | `lines[].currency` | string | yes | The currency for the line. Format ISO 4217, for example 'USD'. | | `lines[].paymentDate` | date | no | The date this line was paid, when it differs from the header. Format YYYY-MM-DD. | | `lines[].paymentType` | string | no | How this line was sent, for example 'ach', 'wire' or 'check'. | | `lines[].receiverName` | string | yes | The name of the payee. | | `lines[].receiverAccount` | string | no | The payee bank account number. It is returned here and on search but never on the collection endpoint and never filterable. | | `lines[].receiverBankCode` | string | no | The payee sort or routing code. It is returned here and on search but never on the collection endpoint and never filterable. | | `lines[].customReference` | string | no | A reference you supplied, carried on the line. | ```json { "data": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "outboundPaymentNumber": "", "description": "", "paymentDate": "2026-01-15", "paymentCurrency": "", "paymentType": "", "status": "", "approvalStatus": "", "applicationStatus": "", "totalPaid": "19.99", "appliedAmount": "19.99", "unappliedAmount": "19.99", "bankCurrency": "", "paymentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "externalUrl": "", "origin": "", "singlePayment": true, "paymentAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "paymentAccountName": "", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityName": "", "lastModifiedDate": "2026-01-15T09:30:00.000Z", "lines": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "lineNumber": "", "description": "", "amount": "19.99", "currency": "", "paymentDate": "2026-01-15", "paymentType": "", "receiverName": "", "receiverAccount": "", "receiverBankCode": "", "customReference": "" } ] }, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Update an outbound payment Operation ID: `updateOutboundPayment` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/updateOutboundPayment) `PATCH /accounting/outbound-payments/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Updates an outbound payment with the fields you send. A field you leave out keeps its current value. Use the outbound payment's id, not its outboundPaymentNumber. Payment items change through three separate arrays, applied in this order: deletePaymentItems, updatePaymentItems, createPaymentItems. Leave out all three to keep the items as they are. When the payment currency differs from the bank account's currency, leaving out actualExchangeRate sets it to systemExchangeRate. If you leave that out too, both are set to the system rate for the payment date. This doesn't apply to a draft payment. Only a payment that hasn't been executed can be changed. Its payment items can change too. For a payment that isn't a draft, the status must be 'manuallyRecorded', 'scheduled', 'unknown', 'rejected', or 'cancelled'. For any other status, the request fails with PAYMENT_NOT_EDITABLE. To withdraw a payment that is 'successful', 'partiallySuccessful', or 'processing', use POST accounting/outbound-payments/:id/void. The payment must keep at least one item. New and changed items are checked the same way as on create. When an item's amount changes, its old amount is credited back first, so sending the same amount again doesn't count as a second payment. The response is empty. To see the updated payment, use GET accounting/outbound-payments/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the outbound payment you want to update. Example: `"e93b7d15-2c48-4a60-9f31-5b7c8d9e0a12"`. | ## Request body Required, `application/json`: updateOutboundPaymentInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `accountId` | string | no | The bank or cash account the payment is made from. See id on GET accounting/accounts/:id. | | `paymentDate` | date | no | The date the payment is made. Use format YYYY-MM-DD. Example: `"2026-08-28"`. | | `paymentCurrency` | string | no | The currency of the payment. Format ISO 4217, for example 'USD'. Governs each item's amount unless the item names its own currency. | | `commonDescription` | string | no | A description applied to the payment as a whole. Example: `"August vendor run (revised)"`. | | `externalUrl` | string | no | A link back to the payment in the upstream system. | | `origin` | string | no | The source system this payment came from. | | `senderAccountId` | string | no | A bank account identifier for the sender, supplied by an integration. It is free-form text, not a reference to an account record. | | `actualExchangeRate` | decimal | no | The exchange rate the bank actually applied, as a decimal. | | `systemExchangeRate` | decimal | no | The exchange rate the system derived, as a decimal. | | `skipJournalEntry` | boolean | no | Whether to skip journal-entry creation for this payment. | | `createPaymentItems` | array | no | Documents to add to this payment. Each has the same shape as on create, without an id. | | `createPaymentItems[].documentId` | string | yes | The document being paid, of the kind this payment already pays. See id on GET accounting/vendor-bills/:id or GET accounting/expense-reports/:id. | | `createPaymentItems[].entityId` | string | yes | The legal entity the document is posted under. See id on GET base/entities. | | `createPaymentItems[].amount` | decimal | yes | The amount applied to this document, as a decimal. | | `createPaymentItems[].currency` | string | no | The currency for this item. Format ISO 4217, for example 'USD'. Omit to use the payment's currency. | | `createPaymentItems[].description` | string | yes | What this item pays for. Required. | | `updatePaymentItems` | array | no | Payment items to change, each addressed by the id the payment publishes. | | `updatePaymentItems[].id` | string | yes | The unique identifier for the payment item to change. It must belong to this payment. | | `updatePaymentItems[].documentId` | string | no | The document being paid. See id on GET accounting/vendor-bills/:id or GET accounting/expense-reports/:id. | | `updatePaymentItems[].entityId` | string | no | The legal entity the document is posted under. See id on GET base/entities. | | `updatePaymentItems[].amount` | decimal | no | The amount applied to this document, as a decimal. | | `updatePaymentItems[].currency` | string | no | The currency for this item. Format ISO 4217, for example 'USD'. | | `updatePaymentItems[].description` | string | no | What this item pays for. | | `deletePaymentItems` | array | no | The identifiers of payment items to remove, applied before the updates and creates. Each must belong to this payment. Removing them all is refused, so delete the payment instead. | ```json { "accountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "paymentDate": "2026-08-28", "paymentCurrency": "", "commonDescription": "August vendor run (revised)", "externalUrl": "", "origin": "", "senderAccountId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "actualExchangeRate": "19.99", "systemExchangeRate": "19.99", "skipJournalEntry": true, "createPaymentItems": [ { "documentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amount": "19.99", "currency": "", "description": "" } ], "updatePaymentItems": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "documentId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "entityId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "amount": "19.99", "currency": "", "description": "" } ], "deletePaymentItems": [ "" ] } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `PAYMENT_NOT_EDITABLE` | 400 | | `REFERENCE_NOT_FOUND` | 400 | | `ITEM_NOT_FOUND` | 400 | | `NO_PAYMENT_ITEMS` | 400 | | `DOCUMENT_NOT_PAYABLE` | 400 | | `PAYMENT_AMOUNT_EXCEEDS_OPEN_AMOUNT` | 400 | | `DOCUMENT_CURRENCY_MISMATCH` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, PAYMENT_NOT_EDITABLE, REFERENCE_NOT_FOUND, ITEM_NOT_FOUND, NO_PAYMENT_ITEMS, DOCUMENT_NOT_PAYABLE, PAYMENT_AMOUNT_EXCEEDS_OPEN_AMOUNT, DOCUMENT_CURRENCY_MISMATCH - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Delete an outbound payment Operation ID: `deleteOutboundPayment` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/deleteOutboundPayment) `DELETE /accounting/outbound-payments/{id}` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Deletes an outbound payment. Only a payment that hasn't been executed can be deleted. The documents the payment paid become unpaid again. For a payment that isn't a draft, the status must be 'manuallyRecorded', 'scheduled', 'unknown', 'rejected', or 'cancelled'. For any other status, the request fails with PAYMENT_NOT_EDITABLE. To withdraw a payment that is 'successful', 'partiallySuccessful', or 'processing', use POST accounting/outbound-payments/:id/void instead. The accounting period of the payment's journal entry must be open for the payment's legal entity and for the legal entity of every payment item. A draft payment skips this check too. The response is empty. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the outbound payment you want to delete. Example: `"e93b7d15-2c48-4a60-9f31-5b7c8d9e0a12"`. | ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `PAYMENT_NOT_EDITABLE` | 400 | | `POSTING_PERIOD_CLOSED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, PAYMENT_NOT_EDITABLE, POSTING_PERIOD_CLOSED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Void an outbound payment Operation ID: `voidOutboundPayment` Group: Accounting › Outbound Payments [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/voidOutboundPayment) `POST /accounting/outbound-payments/{id}/void` Retry-safe: A write that accepts Idempotency-Key: repeating a request with the same key replays the stored answer instead of executing twice. Voids an outbound payment. The payment's journal entry is voided with it, and the vendor bills or expense reports it paid become open again. Only a payment whose status is 'successful', 'partiallySuccessful', 'manuallyRecorded', or 'processing' can be voided. For a payment that is already voided, the request fails with PAYMENT_ALREADY_VOIDED, and for any other status with PAYMENT_NOT_VOIDABLE. The accounting period of the payment's journal entry must be open for the payment's legal entity. A payment that hasn't been executed can be deleted with DELETE accounting/outbound-payments/:id instead, and a 'manuallyRecorded' payment can be either voided or deleted. The response is empty. To see the voided payment, use GET accounting/outbound-payments/:id. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | | `Idempotency-Key` | header | string | no | An opaque caller-chosen string (1-255 characters) that makes this request safe to retry. Repeating the same key with an identical request body replays the original response instead of re-executing - but only once that original request succeeded; a failed attempt re-executes on retry. Repeating the key with a different body is answered with 422. A key still in flight is answered with 409 - as is a key whose original request was interrupted before it finished, for a few minutes afterwards, until that attempt is presumed dead. A completed response stays replayable for 24 hours. The key is scoped to the caller, not to the action: reusing it for a different action is answered with 409 or 422 rather than treated independently. On read actions this header is ignored. On write actions that cannot offer the guarantee it is answered with `400 IDEMPOTENCY_NOT_SUPPORTED` rather than silently ignored. | | `id` | path | string | yes | The unique identifier of the outbound payment you want to void. Example: `"3f1a8c25-9d47-4e60-b892-0c5a7e3b1d84"`. | ## Request body Required, `application/json`: voidOutboundPaymentInput. | Field | Type | Required | Description | | --- | --- | --- | --- | | `voidReason` | string | yes | Why the payment is being voided. It is stored on the payment and shown in the audit trail, and an empty or whitespace-only string is refused. Example: `"Paid the wrong vendor"`. | ```json { "voidReason": "Paid the wrong vendor" } ``` ## Response `200` Success ```json { "data": {}, "diagnostics": [ { "code": "", "message": "", "severity": "error", "path": [ "" ], "details": "
" } ] } ``` ## Response headers - `Idempotency-Replayed` — Present and `true` only when this response replays a previously stored one for the same Idempotency-Key, rather than reflecting a live execution. Absent on a live response - never sent as `false`. ## Errors | Code | Status | | --- | --- | | `NOT_FOUND` | 400 | | `VOID_REASON_REQUIRED` | 400 | | `PAYMENT_ALREADY_VOIDED` | 400 | | `PAYMENT_NOT_VOIDABLE` | 400 | | `POSTING_PERIOD_CLOSED` | 400 | | `VOID_FAILED` | 400 | | `IDEMPOTENCY_KEY_CONFLICT` | 409 | | `IDEMPOTENCY_PAYLOAD_MISMATCH` | 422 | ## Statuses - `400` — Invalid request or business failure. Codes: NOT_FOUND, VOID_REASON_REQUIRED, PAYMENT_ALREADY_VOIDED, PAYMENT_NOT_VOIDABLE, POSTING_PERIOD_CLOSED, VOID_FAILED - `401` — The request carries no valid access token. - `403` — Not authorized - `404` — Not found - `409` — The Idempotency-Key is still being processed by another request. - `422` — The Idempotency-Key was already used with a different request payload. - `429` — Too many requests from this client address. The response has no body and no `Retry-After` header. - `500` — Internal server error --- # Payment Terms Group: Accounting [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/groups/accountingPaymentTerms) The payment terms of accounting: the agreed schedules on which an invoice becomes due, and the early-payment discounts attached to them. These are the tenant-wide catalogue: a term can be used on a record only once it is assigned to that record's legal entity, so a listed code can still be refused by a write such as POST accounting/customers. Sits under Accounting, beside the other domains of that module. ## Endpoints - [List payment terms](https://docs.api.lensing.ai/reference/lensing/v1/endpoints/paymentTerms.html): `GET /accounting/payment-terms` --- # List payment terms Operation ID: `paymentTerms` Group: Accounting › Payment Terms [Open in the interactive reference](https://docs.api.lensing.ai/lensing/v1/endpoints/paymentTerms) `GET /accounting/payment-terms` Read-only: Reads and returns; changes nothing. Lists payment terms. The list includes every payment term in your tenant. Each legal entity has its own list of these terms, managed under Settings & Policies > Feature Settings > Quote to Cash > Invoice Configuration. Only the terms on a legal entity's list can be used for its customers, sales orders, and invoice policies. No endpoint returns a legal entity's list. On POST accounting/customers and PATCH accounting/customers/:id, the request fails with UNKNOWN_REFERENCE when paymentTermCode isn't on the customer's legal entity's list. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `Api-Version` | header | string · v1 | yes | The API version this request targets, named by its public identifier. Required on every request; omitting it, or naming a version this company does not have, is answered with 400. This document describes version `v1`. | ## Query surface - `id` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `code` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between, like - `numberOfDays` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `discountPercentage` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `discountValidityDays` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - `lastModifiedDate` — filter by eq, ne, gt, gte, lt, lte, in, notIn, between - Orderable: id, code, label, numberOfDays, discountPercentage, discountValidityDays, lastModifiedDate - Selectable: id, code, label, description, numberOfDays, discountPercentage, discountValidityDays, lastModifiedDate - `page[size]` — Page size - `page[cursor]` — Opaque continuation cursor ## Response `200` Success Under `data`, an array of paymentTermsRow: | Field | Type | Required | Filter | Sort | Description | | --- | --- | --- | --- | --- | --- | | `id` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The unique identifier for the payment term. | | `code` | string | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`), Pattern (`like`) | yes | The code of the payment term. Unique within the tenant. Other records refer to a payment term by this code. See paymentTermCode on POST accounting/customers. Examples: 'net30', 'dueOnReceipt'. | | `label` | string | no | no | yes | The display name of the payment term. See paymentTermLabel on GET accounting/customers/:id. Example: 'Net 30'. | | `description` | string | no | no | no | A description of the payment term. | | `numberOfDays` | number | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The number of days after the invoice date on which payment is due. | | `discountPercentage` | decimal \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The discount for paying within discountValidityDays, as a decimal fraction of the invoice amount. For example, 0.02 is 2%. 0 when the term has no discount. | | `discountValidityDays` | number \| null | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The number of days after the invoice date during which discountPercentage applies. 0 when the term has no discount. | | `lastModifiedDate` | date-time | no | Exact (`eq`, `ne`), Range (`gt`, `gte`, `lt`, `lte`, `between`), Any of (`in`, `notIn`) | yes | The date and time the payment term last changed, in UTC. Format YYYY-MM-DDTHH:mm:ss.sssZ. | ```json { "data": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "code": "", "label": "