> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hitaji360.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Accounting HTTP API Reference

> Authoritative catalog of the accounting service's HTTP endpoints, grouped by controller.

Authoritative catalog of the accounting service's HTTP endpoints, grouped by
controller. Every controller under `src/accounting/controllers/` is listed with
its base route and each route's HTTP method, path, purpose and required
permission slug.

## How authorization is resolved

The global `PermissionsGuard` (`src/auth/permissions.guard.ts`) decides the
required permission for each request in this order:

1. **Explicit `@RequirePermissions(...)`** on the handler or controller class —
   the user needs **any one** of the listed slugs. These are shown verbatim
   below.
2. **Route-based inference** (fallback when no decorator is present) —
   `module:operation`, where:
   * `module` comes from a longest-prefix match in `ROUTE_TO_MODULE`. For
     accounting: `api/accounting/debts` and `api/accounting/account-defaults`
     map to `accounting-debts`; everything else under `api/accounting` maps to
     the catch-all `accounting` module.
   * `operation` comes from the HTTP method: `GET/HEAD -> read`, `POST ->
     create`, `PUT/PATCH -> update`, `DELETE -> delete`.

So an undecorated `GET api/accounting/accounts` effectively requires
`accounting:read`; an undecorated `POST` under the same base requires
`accounting:create`, and so on. In the tables below, the **Permission** column
shows the explicit decorator slug(s) when present, otherwise the inferred
`accounting:<op>` (marked *inferred*).

> Slug shorthand: `PERMISSIONS.X.Y` resolves to `module:action` via
> `buildPermission`, e.g. `PERMISSIONS.ACCOUNTING_BILLS.CREATE` =
> `accounting-bills:create`. The accounting modules are `accounting`,
> `accounting-accounts`, `accounting-journal`, `accounting-bills`,
> `accounting-expenses`, `accounting-invoices`, `accounting-debts`,
> `accounting-payments`, `accounting-budgets`, `accounting-contacts`,
> `accounting-reports`.

Most controllers also stack `@UseGuards(TenantAuthGuard, AccountingWorkspaceGuard)`
(or the equivalent), which enforce tenant isolation and the `workspaceId`
(accounting-book) scope on top of the permission check.

***

## account-defaults.controller.ts

Base: `api/accounting/account-defaults`

| Method | Path | Purpose                                          | Permission              |
| ------ | ---- | ------------------------------------------------ | ----------------------- |
| GET    | `/`  | Heuristic default-account suggestions for a book | `accounting-debts:read` |

## accounts.controller.ts

Base: `api/accounting/accounts` — no per-route `@RequirePermissions`; all
inferred from route+method (`accounting:<op>`).

| Method | Path                   | Purpose                         | Permission                       |
| ------ | ---------------------- | ------------------------------- | -------------------------------- |
| GET    | `/`                    | List accounts                   | `accounting:read` *(inferred)*   |
| GET    | `/by-type/:type`       | List accounts by type           | `accounting:read` *(inferred)*   |
| GET    | `/tree`                | Chart-of-accounts tree          | `accounting:read` *(inferred)*   |
| GET    | `/setup-status`        | Chart setup/bootstrap status    | `accounting:read` *(inferred)*   |
| POST   | `/bootstrap`           | Bootstrap a default chart       | `accounting:create` *(inferred)* |
| GET    | `/:id`                 | Get one account                 | `accounting:read` *(inferred)*   |
| GET    | `/:id/balance`         | Account balance                 | `accounting:read` *(inferred)*   |
| POST   | `/`                    | Create account                  | `accounting:create` *(inferred)* |
| PUT    | `/:id`                 | Update account                  | `accounting:update` *(inferred)* |
| DELETE | `/:id`                 | Delete account                  | `accounting:delete` *(inferred)* |
| POST   | `/:id/reparent`        | Move account under a new parent | `accounting:create` *(inferred)* |
| POST   | `/:id/freeze`          | Freeze account                  | `accounting:create` *(inferred)* |
| POST   | `/:id/disable`         | Disable account                 | `accounting:create` *(inferred)* |
| POST   | `/:id/merge`           | Merge into another account      | `accounting:create` *(inferred)* |
| POST   | `/:id/opening-balance` | Set opening balance             | `accounting:create` *(inferred)* |

## accounting-audit.controller.ts

Base: `api/accounting/audit-events`

| Method | Path | Purpose                      | Permission                     |
| ------ | ---- | ---------------------------- | ------------------------------ |
| GET    | `/`  | List accounting audit events | `accounting:read` *(inferred)* |

## accounting-bridge.controller.ts

Base: `accounting/bridge` (note: **not** under `api/accounting`). Service-to-
service posting bridge used by other products.

