137 endpoints across 31 groups.
Each endpoint's auth requirement is shown on its row.
/api/v1/businesses/registerFirst-user business registration with TaxCore verification. Validates the supplied PFX/PAC/UID against V-SDC, creates the business, promotes the calling user to Administrator, and returns fresh JWT tokens. No role guard — the caller has no businessId yet./api/v1/businessesCreate an additional business. Used after the first business is registered. TIN must be unique across all businesses./api/v1/businessesReturns the single business the authenticated user belongs to. Users without a businessId receive an empty list. Returned as an APIResponse list shape for FE consistency./api/v1/businesses/:businessIdPartially update non-credential business settings — name, address fields, currencyCode, and the demo Xero tenant id used for Training routing. At least one field must be provided./api/v1/businesses/:businessId/taxcore-configUpdate V-SDC credentials for a business. multipart/form-data — every field optional, at least one required. When `pac` is supplied, the server verifies it against V-SDC `/status` (mTLS) before persisting. When `businessUID` is also supplied, `status.uid` is cross-checked against it./api/v1/businesses/:businessId/default-certificateRegistration wizard step 2 shortcut. Creates a Certificate row against the business's default Location and mirrors the same fields into the legacy `Businesses.Vsdc*` columns so the consumer's fallback path keeps working./api/v1/auth/loginValidate credentials and issue a JWT access token plus an httpOnly refresh-token cookie. Returns the user and businessId (null if onboarding incomplete). Constant-time dummy hash verify on unknown emails to prevent timing-based enumeration./api/v1/auth/registerCreate the first user account. Hashes the password with argon2, creates a Users row with roles=[] and BusinessId=NULL, and fires AUTH_REGISTER + fire-and-forget welcome email. No tokens are issued — the client is expected to redirect to login./api/v1/auth/refreshRotate the refresh token. Reads the httpOnly refreshToken cookie (or body fallback for native clients), verifies RS256 signature, checks typ='refresh', re-fetches the user to detect deactivation and post-iat password changes, and issues a fresh access+refresh pair./api/v1/auth/logoutClear the refresh-token cookie. Idempotent — returns 200 even when the cookie is absent or malformed. Decodes (does not verify) the cookie to write an AUTH_LOGOUT audit event with the userId/businessId when present./api/v1/auth/setup-statusReturns whether any user has been registered yet — used by the frontend to gate the registration form vs. invite-only flow./api/v1/auth/register-invitedComplete registration for a user who received an invite link. Validates the invite token in Redis (invite:{token}), creates the user with the invite's roles and organizationId, deletes the token (single-use), and fires AUTH_REGISTER + welcome email./api/v1/auth/change-passwordVerify the caller's current password, hash the new one, and issue a fresh access+refresh token pair. Sets Users.PasswordChangedAt to invalidate all existing refresh tokens. Rate-limited via 'default' bucket./api/v1/auth/forgot-passwordRequest a 6-digit OTP reset code. Always returns 200 with a generic message — silently no-ops for unknown emails (anti-enumeration). Generates a code, SHA-256-hashes it into PasswordResetTokens, and XADDs the raw code to the auth:password-reset Redis Stream for the email worker. Rate-limited via 'default' bucket./api/v1/auth/reset-passwordConfirm a password reset. Validates the OTP against the stored SHA-256 hash, checks expiry, deletes the token (single-use), hashes the new password with argon2, and sets PasswordChangedAt to invalidate every existing refresh token. All failure modes return the same RESET_TOKEN_INVALID code. Rate-limited via 'default' bucket./api/v1/usersList all users belonging to the authenticated caller's business (businessId taken from the JWT, not the URL)./api/v1/usersCreate a new user inside the caller's business. Argon2-hashes the password, derives username from the email local part, inserts into Users + UserRoles, and fires USER_CREATED + welcome email (fire-and-forget)./api/v1/users/:userIdPartial update of a user inside the caller's business. Patch may include roles, isActive, firstName, lastName, email, and/or password. Admin-removal safeguards prevent removing your own Administrator role or removing the last administrator. Fires USER_DEACTIVATED on isActive=false, USER_UPDATED otherwise./api/v1/invitesCreate a one-time invite. Generates a UUID token, stores `invite:{uuid}` in Redis with TTL INVITE_TTL_SECONDS (default 72h) holding { email, roles, organizationId=businessId, invitedBy=actorUserId }, and sends an invite email. On email failure the Redis token is deleted to avoid orphans./api/v1/invites/:tokenValidate an invite token and return its metadata so the registration page can pre-fill the email and show the role(s). Public — invite recipients have no account yet./api/v1/businesses/:businessId/audit-eventsPaginated audit log for a business. Supports filters by eventType (comma-separated → IN clause), resourceType, resourceId, dateFrom/dateTo (epoch ms), and a search term (matches ResourceId or ActorEmail with %term%). Returns items + total + stats (fiscalised/failed/actors counts). Fires VIEW_AUDIT_LOG fire-and-forget after the response is sent./api/v1/businesses/:businessId/audit-events/exportStream all matching audit events as CSV (Content-Disposition: attachment; filename=audit-log-<businessId>-<yyyy-mm-dd>.csv). Same filters as the list endpoint; Administrator only (requireAdmin). Registered before /:id so Express does not treat 'export' as an id param./api/v1/businesses/:businessId/audit-events/:idFetch a single audit log entry by its BIGINT AuditLogId. Scoped to the business./api/v1/businesses/:businessId/api-keysGenerate a new static API key for on-premise agents (Sage 100/300/200 Evolution, Amicus). Stores SHA-256(rawKey) + an 8-char prefix; returns the raw 64-hex-char key exactly once. Optional scope binds the key to one AccountingProviderType; optional locationId scopes it to one Location (validated against the business). Fires API_KEY_CREATED./api/v1/businesses/:businessId/api-keysList all API keys for a business (active + revoked). Raw secret is never returned — only label, prefix, scope, locationId, createdAt, lastUsedAt, revokedAt./api/v1/businesses/:businessId/api-keys/:keyIdSoft-revoke an API key (sets RevokedAt). Idempotent — re-calling on an already-revoked key is a no-op. Fires API_KEY_REVOKED on first revocation./api/v1/businesses/:businessId/locationsCreate a new Location for the business. locationType defaults server-side; isDefault optionally promotes this Location to the business's default (existing default cleared atomically)./api/v1/businesses/:businessId/locations/:locationIdFetch a single Location scoped to the business./api/v1/businesses/:businessId/locations/:locationIdPartial update of a Location. isDefault=true is routed through the dedicated setDefault path so the previous default is cleared atomically. status='inactive' soft-disables the Location./api/v1/businesses/:businessId/locations/:locationIdHard-delete a Location (or soft-delete depending on repository impl — see ILocationRepository.remove)./api/v1/businesses/:businessId/locations/:locationId/certificatesList all Certificates registered against a Location. Returns the PUBLIC projection — VSDC credentials (PFX filename, password, PAC) are stripped; `hasCertificate: boolean` indicates cert presence./api/v1/businesses/:businessId/locations/:locationId/certificatesUpload a new V-SDC certificate for a Location. multipart/form-data: PFX file + password + PAC + UID. The server RC2→3DES-converts the PFX, verifies the PAC against V-SDC /status, cross-checks UID against status.uid, writes the file under CERT_STORAGE_DIR/{uuid}.pfx, and persists a Certificates row. PFX bytes are cleaned up if persistence fails. PFX file max 5 MB./api/v1/businesses/:businessId/locations/:locationId/certificates/:certificateIdFetch a single Certificate (public projection — credential fields stripped)./api/v1/businesses/:businessId/locations/:locationId/certificates/:certificateIdPatch a Certificate's metadata — name, status (active|revoked), and/or isDefault. isDefault=true is routed through setLocationDefault for an atomic swap of the Location's default certificate./api/v1/businesses/:businessId/locations/:locationId/certificates/:certificateId/revokeRevoke a Certificate (sets Status='revoked'). Convenience action — equivalent to PATCH with status='revoked'./api/v1/businesses/:businessId/locations/:locationId/certificates/:certificateIdDelete a Certificate. Removes the DB row and unlinks the PFX file from CERT_STORAGE_DIR (fire-and-forget; unlink errors swallowed)./api/v1/mcp/connectionReturn the public MCP server URL, transport, and tool list. Does NOT require an MCP token — only a valid JWT. Used by the app to display connection details before the user creates their first token./api/v1/mcp/tokensIssue a 90-day RS256 personal-access token (typ='mcp') for AI agents. jti = McpTokens.McpTokenId UUID. Only SHA-256(rawToken) is persisted; the raw token is returned ONCE on creation and never again./api/v1/mcp/tokensList the caller's active MCP tokens (non-revoked, non-expired). Raw token hash is never exposed — only id, name, createdAt, lastUsedAt, expiresAt./api/v1/mcp/tokens/:tokenIdSoft-revoke an MCP token (sets RevokedAt). Idempotent — re-calling on an already-revoked token is a no-op./api/v1/mcp/agent-configReturn a Claude Desktop / mcp.json configuration snippet referencing the MCP server URL and a placeholder Authorization header. The raw token is not returned — the user must substitute the value they saved at creation time./healthLiveness probe. Returns `{ ok: true }` for a bare call. When `?services` is present (any value), the response also includes `services: { redis: boolean }` reflecting `Redis.testConnection()`. Unversioned (does NOT live under /api/v1)./.well-known/apple-app-site-associationiOS Universal Links association file. Returns a JSON payload listing the app's TeamID + bundleIdentifier under `applinks.details` with path scope `/go*`. Served unauthenticated and OUTSIDE the /api/v1 prefix — the OS fetches it directly over HTTPS. TeamID is a deploy-time placeholder./.well-known/assetlinks.jsonAndroid App Links assetlinks. Returns a JSON array delegating common.handle_all_urls to the app's android_app namespace with package_name + sha256_cert_fingerprints. Served unauthenticated and OUTSIDE the /api/v1 prefix. Package name and fingerprint are deploy-time placeholders./api/v1/businesses/:businessId/invoices/batchesPaginated list of import batches for the business. Each batch row carries row counts (imported/failed) and the originating file metadata. Registered before /:invoiceId so the literal 'batches' segment never collides with the dynamic id./api/v1/businesses/:businessId/invoices/daily-fiscal-reportServer-side aggregation of fiscalised payments inside a date window. Payment-aware — every fiscalised InvoicePayments row counts as one fiscal event and amounts come from PaymentAmount, so a multi-payment invoice with payments on different days splits correctly. Fires DAILY_FISCAL_REPORT_GENERATED (fire-and-forget) for audit traceability./api/v1/businesses/:businessId/invoicesPaginated invoice list for the business. RTK infinite-query compatible. Returns invoices in the same shape as the single-invoice GET (with embedded payments[] and line-items omitted)./api/v1/businesses/:businessId/invoices/:invoiceIdFetch a single invoice with its line items and payments[] embedded. Cross-tenant probing returns 404 since the repo filters by businessId./api/v1/businesses/:businessId/invoices/:invoiceId/paymentsList every payment under a single invoice. Returns the same payments[] array embedded on GET /invoices/:invoiceId plus per-payment fiscal data (FiscalInvoiceNumber, FiscalTimestamp, etc.). Ordered by PaymentDate ASC, CreatedAt ASC. Lets the FE refresh only the payments list after a per-payment action without re-fetching the parent invoice./api/v1/businesses/:businessId/invoices/:invoiceId/payments/:paymentIdDirect drill-down for a single InvoicePayments row. Returns the same InvoicePaymentDTO shape that appears inside the parent invoice's payments[] array. Returns 404 when the payment doesn't belong to the (invoiceId, businessId) pair — protects against cross-tenant or cross-invoice probing./api/v1/businesses/:businessId/invoices/reprocess-unmappedRe-evaluates every invoice blocked for MISSING_TAX_MAPPING against the current active TaxRateMappings. For each invoice whose codes are now fully mapped: updates line-item TaxLabel/TaxRatePercent in place, strips MISSING_TAX_MAPPING from FiscalisationBlockReasons, and sets EligibleForFiscalisation = 1. Idempotent — already-eligible invoices are untouched./api/v1/businesses/:businessId/invoices/cancel-bulkBulk cancel up to MAX_PAYMENTS_PER_FISCALISATION_JOB fiscalised payments in one job. Builds a cancellation row per valid payment, then dispatches every freshly-inserted cancellation in a single FiscalisationJob so the client polls one jobId. Invalid IDs come back in blocked[]; IDs whose original already has a cancellation in flight come back in inFlight[]. Fires INVOICE_PAYMENT_CANCELLATION_REQUESTED audit per dispatched payment./api/v1/businesses/:businessId/invoices/:invoiceId/payments/:paymentId/cancelCancel a single fiscalised payment. Issues a cancellation row (Sale → Refund / Refund → Sale) with the same line items but BuyerTin set to the seller's TIN and ReferentDocumentNumber pointing back at the original SDC fiscal number, then dispatches it through the standard fiscalisation pipeline. Poll the returned jobId — on success the original payment is marked cancelled by the consumer. Fires INVOICE_PAYMENT_CANCELLATION_REQUESTED audit./api/v1/businesses/:businessId/invoices/:invoiceId/payments/:paymentId/copyPer-payment Copy. Inserts a new Invoices row with InvoiceType='COPY', CopiesInvoiceId pointing at the source invoice, totals scaled to the source payment's amount, plus a synthetic InvoicePayments row carrying CopiesPaymentId. Source payment's SDC FiscalInvoiceNumber is the V-SDC referent on the new submission; TransactionType is preserved (Copy of Sale → Sale, Copy of Refund → Refund). One-time per source payment (retry of a failed copy allowed). Immediately dispatches for fiscalisation and fires INVOICE_COPY_REQUESTED audit./api/v1/businesses/:businessId/invoices/:invoiceId/payments/:paymentId/refundPer-payment Refund. Inserts a new Invoices row with TransactionType='REFUND' and InvoiceType inherited from the source (Normal → Normal Refund, Advance → Advance Refund), RefundsInvoiceId pointing at the source invoice, totals scaled to the source payment's amount, plus a synthetic InvoicePayments row carrying RefundsPaymentId. Source payment's SDC FiscalInvoiceNumber is the V-SDC referent on the new submission. Only valid on a fiscalised SALE payment under a Normal or Advance invoice — refunding a refund is rejected. One-time per source payment (retry of a failed refund allowed). FE button is hidden on Normal/Advance (only surfaced for PROFORMA quotes); backend stays permissive so the service-layer REFUNDABLE_TYPES guard remains the source of truth. Fires INVOICE_REFUND_REQUESTED audit./api/v1/businesses/:businessId/fiscalisation-jobsTrigger fiscalisation for a batch of payments. Pre-flight eligibility validator re-reads persisted EligibleForFiscalisation / FiscalisationBlockReasons + checks the business's TaxCore config, then partitions the batch into dispatched and blocked. Blocked payments come back with per-payment reason codes so operators can fix the root cause and retry. Batch size capped at MAX_PAYMENTS_PER_FISCALISATION_JOB (currently 10)./api/v1/businesses/:businessId/fiscalisation-jobs/by-invoiceInvoice-level fiscalisation trigger for PROFORMA (quote) rows. Accepts invoiceIds[]; the service translates each to a synthetic payment (idempotent) before delegating to the standard per-payment dispatch. Same partitioned-response contract as POST /fiscalisation-jobs./api/v1/businesses/:businessId/fiscalisation-jobs/:jobIdPoll fiscalisation job status and per-payment results. The Results array carries per-payment fiscal data (FiscalInvoiceNumber, FiscalTimestamp, FiscalVerificationUrl, errorMessage, attempts). IDOR-guarded — a job belonging to another business returns 404. Add ?lite=true to omit the Results array — returns counts + status only, reducing repeated-poll payload from MB → ~200 bytes for large jobs./api/v1/businesses/:businessId/fiscal-records/:requestIdFetches a previously fiscalised invoice directly from the TaxCore V-SDC by its 8-character RequestId (stored in InvoicePayments.FiscalRequestId after fiscalisation). Proxies to TaxCore GET /api/v3/invoices/{requestId} using mTLS (cert + pfxPassword + PAC). The encryptedInternalData field is stripped server-side before the response is returned to the client./api/v1/accounting/sync-from-datePer-business sync lower-bound date — Administrator-only. The stored epoch-ms lower bound is read by all sync paths (manual Sync Now + scheduled workers for Xero/Sage/MYOB) and passed as modifiedSince to the accounting provider. Setting to null restores the default 'full history' behaviour. Webhooks are never filtered. Surfaced on GET /accounting/{xero|sage|myob}/status so the app can render the date picker pre-populated./api/v1/businesses/:businessId/accounting/unmapped-tax-typesList provider tax codes that have arrived on invoices without a confirmed mapping. Populated fire-and-forget by sync services and webhook workers on every invoice ingestion pass. Each row includes providerTaxName and providerRate (nullable). Rows that now have an active TaxRateMappings entry are excluded server-side via NOT EXISTS join — confirming a mapping silently removes the entry; deactivating a mapping makes it reappear. Used by the home-screen banner and the Integrations nav badge to notify admins./api/v1/accounting/auto-fiscaliseToggle the per-business Businesses.AutoFiscaliseInvoices flag (DB default 1). When on, every newly-inserted eligible invoice from any sync path (manual Sync Now, webhook, scheduled poll) is dispatched to TaxCore automatically. Fires AUTO_FISCALISE_ENABLED / AUTO_FISCALISE_DISABLED audit. Reflected on GET /accounting/{provider}/status.autoFiscaliseInvoices so the app can render the switch alongside connection state. Manual fiscalisation via POST /fiscalisation-jobs remains available regardless./api/v1/accounting/xero/startBegin a Xero OAuth2 authorisation flow for the caller's business. Generates a PKCE verifier + state, persists them in OAuthStates, and returns the Xero authorisation URL the app/browser must redirect to. Rate-limited (10/min open bucket)./api/v1/accounting/xero/callbackOAuth callback target hit by the user's browser after Xero consent. Validates the state (constant-time), exchanges the authorisation code, upserts XeroConnections rows for every tenant returned, and either 302-redirects (desktop) or serves a 200 text/html bridge page (mobile UA) that opens the native `vsms://auth/xero/...` deep link./api/v1/accounting/xero/statusConnection summary for the caller's business — whether a Xero connection exists, the active tenant, the list of authorised tenants (no tokens), and the current `autoFiscaliseInvoices` flag./api/v1/accounting/xero/tenantsList every Xero tenant authorised for the caller's business. Each entry carries `isActive` to indicate which tenant is the current target of sync / webhooks./api/v1/accounting/xero/tenants/:xeroTenantId/activateSwitch the active Xero tenant for the caller's business. Flips `IsActive` on XeroConnections rows so subsequent sync / webhook traffic uses the chosen tenant./api/v1/accounting/xero/connectionHard-disconnect Xero for the caller's business — deletes every XeroConnections row, drops encrypted tokens, fires `XERO_DISCONNECTED` audit./api/v1/accounting/xero/tax-mappingsList all Xero tax-type to V-SDC tax-label mappings for the business (active and inactive). Active rows ordered first, then alphabetically. `isActive: boolean` per row so the admin UI can surface a Restore action on soft-deleted entries./api/v1/accounting/xero/tax-mappings/auto-mapFetch Xero `/TaxRates` from the active tenant + V-SDC `/status`, then return match suggestions with `needsReview` flags + alternatives. Does NOT persist — the admin confirms each suggestion via PUT. Rate-limited (10/min open bucket)./api/v1/accounting/xero/tax-mappingsCreate or update one Xero tax-type to V-SDC label mapping (MERGE on (businessId, 'xero', providerTaxType)). Re-activates an existing inactive row instead of inserting a duplicate. Fires `XERO_TAX_MAPPING_UPDATED` audit./api/v1/accounting/xero/tax-mappings/:providerTaxTypeSoft-delete a Xero tax mapping — sets `IsActive=0`. Row is retained for history and can be re-activated via the restore endpoint. Idempotent./api/v1/accounting/xero/tax-mappings/:providerTaxType/restoreRe-activate a previously soft-deleted Xero tax mapping (`IsActive=0` → `1`). Idempotent./webhooks/xeroXero-initiated webhook delivery. Verifies HMAC-SHA256 over the raw request body using `XERO_WEBHOOK_SIGNING_KEY` (constant-time compared to the base64 `x-xero-signature` header). Valid signature → fan each event to the `xero:webhook:events` Redis Stream and respond `200` within Xero's 5-second ack budget. Xero's intent-to-receive handshake (`events: []`) flows through the same code path. NOT mounted under `/api/v1`./api/v1/accounting/sage/startBegin a Sage OAuth2 authorisation flow for the caller's business. Generates PKCE + state, persists in OAuthStates, returns the Sage authorisation URL. Rate-limited (10/min open bucket)./api/v1/accounting/sage/callbackSage OAuth callback. Validates state, exchanges the code, upserts a single SageConnections row (Sage has one business per token — no tenant selection), then 302 redirects (desktop) or serves a 200 text/html bridge page (mobile) opening `vsms://auth/sage/{success|failure}`./api/v1/accounting/sage/statusSage connection summary — whether a Sage connection exists, the connected Sage business, and the current `autoFiscaliseInvoices` flag./api/v1/accounting/sage/connectionSoft-deactivate the Sage connection for the caller's business (`IsActive=0`). Rows are retained for history; the user may re-authorise via `/sage/start`. Fires `SAGE_DISCONNECTED` audit./api/v1/accounting/sage/tax-mappingsList all Sage tax-type to V-SDC tax-label mappings for the business (`Provider='sage'`). Mirrors `/accounting/xero/tax-mappings`./api/v1/accounting/sage/tax-mappings/auto-mapPull Sage tax-rates + V-SDC `/status` and return match suggestions. Does NOT persist. Rate-limited (10/min open bucket)./api/v1/accounting/sage/tax-mappingsCreate or update one Sage tax-mapping. Re-activates an inactive row on match. Fires `SAGE_TAX_MAPPING_UPDATED` audit (resource type `SAGE_CONNECTION`)./api/v1/accounting/sage/tax-mappings/:providerTaxTypeSoft-delete a Sage tax-mapping (`IsActive=0`). Idempotent./api/v1/accounting/sage/tax-mappings/:providerTaxType/restoreRe-activate a deactivated Sage tax-mapping (`IsActive=1`). Idempotent./api/v1/accounting/sage200evolution/tax-mappingsList all Sage 200 Evolution tax-mappings for the business (`Provider='sage200evolution'`). The path-param `:id` everywhere else in this surface is the Sage `idTaxRate` (the provider tax type)./api/v1/accounting/sage200evolution/tax-mappings/autoOne-off auto-map against the customer's on-premise Sage 200 Evolution database. Opens a direct MSSQL connection using the supplied credentials, runs `SELECT idTaxRate, Code, cFiscalTaxLabel FROM dbo.TaxRate`, and upserts a proposed mapping for every row not already mapped. Distinct from the `auto-map` pattern used by Xero/Sage/MYOB — this endpoint reads the customer's own DB and persists proposals immediately./api/v1/accounting/sage200evolution/tax-mappings/:idUpsert a single Sage 200 Evolution mapping. `:id` is the Sage `idTaxRate` (provider tax type). Body carries the V-SDC label + optional metadata./api/v1/accounting/sage200evolution/tax-mappings/:idSoft-delete a Sage 200 Evolution mapping (`IsActive=0`). Idempotent./api/v1/accounting/sage200evolution/tax-mappings/:id/restoreRe-activate a deactivated Sage 200 Evolution mapping (`IsActive=1`). Note: uses POST (unlike the Xero/Sage/MYOB restore endpoints which use PATCH). Idempotent./api/v1/accounting/myob/startBegin a MYOB AccountRight Live OAuth2 authorisation flow. Generates PKCE + state, returns the MYOB authorisation URL. Rate-limited (10/min open bucket)./api/v1/accounting/myob/callbackMYOB OAuth callback. Validates state, exchanges the code, upserts a MyobConnections row per discovered company file, then 302-redirects (desktop) or serves a 200 text/html bridge page (mobile) opening `vsms://auth/myob/{success|failure}`./api/v1/accounting/myob/statusMYOB connection summary — whether a connection exists, the file list, and the current `autoFiscaliseInvoices` flag./api/v1/accounting/myob/filesList every MYOB company file authorised for the caller's business, each carrying `isActive` indicating the current sync target./api/v1/accounting/myob/files/:myobFileId/activateSwitch the active MYOB company file for the caller's business. Fires `MYOB_FILE_ACTIVATED` audit./api/v1/accounting/myob/connectionSoft-deactivate all MYOB connections for the caller's business (`IsActive=0`). Rows are retained for history. Fires `MYOB_DISCONNECTED` audit./api/v1/accounting/myob/tax-mappingsList all MYOB tax-mappings for the business (`Provider='myob'`). Mirrors `/accounting/sage/tax-mappings`./api/v1/accounting/myob/tax-mappings/auto-mapPull MYOB tax codes + V-SDC `/status` and return match suggestions. Does NOT persist. Rate-limited (10/min open bucket)./api/v1/accounting/myob/tax-mappingsCreate or update one MYOB tax-mapping. Fires `MYOB_TAX_MAPPING_UPDATED` audit./api/v1/accounting/myob/tax-mappings/:providerTaxTypeSoft-delete a MYOB tax-mapping (`IsActive=0`). Idempotent./api/v1/accounting/myob/tax-mappings/:providerTaxType/restoreRe-activate a deactivated MYOB tax-mapping (`IsActive=1`). Idempotent./api/v1/accounting/xero/syncPull-sync PAID invoices from the active Xero tenant: fetch via `XeroProvider.getInvoices`, normalise to `AccountingInvoice[]`, persist to `Invoices` table (`Source='xero'`) with one `ImportBatches` row per call. Idempotency key = `SHA-256(businessId:xeroTenantId:xeroInvoiceId)`. When `AutoFiscaliseInvoices=1`, newly-inserted eligible invoices are chunked (10) and dispatched as fiscalisation jobs. Returns the `AccountingInvoice[]` plus a summary message including `jobIds=`. HTTP caching disabled./api/v1/accounting/sage/syncPull-sync invoices from the active Sage connection. Fetches OUTSTANDING / OVERDUE / PAID via three API calls merged client-side, normalises, persists to `Invoices` (`Source='sage'`). Idempotency key = `SHA-256(businessId:sageBusinessId:sageInvoiceId)`. `modifiedSince` is silently ignored (Sage's `sales_invoices` has no date filter). Auto-fiscalisation behaviour matches Xero. HTTP caching disabled./api/v1/accounting/myob/syncPull-sync `Closed` invoices from the active MYOB company file. Fetches `/Sale/Invoice/Service` and `/Sale/Invoice/Item` merged by UID, normalises, persists to `Invoices` (`Source='myob'`). Idempotency key = `SHA-256(businessId:myobFileId:myobInvoiceId)`. Auto-fiscalisation behaviour matches Xero. HTTP caching disabled./api/v1/businesses/:businessId/accounting/sage100/syncOn-premise Sage 100 agent push. Headless agent POSTs a batch of normalised `AccountingInvoice[]` for the business; the helper inserts → dedupes → auto-fiscalises (when the flag is on). API key must have `scope=sage100` (enforced by `requireScope`); empty array is allowed (used by the agent's first-run dry-run wizard)./api/v1/businesses/:businessId/accounting/sage300/syncOn-premise Sage 300 agent push. Same shape as the Sage 100 endpoint but scope-bound to `sage300`./api/v1/businesses/:businessId/accounting/sage200evolution/syncOn-premise Sage 200 Evolution agent push. Scope-bound to `sage200evolution`. Per-row register→till routing via `Tills.PosRegisterId`; unrouted invoices return `outcomes[].outcome='register_unmapped'` and fire `SAGE200_RECEIPT_UNROUTED` audit. Supports advance-sale deposits (one `AccountingInvoicePayment` per `_etblInvoiceDeposits` row, fan-out to N `NormalizedInvoicePayment`s)./api/v1/businesses/:businessId/accounting/amicus/syncOn-premise AMICUS agent push. Scope-bound to `amicus`. Per-row register→till routing (null till → sign-but-don't-print), `PosSaleId` persisted on `Invoices`, refund referent resolution via `originalPosSaleId`. Supports split-tender (one payment with `tenders[]`). Unrouted invoices return `outcomes[].outcome='register_unmapped'` and fire `AMICUS_RECEIPT_UNROUTED` audit./api/v1/businesses/:businessId/accounting/sync/statusReturn the last-push timestamp per on-premise source (sage100, sage300, sage200evolution, amicus) for the business. Reads `ImportBatches.CreatedAt` per source via `findLatestCreatedAtBySources`; null when the source has never pushed./api/v1/accounting/sage100/tax-mappingsList Sage 100 tax-mappings for the business (`Provider='sage100'`)./api/v1/accounting/sage100/tax-mappingsUpsert one Sage 100 tax-mapping. Same body shape as the other tax-mapping CRUD surfaces./api/v1/accounting/sage100/tax-mappings/:providerTaxTypeSoft-delete a Sage 100 tax-mapping (`IsActive=0`). Idempotent./api/v1/accounting/sage100/tax-mappings/:providerTaxType/restoreRe-activate a deactivated Sage 100 mapping (`IsActive=1`). Idempotent./api/v1/accounting/sage300/tax-mappingsList Sage 300 tax-mappings for the business (`Provider='sage300'`)./api/v1/accounting/sage300/tax-mappings/:providerTaxTypeSoft-delete a Sage 300 tax-mapping (`IsActive=0`). Idempotent./api/v1/accounting/sage300/tax-mappings/:providerTaxType/restoreRe-activate a deactivated Sage 300 mapping (`IsActive=1`). Idempotent./api/v1/accounting/amicus/tax-mappingsList AMICUS tax-mappings for the business (`Provider='amicus'`)./api/v1/accounting/amicus/tax-mappings/:providerTaxTypeSoft-delete an AMICUS tax-mapping (`IsActive=0`). Idempotent./api/v1/accounting/amicus/tax-mappings/:providerTaxType/restoreRe-activate a deactivated AMICUS mapping (`IsActive=1`). Idempotent./api/v1/accounting/amicus/tax-mappings/auto-seedPre-populate the fixed AMICUS taxonomy (`VAT` + `EXEMPT`) so the admin can confirm both V-SDC labels before any sale arrives. No body — the taxonomy is known by contract with the bundled agent profile, not discovered from the customer DB. AMICUS-only; no equivalent for Sage 100/300./api/v1/businesses/:businessId/print/tillsList every registered till for the business — name, machine id, `IsDefault`, `IsOnline`, `LastSeenAt`, and optional `LocationId`./api/v1/businesses/:businessId/print/tills/:tillId/nameRename a till. Fires `TILL_RENAMED` audit (resource type `TILL`)./api/v1/businesses/:businessId/print/tills/:tillId/testDispatch a test PrintJob (`type='TEST'`) to the till. The agent picks it up on its next poll or via the SSE wake stream. Fires `TEST_PRINT_SENT` audit./api/v1/businesses/:businessId/print/tills/setup-tokenGenerate a one-time setup token used by the print agent's first-run wizard to claim a permanent TillToken. Optionally binds the resulting till to a specific Location (TAXCORE-246) — single-location merchants omit and the claim resolves to the business default. Fires `TILL_SETUP_TOKEN_GENERATED` audit./api/v1/businesses/:businessId/print/default-tillSet the default till for the business. Future auto-print jobs that don't carry an explicit till routing key target this till. Fires `TILL_DEFAULT_SET` audit./api/v1/businesses/:businessId/print/jobsList print jobs for the business, optionally filtered by status./api/v1/businesses/:businessId/print/jobs/:id/retryReset a `FAILED` or `STALE` print job back to `PENDING` so the agent will pick it up again. Fires `PRINT_JOB_RETRIED` audit./api/v1/print/agent/claimPrint-agent first-run setup. Exchanges the one-time SetupToken (issued via `POST /print/tills/setup-token`) for a permanent TillToken — the SetupToken itself IS the credential for this single call. Returns the till + plaintext token (only time it is returned). Fires `TILL_CLAIMED` audit./api/v1/print/agent/jobsReturn PENDING print jobs for the authenticated till. Polled by the agent as a fallback to the SSE wake channel./api/v1/print/agent/jobs/streamLong-lived SSE channel — emits a `wake` event whenever a new PrintJob is published for this till so the agent can fetch + print without waiting for the next poll tick. Keep-alive `:keepalive` comment every 25 s to beat reverse-proxy idle timeouts./api/v1/print/agent/jobs/:id/ackAgent ack — mark a print job `PRINTED` (success) or `FAILED` (USB / printer error). The repository asserts the job belongs to the authenticated till and is still in a settable state./api/v1/print/agent/heartbeatPeriodic heartbeat — updates `Tills.IsOnline=1` and `LastSeenAt=now`. Sent by the agent on every poll tick / SSE reconnect./api/v1/businesses/:businessId/fiscaliseMain ingestion endpoint. Persists the invoice + dispatches it to V-SDC and sync-waits up to ~10 s for the fiscal response. Returns 200 with `fiscalInvoiceNumber` + QR/URL/hash when the consumer reaches a terminal state inside the window; 202 with a `statusUrl` when the timeout elapses with payments still in-flight; 201 with a `triggerUrl` for PROFORMA bodies (caller must follow up with the trigger endpoint)./api/v1/businesses/:businessId/fiscalise/:invoiceIdIdempotent status retrieval. Returns the same response shape as `POST /fiscalise` so a caller polling after a 202 gets a drop-in replacement once the consumer reaches a terminal state./api/v1/businesses/:businessId/fiscalise/:invoiceId/triggerExplicit dispatch for a PROFORMA imported via POST /fiscalise's 201 path. Delegates to the existing `triggerFiscalisationByInvoiceIds` primitive — non-PROFORMA invoices are rejected with 422 INVOICE_NOT_QUOTE./api/v1/businesses/:businessId/fiscalise/cancelSubmit a V-SDC cancellation document for a previously-fiscalised payment. Resolves `fiscalInvoiceNumber` via `findByFiscalInvoiceNumber`, delegates to the per-payment `cancelPayment` primitive (sibling InvoicePayments row with `CancelsPaymentId`), and waits for the cancellation receipt to be signed before responding — so the caller gets back the full `fiscalJournal` + QR + signed hash in one round-trip and can print the cancellation receipt without polling. Falls through to 202 + `statusUrl` if the sync-wait window elapses.