| Method | Path                                              | Purpose                   | Permission                                                                   |
| ------ | ------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------- |
| POST   | `/journal-intents`                                | Post a journal intent     | `accounting:write`                                                           |
| POST   | `/document-intents`                               | Post a document intent    | `accounting:write`                                                           |
| POST   | `/bulk-journal-intents`                           | Post many journal intents | `accounting:write`                                                           |
| POST   | `/reverse`                                        | Reverse a posted intent   | `accounting:write`                                                           |
| GET    | `/source-links/:productKey/:sourceType/:sourceId` | Resolve source links      | none declared (no `@RequirePermissions`; base path not in `ROUTE_TO_MODULE`) |
| POST   | `/source-links/statuses`                          | Bulk source-link statuses | `accounting:write`                                                           |

## accounting-periods.controller.ts

Base: `api/accounting/periods`

| Method | Path          | Purpose                 | Permission                       |
| ------ | ------------- | ----------------------- | -------------------------------- |
| GET    | `/`           | List accounting periods | `accounting:read` *(inferred)*   |
| POST   | `/`           | Create a period         | `accounting:create` *(inferred)* |
| POST   | `/:id/close`  | Close a period          | `accounting:create` *(inferred)* |
| POST   | `/:id/reopen` | Reopen a period         | `accounting:create` *(inferred)* |

## bank-loans.controller.ts

Base: `api/accounting/loans` — inferred permissions.

| Method | Path                           | Purpose            | Permission                       |
| ------ | ------------------------------ | ------------------ | -------------------------------- |
| GET    | `/`                            | List bank loans    | `accounting:read` *(inferred)*   |
| GET    | `/stats`                       | Loan stats         | `accounting:read` *(inferred)*   |
| GET    | `/:id`                         | Get one loan       | `accounting:read` *(inferred)*   |
| POST   | `/`                            | Create a loan      | `accounting:create` *(inferred)* |
| PUT    | `/:id`                         | Update a loan      | `accounting:update` *(inferred)* |
| POST   | `/:id/activate`                | Activate a loan    | `accounting:create` *(inferred)* |
| POST   | `/:id/close`                   | Close a loan       | `accounting:create` *(inferred)* |
| DELETE | `/:id`                         | Delete a loan      | `accounting:delete` *(inferred)* |
| POST   | `/:id/repayments`              | Record a repayment | `accounting:create` *(inferred)* |
| DELETE | `/:id/repayments/:repaymentId` | Delete a repayment | `accounting:delete` *(inferred)* |

## bank-statement-lines.controller.ts

Base: `api/accounting/bank-statement-lines`

| Method | Path      | Purpose                 | Permission                       |
| ------ | --------- | ----------------------- | -------------------------------- |
| GET    | `/`       | List statement lines    | `accounting:read` *(inferred)*   |
| POST   | `/import` | Import statement lines  | `accounting:create` *(inferred)* |
| DELETE | `/:id`    | Delete a statement line | `accounting:delete` *(inferred)* |

## bank-reconciliations.controller.ts

Base: `api/accounting/bank-reconciliations`

| Method | Path                                        | Purpose                          | Permission                       |
| ------ | ------------------------------------------- | -------------------------------- | -------------------------------- |
| GET    | `/statement`                                | Statement view                   | `accounting:read` *(inferred)*   |
| GET    | `/stats`                                    | Reconciliation stats             | `accounting:read` *(inferred)*   |
| GET    | `/`                                         | List reconciliations             | `accounting:read` *(inferred)*   |
| GET    | `/:id`                                      | Get one reconciliation           | `accounting:read` *(inferred)*   |
| POST   | `/`                                         | Create a reconciliation          | `accounting:create` *(inferred)* |
| GET    | `/:reconciliationId/statement-lines`        | Lines for a reconciliation       | `accounting:read` *(inferred)*   |
| GET    | `/:reconciliationId/system-transactions`    | System txns for a reconciliation | `accounting:read` *(inferred)*   |
| POST   | `/:reconciliationId/match`                  | Match a line                     | `accounting:create` *(inferred)* |
| POST   | `/:reconciliationId/unmatch`                | Unmatch a line                   | `accounting:create` *(inferred)* |
| POST   | `/:reconciliationId/attach-lines`           | Attach lines                     | `accounting:create` *(inferred)* |
| GET    | `/:reconciliationId/auto-match-suggestions` | Auto-match suggestions           | `accounting:read` *(inferred)*   |
| POST   | `/:reconciliationId/auto-match-apply`       | Apply auto-matches               | `accounting:create` *(inferred)* |
| DELETE | `/:id`                                      | Delete a reconciliation          | `accounting:delete` *(inferred)* |
| POST   | `/:id/complete`                             | Complete a reconciliation        | `accounting:create` *(inferred)* |

## bills.controller.ts

Base: `api/accounting/bills` — explicit `@RequirePermissions` on all mutations.

| Method | Path                               | Purpose                      | Permission                     |
| ------ | ---------------------------------- | ---------------------------- | ------------------------------ |
| GET    | `/`                                | List bills                   | `accounting:read` *(inferred)* |
| GET    | `/stats`                           | Bill stats                   | `accounting:read` *(inferred)* |
| GET    | `/returnable`                      | Returnable bills             | `accounting:read` *(inferred)* |
| GET    | `/:id`                             | Get one bill                 | `accounting:read` *(inferred)* |
| POST   | `/`                                | Create a bill                | `accounting-bills:create`      |
| PUT    | `/:id`                             | Update a bill                | `accounting-bills:update`      |
| DELETE | `/:id`                             | Delete a bill                | `accounting-bills:delete`      |
| POST   | `/:id/submit`                      | Submit a bill                | `accounting-bills:update`      |
| POST   | `/:id/approve`                     | Approve a bill               | `accounting-bills:approve`     |
| POST   | `/:id/pay`                         | Pay a bill                   | `accounting-bills:post`        |
| GET    | `/:id/pdf`                         | Bill PDF                     | `accounting:read` *(inferred)* |
| POST   | `/:id/void`                        | Void a bill                  | `accounting-bills:void`        |
| POST   | `/:id/preview`                     | Preview posting              | `accounting-bills:read`        |
| POST   | `/:id/legacy-amend`                | Legacy amend                 | `accounting-bills:update`      |
| POST   | `/:id/docstatus/submit`            | Submit doc-status            | `accounting-bills:post`        |
| POST   | `/:id/docstatus/cancel`            | Cancel doc-status            | `accounting-bills:void`        |
| POST   | `/:id/amend`                       | Amend a bill                 | `accounting-bills:update`      |
| POST   | `/:id/returns`                     | Create a bill return         | `accounting-bills:create`      |
| POST   | `/debit-notes/:id/issue`           | Issue a debit note           | `accounting-bills:post`        |
| POST   | `/debit-notes/:id/allocate`        | Allocate a debit note        | `accounting-bills:update`      |
| POST   | `/debit-notes/:id/refund-residue`  | Refund debit-note residue    | `accounting-bills:update`      |
| POST   | `/debit-note-allocations/:id/void` | Void a debit-note allocation | `accounting-bills:void`        |

## budgets.controller.ts

Base: `api/accounting/budgets` — inferred permissions.

| Method | Path             | Purpose                  | Permission                       |
| ------ | ---------------- | ------------------------ | -------------------------------- |
| GET    | `/consolidated`  | Consolidated budget view | `accounting:read` *(inferred)*   |
| GET    | `/`              | List budgets             | `accounting:read` *(inferred)*   |
| GET    | `/:id`           | Get one budget           | `accounting:read` *(inferred)*   |
| GET    | `/:id/balance`   | Budget balance           | `accounting:read` *(inferred)*   |
| GET    | `/:id/vs-actual` | Budget vs actual         | `accounting:read` *(inferred)*   |
| POST   | `/`              | Create a budget          | `accounting:create` *(inferred)* |
| PUT    | `/:id`           | Update a budget          | `accounting:update` *(inferred)* |
| DELETE | `/:id`           | Delete a budget          | `accounting:delete` *(inferred)* |
| POST   | `/:id/submit`    | Submit a budget          | `accounting:create` *(inferred)* |
| POST   | `/:id/activate`  | Activate a budget        | `accounting:create` *(inferred)* |
| POST   | `/:id/close`     | Close a budget           | `accounting:create` *(inferred)* |

## categories.controller.ts

Base: `api/accounting/categories`

| Method | Path   | Purpose           | Permission                       |
| ------ | ------ | ----------------- | -------------------------------- |
| GET    | `/`    | List categories   | `accounting:read` *(inferred)*   |
| GET    | `/:id` | Get one category  | `accounting:read` *(inferred)*   |
| POST   | `/`    | Create a category | `accounting:create` *(inferred)* |
| PUT    | `/:id` | Update a category | `accounting:update` *(inferred)* |
| DELETE | `/:id` | Delete a category | `accounting:delete` *(inferred)* |

## chart-import.controller.ts

Base: `api/accounting/chart-import`

| Method | Path                       | Purpose                 | Permission                       |
| ------ | -------------------------- | ----------------------- | -------------------------------- |
| GET    | `/templates`               | List chart templates    | `accounting:read` *(inferred)*   |
| GET    | `/templates/:name/preview` | Preview a template      | `accounting:read` *(inferred)*   |
| POST   | `/parse`                   | Parse an uploaded chart | `accounting:create` *(inferred)* |
| POST   | `/validate`                | Validate a parsed chart | `accounting:create` *(inferred)* |
| POST   | `/execute`                 | Execute import          | `accounting:create` *(inferred)* |
| GET    | `/batches`                 | List import batches     | `accounting:read` *(inferred)*   |
| POST   | `/batches/:id/undo`        | Undo an import batch    | `accounting:create` *(inferred)* |
| GET    | `/download-template`       | Download a CSV template | `accounting:read` *(inferred)*   |

## contacts.controller.ts

Base: `api/accounting/contacts`

| Method | Path                    | Purpose              | Permission                       |
| ------ | ----------------------- | -------------------- | -------------------------------- |
| GET    | `/`                     | List contacts        | `accounting:read` *(inferred)*   |
| GET    | `/:id`                  | Get one contact      | `accounting:read` *(inferred)*   |
| POST   | `/`                     | Create a contact     | `accounting:create` *(inferred)* |
| PUT    | `/:id`                  | Update a contact     | `accounting:update` *(inferred)* |
| DELETE | `/:id`                  | Delete a contact     | `accounting:delete` *(inferred)* |
| POST   | `/:id/link-crm-contact` | Link a CRM contact   | `accounting:create` *(inferred)* |
| DELETE | `/:id/link-crm-contact` | Unlink a CRM contact | `accounting:delete` *(inferred)* |

## cost-centers.controller.ts

Base: `api/accounting/cost-centers`

| Method | Path        | Purpose              | Permission                       |
| ------ | ----------- | -------------------- | -------------------------------- |
| GET    | `/`         | List cost centers    | `accounting:read` *(inferred)*   |
| GET    | `/tree`     | Cost-center tree     | `accounting:read` *(inferred)*   |
| GET    | `/:id`      | Get one cost center  | `accounting:read` *(inferred)*   |
| POST   | `/`         | Create a cost center | `accounting:create` *(inferred)* |
| PUT    | `/:id`      | Update a cost center | `accounting:update` *(inferred)* |
| POST   | `/:id/move` | Move within the tree | `accounting:create` *(inferred)* |
| DELETE | `/:id`      | Delete a cost center | `accounting:delete` *(inferred)* |

## debt-settings.controller.ts

Base: `api/accounting/debt-settings`

| Method | Path | Purpose              | Permission                                |
| ------ | ---- | -------------------- | ----------------------------------------- |
| GET    | `/`  | Get debt settings    | `accounting:admin` or `accounting:config` |
| PUT    | `/`  | Update debt settings | `accounting:admin` or `accounting:config` |

## debts.controller.ts

Base: `api/accounting/debts` (maps to the `accounting-debts` module).

| Method | Path | Purpose                          | Permission                |
| ------ | ---- | -------------------------------- | ------------------------- |
| POST   | `/`  | Create a debt (other-receivable) | `accounting-debts:create` |
| GET    | `/`  | List debts                       | `accounting-debts:read`   |

## expense-claim-types.controller.ts

Base: `api/accounting/expense-claim-types`

| Method | Path   | Purpose             | Permission                       |
| ------ | ------ | ------------------- | -------------------------------- |
| GET    | `/`    | List claim types    | `accounting:read` *(inferred)*   |
| GET    | `/:id` | Get one claim type  | `accounting:read` *(inferred)*   |
| POST   | `/`    | Create a claim type | `accounting:create` *(inferred)* |
| PATCH  | `/:id` | Update a claim type | `accounting:update` *(inferred)* |
| DELETE | `/:id` | Delete a claim type | `accounting:delete` *(inferred)* |

## expenses.controller.ts

Base: `api/accounting/expenses` — explicit `@RequirePermissions` on mutations.

| Method | Path                  | Purpose                     | Permission                     |
| ------ | --------------------- | --------------------------- | ------------------------------ |
| GET    | `/`                   | List expenses               | `accounting:read` *(inferred)* |
| GET    | `/unpaid-report`      | Unpaid expenses report      | `accounting:read` *(inferred)* |
| GET    | `/summary`            | Expense summary             | `accounting:read` *(inferred)* |
| GET    | `/:id`                | Get one expense             | `accounting:read` *(inferred)* |
| POST   | `/`                   | Create an expense           | `accounting-expenses:create`   |
| PUT    | `/:id`                | Update an expense           | `accounting-expenses:update`   |
| DELETE | `/:id`                | Delete an expense           | `accounting-expenses:delete`   |
| POST   | `/:id/submit`         | Submit an expense           | `accounting-expenses:update`   |
| POST   | `/:id/sanction`       | Sanction/approve an expense | `accounting-expenses:approve`  |
| POST   | `/:id/record-payment` | Record payment              | `accounting-expenses:post`     |
| GET    | `/:id/pdf`            | Expense PDF                 | `accounting:read` *(inferred)* |
| POST   | `/:id/void`           | Void an expense             | `accounting-expenses:void`     |

## financial-reports.controller.ts

Base: `api/accounting/reports` — no per-route `@RequirePermissions`; inferred.

| Method | Path                           | Purpose                            | Permission                       |
| ------ | ------------------------------ | ---------------------------------- | -------------------------------- |
| GET    | `/general-ledger`              | General ledger report              | `accounting:read` *(inferred)*   |
| GET    | `/trial-balance`               | Trial balance (v2)                 | `accounting:read` *(inferred)*   |
| GET    | `/profit-and-loss`             | Income statement                   | `accounting:read` *(inferred)*   |
| GET    | `/cost-center-profit-and-loss` | Cost-center P\&L                   | `accounting:read` *(inferred)*   |
| GET    | `/balance-sheet`               | Balance sheet                      | `accounting:read` *(inferred)*   |
| GET    | `/aged-receivables`            | Aged receivables                   | `accounting:read` *(inferred)*   |
| GET    | `/aged-payables`               | Aged payables                      | `accounting:read` *(inferred)*   |
| GET    | `/cash-flow`                   | Cash flow statement                | `accounting:read` *(inferred)*   |
| POST   | `/queue-export`                | Queue async CSV export             | `accounting:create` *(inferred)* |
| POST   | `/freeze-as-of`                | Freeze reports for a closed period | `accounting:create` *(inferred)* |
| GET    | `/account-ledger`              | Single-account transaction history | `accounting:read` *(inferred)*   |

## integration.controller.ts

Base: `api/accounting/integrations`

| Method | Path       | Purpose                                     | Permission                       |
| ------ | ---------- | ------------------------------------------- | -------------------------------- |
| POST   | `/journal` | Post a journal from an external integration | `accounting:create` *(inferred)* |

## invoices.controller.ts

Base: `api/accounting/invoices` — explicit `@RequirePermissions` on mutations.

| Method | Path                                          | Purpose                       | Permission                     |
| ------ | --------------------------------------------- | ----------------------------- | ------------------------------ |
| GET    | `/stats`                                      | Invoice stats                 | `accounting:read` *(inferred)* |
| GET    | `/receivables-summary`                        | Receivables summary           | `accounting:read` *(inferred)* |
| GET    | `/`                                           | List invoices                 | `accounting:read` *(inferred)* |
| GET    | `/:id/payments`                               | Payments for an invoice       | `accounting:read` *(inferred)* |
| GET    | `/:id`                                        | Get one invoice               | `accounting:read` *(inferred)* |
| POST   | `/`                                           | Create an invoice             | `accounting-invoices:create`   |
| PUT    | `/:id`                                        | Update an invoice             | `accounting-invoices:update`   |
| DELETE | `/:id`                                        | Delete an invoice             | `accounting-invoices:delete`   |
| POST   | `/:id/submit`                                 | Submit an invoice             | `accounting-invoices:update`   |
| POST   | `/:id/returns`                                | Create an invoice return      | `accounting-invoices:create`   |
| POST   | `/:id/allocate`                               | Allocate payment              | `accounting-invoices:update`   |
| POST   | `/credit-note-allocations/:allocationId/void` | Void a credit-note allocation | `accounting-invoices:void`     |
| POST   | `/:id/refund-residue`                         | Refund credit residue         | `accounting-invoices:update`   |
| POST   | `/:id/send`                                   | Send an invoice               | `accounting-invoices:send`     |
| GET    | `/:id/pdf`                                    | Invoice PDF                   | `accounting:read` *(inferred)* |
| POST   | `/:id/void`                                   | Void an invoice               | `accounting-invoices:void`     |
| POST   | `/:id/preview`                                | Preview posting               | `accounting-invoices:read`     |
| POST   | `/:id/recompute-outstanding`                  | Recompute outstanding         | `accounting-invoices:update`   |
| POST   | `/:id/docstatus/submit`                       | Submit doc-status             | `accounting-invoices:post`     |
| POST   | `/:id/docstatus/cancel`                       | Cancel doc-status             | `accounting-invoices:void`     |
| POST   | `/:id/amend`                                  | Amend an invoice              | `accounting-invoices:update`   |
| POST   | `/:id/mark-followed-up`                       | Mark followed-up              | `accounting-invoices:update`   |
| POST   | `/:id/snooze-follow-up`                       | Snooze follow-up              | `accounting-invoices:update`   |

## journal.controller.ts

Base: `api/accounting/journal-entries` — explicit `@RequirePermissions` on mutations.

| Method | Path            | Purpose                    | Permission                     |
| ------ | --------------- | -------------------------- | ------------------------------ |
| GET    | `/`             | List journal entries       | `accounting:read` *(inferred)* |
| GET    | `/:id`          | Get one journal entry      | `accounting:read` *(inferred)* |
| POST   | `/`             | Create a journal entry     | `accounting-journal:create`    |
| PUT    | `/:id`          | Update a journal entry     | `accounting-journal:update`    |
| DELETE | `/:id`          | Delete a journal entry     | `accounting-journal:delete`    |
| POST   | `/:id/preview`  | Preview posting            | `accounting-journal:read`      |
| GET    | `/:id/postings` | List postings for an entry | `accounting:read` *(inferred)* |
| POST   | `/:id/post`     | Post a journal entry       | `accounting-journal:post`      |
| POST   | `/:id/reverse`  | Reverse a journal entry    | `accounting-journal:reverse`   |
| POST   | `/:id/submit`   | Submit for approval        | `accounting-journal:update`    |

## journal-entry-template.controller.ts

Base: `api/accounting/journal-entries/templates`

| Method | Path               | Purpose                                   | Permission                       |
| ------ | ------------------ | ----------------------------------------- | -------------------------------- |
| GET    | `/`                | List templates                            | `accounting:read` *(inferred)*   |
| GET    | `/:id`             | Get one template                          | `accounting:read` *(inferred)*   |
| POST   | `/`                | Create a template                         | `accounting:create` *(inferred)* |
| PUT    | `/:id`             | Update a template                         | `accounting:update` *(inferred)* |
| DELETE | `/:id`             | Delete a template                         | `accounting:delete` *(inferred)* |
| POST   | `/:id/instantiate` | Instantiate a template into a draft entry | `accounting:create` *(inferred)* |

## opening-balances.controller.ts

Base: `api/accounting/opening-balances`

| Method | Path                       | Purpose                         | Permission                       |
| ------ | -------------------------- | ------------------------------- | -------------------------------- |
| POST   | `/validate`                | Validate an opening-balance set | `accounting:create` *(inferred)* |
| POST   | `/`                        | Create an opening-balance set   | `accounting:create` *(inferred)* |
| GET    | `/template`                | Download import template        | `accounting:read` *(inferred)*   |
| POST   | `/import`                  | Import opening balances         | `accounting:create` *(inferred)* |
| POST   | `/:id/validate`            | Validate a saved set            | `accounting:create` *(inferred)* |
| POST   | `/:id/freeze`              | Freeze a set                    | `accounting:create` *(inferred)* |
| POST   | `/:id/post`                | Post a set                      | `accounting:create` *(inferred)* |
| POST   | `/:id/submit-for-approval` | Submit a set for approval       | `accounting:create` *(inferred)* |
| GET    | `/:id`                     | Get one set                     | `accounting:read` *(inferred)*   |

## payment-reconciliation.controller.ts

Base: `api/accounting/payment-reconciliation`

| Method | Path                     | Purpose                         | Permission                       |
| ------ | ------------------------ | ------------------------------- | -------------------------------- |
| GET    | `/unreconciled-payments` | List unreconciled payments      | `accounting:read` *(inferred)*   |
| GET    | `/outstanding-invoices`  | List outstanding invoices       | `accounting:read` *(inferred)*   |
| POST   | `/auto-match`            | Auto-match payments to invoices | `accounting:create` *(inferred)* |
| POST   | `/reconcile`             | Reconcile a payment             | `accounting:create` *(inferred)* |

## payment-term.controller.ts

Base: `api/accounting/payment-terms`

| Method | Path   | Purpose               | Permission                                |
| ------ | ------ | --------------------- | ----------------------------------------- |
| GET    | `/`    | List payment terms    | `accounting:read` *(inferred)*            |
| GET    | `/:id` | Get one payment term  | `accounting:read` *(inferred)*            |
| POST   | `/`    | Create a payment term | `accounting:admin` or `accounting:config` |
| PUT    | `/:id` | Update a payment term | `accounting:admin` or `accounting:config` |
| DELETE | `/:id` | Delete a payment term | `accounting:admin` or `accounting:config` |

## payments.controller.ts

Base: `api/accounting/payments` — explicit `@RequirePermissions` on mutations.

| Method | Path                   | Purpose                               | Permission                     |
| ------ | ---------------------- | ------------------------------------- | ------------------------------ |
| GET    | `/cashflow-trend`      | Cash-flow trend                       | `accounting:read` *(inferred)* |
| GET    | `/`                    | List payments                         | `accounting:read` *(inferred)* |
| GET    | `/:id`                 | Get one payment                       | `accounting:read` *(inferred)* |
| POST   | `/`                    | Create a payment                      | `accounting-payments:create`   |
| PUT    | `/:id`                 | Update a payment                      | `accounting-payments:update`   |
| DELETE | `/:id`                 | Delete a payment                      | `accounting-payments:delete`   |
| POST   | `/:id/post`            | Post a payment                        | `accounting-payments:post`     |
| POST   | `/:id/allocate`        | Allocate a payment                    | `accounting-payments:update`   |
| POST   | `/:id/get-outstanding` | Get outstanding for allocation        | `accounting-payments:read`     |
| GET    | `/:id/deductions`      | List deductions                       | `accounting:read` *(inferred)* |
| POST   | `/:id/preview`         | Preview posting                       | `accounting-payments:read`     |
| POST   | `/:id/create-advance`  | Create a staff advance from a payment | `accounting-payments:create`   |
| GET    | `/:id/pdf`             | Payment/receipt PDF                   | `accounting:read` *(inferred)* |
| POST   | `/:id/send-receipt`    | Send a receipt                        | `accounting-payments:send`     |
| POST   | `/:id/reverse`         | Reverse a payment                     | `accounting-payments:reverse`  |

## period-reopen.controller.ts

Base: `api/accounting/accounting-periods` (period-reopen request workflow).

| Method | Path                              | Purpose                  | Permission                       |
| ------ | --------------------------------- | ------------------------ | -------------------------------- |
| POST   | `/:id/request-reopen`             | Request a period reopen  | `accounting:create` *(inferred)* |
| POST   | `/reopen-requests/:reqId/approve` | Approve a reopen request | `accounting:create` *(inferred)* |
| POST   | `/reopen-requests/:reqId/reject`  | Reject a reopen request  | `accounting:create` *(inferred)* |
| GET    | `/reopen-requests`                | List reopen requests     | `accounting:read` *(inferred)*   |

## petty-cash.controller.ts

Base: `api/accounting/petty-cash`

| Method | Path                      | Purpose               | Permission                       |
| ------ | ------------------------- | --------------------- | -------------------------------- |
| GET    | `/`                       | List petty-cash funds | `accounting:read` *(inferred)*   |
| GET    | `/:id`                    | Get one fund          | `accounting:read` *(inferred)*   |
| POST   | `/`                       | Create a fund         | `accounting:create` *(inferred)* |
| PUT    | `/:id`                    | Update a fund         | `accounting:update` *(inferred)* |
| POST   | `/:id/close`              | Close a fund          | `accounting:create` *(inferred)* |
| POST   | `/:fundId/disburse`       | Disburse from a fund  | `accounting:create` *(inferred)* |
| GET    | `/:fundId/disbursements`  | List disbursements    | `accounting:read` *(inferred)*   |
| POST   | `/:fundId/replenish`      | Replenish a fund      | `accounting:create` *(inferred)* |
| GET    | `/:fundId/replenishments` | List replenishments   | `accounting:read` *(inferred)*   |

## print-settings.controller.ts

Base: `api/accounting/print-settings`

| Method | Path    | Purpose               | Permission                       |
| ------ | ------- | --------------------- | -------------------------------- |
| GET    | `/`     | Get print settings    | `accounting:read` *(inferred)*   |
| PATCH  | `/`     | Update print settings | `accounting:update` *(inferred)* |
| DELETE | `/logo` | Remove the logo       | `accounting:delete` *(inferred)* |

## quotations.controller.ts

Base: `api/accounting/quotations`

| Method | Path                | Purpose                       | Permission                       |
| ------ | ------------------- | ----------------------------- | -------------------------------- |
| POST   | `/from-opportunity` | Create from a CRM opportunity | `accounting:create` *(inferred)* |
| GET    | `/`                 | List quotations               | `accounting:read` *(inferred)*   |
| GET    | `/stats`            | Quotation stats               | `accounting:read` *(inferred)*   |
| GET    | `/:id`              | Get one quotation             | `accounting:read` *(inferred)*   |
| POST   | `/`                 | Create a quotation            | `accounting:create` *(inferred)* |
| PUT    | `/:id`              | Update a quotation            | `accounting:update` *(inferred)* |
| DELETE | `/:id`              | Delete a quotation            | `accounting:delete` *(inferred)* |
| POST   | `/:id/submit`       | Submit a quotation            | `accounting:create` *(inferred)* |
| POST   | `/:id/approve`      | Approve a quotation           | `accounting:create` *(inferred)* |
| POST   | `/:id/reject`       | Reject a quotation            | `accounting:create` *(inferred)* |
| POST   | `/:id/send`         | Send a quotation              | `accounting:create` *(inferred)* |
| POST   | `/:id/resend`       | Resend a quotation            | `accounting:create` *(inferred)* |
| POST   | `/:id/accept`       | Mark accepted                 | `accounting:create` *(inferred)* |
| POST   | `/:id/mark-lost`    | Mark lost                     | `accounting:create` *(inferred)* |
| POST   | `/:id/reopen`       | Reopen a quotation            | `accounting:create` *(inferred)* |
| POST   | `/:id/revise`       | Revise a quotation            | `accounting:create` *(inferred)* |
| POST   | `/:id/convert`      | Convert to an invoice         | `accounting:create` *(inferred)* |
| GET    | `/:id/pdf`          | Quotation PDF                 | `accounting:read` *(inferred)*   |

## recurring-transactions.controller.ts

Base: `api/accounting/recurring-transactions`

| Method | Path            | Purpose                          | Permission                       |
| ------ | --------------- | -------------------------------- | -------------------------------- |
| GET    | `/`             | List recurring transactions      | `accounting:read` *(inferred)*   |
| GET    | `/:id`          | Get one                          | `accounting:read` *(inferred)*   |
| POST   | `/`             | Create one                       | `accounting:create` *(inferred)* |
| PUT    | `/:id`          | Update one                       | `accounting:update` *(inferred)* |
| DELETE | `/:id`          | Delete one                       | `accounting:delete` *(inferred)* |
| POST   | `/:id/pause`    | Pause one                        | `accounting:create` *(inferred)* |
| POST   | `/:id/resume`   | Resume one                       | `accounting:create` *(inferred)* |
| POST   | `/:id/cancel`   | Cancel one                       | `accounting:create` *(inferred)* |
| POST   | `/:id/generate` | Generate the next occurrence now | `accounting:create` *(inferred)* |

## reminder-settings.controller.ts

Base: `api/accounting/reminder-settings`

| Method | Path       | Purpose                             | Permission                                |
| ------ | ---------- | ----------------------------------- | ----------------------------------------- |
| GET    | `/`        | Get reminder settings               | `accounting:admin` or `accounting:config` |
| PUT    | `/`        | Update reminder settings            | `accounting:admin` or `accounting:config` |
| POST   | `/run-now` | Run the overdue-reminder digest now | `accounting:admin` or `accounting:config` |

## report-presets.controller.ts

Base: `api/accounting/reports/presets` — no per-route `@RequirePermissions`; inferred.

| Method | Path                     | Purpose                                           | Permission                       |
| ------ | ------------------------ | ------------------------------------------------- | -------------------------------- |
| GET    | `/`                      | List presets visible to the user                  | `accounting:read` *(inferred)*   |
| POST   | `/`                      | Create a preset                                   | `accounting:create` *(inferred)* |
| PATCH  | `/:id`                   | Update a preset (owner only, enforced in service) | `accounting:update` *(inferred)* |
| DELETE | `/:id`                   | Delete a preset (owner only)                      | `accounting:delete` *(inferred)* |
| GET    | `/schedules`             | List schedules                                    | `accounting:read` *(inferred)*   |
| POST   | `/schedules`             | Create a schedule                                 | `accounting:create` *(inferred)* |
| PATCH  | `/schedules/:id`         | Update a schedule                                 | `accounting:update` *(inferred)* |
| DELETE | `/schedules/:id`         | Delete a schedule                                 | `accounting:delete` *(inferred)* |
| POST   | `/schedules/:id/run-now` | Dispatch a schedule once now                      | `accounting:create` *(inferred)* |

## tax-code.controller.ts

Base: `api/accounting/tax-codes`

| Method | Path   | Purpose           | Permission                                |
| ------ | ------ | ----------------- | ----------------------------------------- |
| GET    | `/`    | List tax codes    | `accounting:read` *(inferred)*            |
| GET    | `/:id` | Get one tax code  | `accounting:read` *(inferred)*            |
| POST   | `/`    | Create a tax code | `accounting:admin` or `accounting:config` |
| PUT    | `/:id` | Update a tax code | `accounting:admin` or `accounting:config` |
| DELETE | `/:id` | Delete a tax code | `accounting:admin` or `accounting:config` |

## terms-templates.controller.ts

Base: `api/accounting/terms-templates`

| Method | Path   | Purpose                 | Permission                       |
| ------ | ------ | ----------------------- | -------------------------------- |
| GET    | `/`    | List terms templates    | `accounting:read` *(inferred)*   |
| POST   | `/`    | Create a terms template | `accounting:create` *(inferred)* |
| PUT    | `/:id` | Update a terms template | `accounting:update` *(inferred)* |
| DELETE | `/:id` | Delete a terms template | `accounting:delete` *(inferred)* |

## withholding-code.controller.ts

Base: `api/accounting/withholding-codes`

| Method | Path   | Purpose                   | Permission                                |
| ------ | ------ | ------------------------- | ----------------------------------------- |
| GET    | `/`    | List withholding codes    | `accounting:read` *(inferred)*            |
| GET    | `/:id` | Get one withholding code  | `accounting:read` *(inferred)*            |
| POST   | `/`    | Create a withholding code | `accounting:admin` or `accounting:config` |
| PUT    | `/:id` | Update a withholding code | `accounting:admin` or `accounting:config` |
| DELETE | `/:id` | Delete a withholding code | `accounting:admin` or `accounting:config` |

## workspace-settings.controller.ts

Base: `api/accounting/workspace-settings`

| Method | Path | Purpose                       | Permission                                |
| ------ | ---- | ----------------------------- | ----------------------------------------- |
| GET    | `/`  | Get workspace (book) settings | `accounting:read` *(inferred)*            |
| PATCH  | `/`  | Update workspace settings     | `accounting:config` or `accounting:admin` |

## year-end-close.controller.ts

Base: `api/accounting/year-end-close`

| Method | Path      | Purpose                     | Permission                       |
| ------ | --------- | --------------------------- | -------------------------------- |
| GET    | `/`       | Get year-end-close status   | `accounting:read` *(inferred)*   |
| POST   | `/`       | Create/run a year-end close | `accounting:create` *(inferred)* |
| POST   | `/submit` | Submit the year-end close   | `accounting:create` *(inferred)* |

***

## Notes & caveats

* **Inferred permissions are effective, not declared.** Endpoints marked
  *(inferred)* carry no `@RequirePermissions` decorator; the permission shown is
  what the global `PermissionsGuard` computes from the route base + HTTP method.
  If `ROUTE_TO_MODULE` changes, these change with it. Confirm against
  `src/auth/permissions.guard.ts` when precision matters.
* **Platform admins bypass** the permission check entirely (`Admin`,
  `SuperAdmin`, `Super Admin` — exact, suffix-free role names only).
* **`accounting/bridge`** is the one accounting controller **not** under
  `api/accounting`; its four POST intents carry explicit `accounting:write`,
  while the `source-links` GET has no decorator and the base path is not in
  `ROUTE_TO_MODULE` (verify its effective guard before relying on it).
* Several controllers also enforce ownership/scope **inside the service** beyond
  the guard (e.g. report presets are owner-only for mutations; bills/invoices
  run a maker-checker doc-status state machine). Those checks are documented in
  the respective service docs, not encoded in the permission slug.
