# DayZero API — Full Reference > Complete API endpoint documentation generated from the OpenAPI spec. > All amounts are in cents (e.g. $150.00 = 15000). IDs are UUID v7. > Authentication: Bearer token in Authorization header. > Pagination: cursor-based (pass `cursor` param from `next_cursor`). ## Base URL https://api.ondayzero.com/api/v1 ## Authentication Authorization: Bearer dz_your_token x-business-id: YOUR_BUSINESS_ID ## accounting-dimensions ### GET /api/v1/accounting-dimensions List classes/locations List accounting dimensions, optionally filtered by type. Parameters: - dimension_type (query, string, optional): Filter by 'class' or 'location'. - include_inactive (query, boolean, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/accounting-dimensions Create class/location Create a new accounting dimension. Request body (AccountingDimensionCreateRequest): - dimension_type (string, required) - name (string, required) - code (string, optional) - parent_id (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/accounting-dimensions/pnl Dimensional P&L Profit & loss broken down by class or location for a date range. Parameters: - dimension_type (query, string, required): 'class' or 'location'. - start_date (query, string · date, required): Period start (inclusive). - end_date (query, string · date, required): Period end (inclusive). Responses: 200, 400, 401, 403, 422 ### DELETE /api/v1/accounting-dimensions/{dimension_id} Deactivate class/location Soft-delete (deactivate) an accounting dimension. Parameters: - dimension_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/accounting-dimensions/{dimension_id} Get class/location Get a single accounting dimension. Parameters: - dimension_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/accounting-dimensions/{dimension_id} Update class/location Update an accounting dimension. Parameters: - dimension_id (path, string, required) Request body (AccountingDimensionUpdateRequest): - name (string, optional) - code (string, optional) - parent_id (string, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ## accounting-periods — Manage fiscal periods (month/quarter/year) and period locks. ### GET /api/v1/accounting-periods List accounting periods List all accounting periods for the business. Parameters: - status (query, string, optional): Filter by status: open, closed, locked - search (query, string, optional): Search status, dates, or lock/reopen reasons (case-insensitive) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/accounting-periods Create accounting period Create a new accounting period. Request body (AccountingPeriodCreateRequest): - period_start (string · date, required) - period_end (string · date, required) Responses: 201, 400, 401, 403, 409, 422 ### POST /api/v1/accounting-periods/bulk-lock Bulk close-and-lock accounting periods Close and lock multiple accounting periods in one call. Designed for historical-data migration: any open period in the list is closed first, then transitioned to locked. Already-locked periods are skipped. Request body (BulkLockPeriodsRequest): - period_ids (array · string, required) - lock_reason (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/accounting-periods/close Close accounting period Close an accounting period to prevent modifications. Request body (ClosePeriodRequest): - period_end (string · date, required) - lock_reason (string, optional) - force (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/accounting-periods/{period_id} Get accounting period Get details of a specific accounting period. Parameters: - period_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/accounting-periods/{period_id}/blockers List what's blocking close Return only the itemized 'What's between you and 100%' blockers list ordered by severity. Used by the AccountingPeriodsPage side panel and the Daily Brief tile. Parameters: - period_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/accounting-periods/{period_id}/checklist-acks List checklist acknowledgements Return all manual checklist step acknowledgements recorded for the period. Used by the close dashboard so Phase 4 'trial balance reviewed', 'P&L variance reviewed', 'controller signed off' etc. persist across page refreshes. Parameters: - period_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/accounting-periods/{period_id}/checklist-acks/{step_id} Remove a checklist acknowledgement Clear a previously-recorded checklist step ack for a period. Parameters: - period_id (path, string, required) - step_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/accounting-periods/{period_id}/checklist-acks/{step_id} Acknowledge a checklist step Record (or update) a manual-step ack so the dashboard treats the step as done. Idempotent — re-calling updates the notes. Parameters: - period_id (path, string, required) - step_id (path, string, required) Request body (AcknowledgeCloseStepRequest): - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/accounting-periods/{period_id}/close-readiness AI close readiness check Check period close readiness with AI-enhanced analysis. Parameters: - period_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/accounting-periods/{period_id}/close-score Get unified close readiness score Return the persisted close-as-score gauge value for the period. If no score has been computed yet (or upstream state has changed) the score is recomputed in-line. Components include: txn_review, categorization, reconciliation, checklist, anomalies. Parameters: - period_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/accounting-periods/{period_id}/close-score/recompute Force-recompute close score Re-run scoring for the period and persist the new value. Use this after resolving a blocker (e.g. dismissing the last anomaly) so the gauge updates immediately rather than waiting for the nightly Temporal recompute. Parameters: - period_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/accounting-periods/{period_id}/lock Lock accounting period Lock a closed period (stronger protection than close). Parameters: - period_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/accounting-periods/{period_id}/reopen Reopen accounting period Reopen a closed or locked period (requires reason). Parameters: - period_id (path, string, required) Request body (ReopenPeriodRequest): - reopen_reason (string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/accounting-periods/{period_id}/score-history Daily close-score trend Return the append-only daily snapshot of close score for this period (oldest first). Powers the 'Day 1 → Day 6' sparkline. Parameters: - period_id (path, string, required) - limit (query, integer, optional) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/accounting-periods/{period_id}/sign-offs Revoke a reviewer sign-off Remove the caller's sign-off for the given role. The close-score is recomputed on success so the gauge reflects the lost sign-off. Parameters: - period_id (path, string, required) - role (query, string, required): Role to revoke (preparer/reviewer/...) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/accounting-periods/{period_id}/sign-offs List multi-reviewer sign-offs Return all sign-off rows for the period (preparer, reviewer, controller, partner). Powers the Multi-reviewer sign-off badge on the AccountingPeriodsPage. Parameters: - period_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/accounting-periods/{period_id}/sign-offs Record a reviewer sign-off Record (or update) a reviewer's sign-off on the period. Idempotent on (user_id, role) — re-signing refreshes the snapshot score + notes. Side-effect: triggers a close-score recompute so the gauge updates immediately. Parameters: - period_id (path, string, required) Request body (SignOffRequest): - role (string, required) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ## advisory-firm-reports — Cross-client reporting for advisory firms. ### GET /api/v1/advisory-firms/{firm_id}/reports List available firm reports Get list of available reports for this advisory firm. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/reports/portfolio-overview Get portfolio overview Quick access to the client portfolio overview report. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/reports/work-queue Get work queue Get prioritized work queue for the advisory firm. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/reports/{report_name} Generate firm report Generate a specific report for an advisory firm. Parameters: - firm_id (path, string, required) - report_name (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ## advisory-firm-teams — Team management within advisory firms — assign staff to teams and manage client access. ### GET /api/v1/advisory-firms/{firm_id}/teams List firm teams List all teams for a firm with member and business counts. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 422 ### POST /api/v1/advisory-firms/{firm_id}/teams Create team Create a new team within the firm. Requires owner or admin role. Parameters: - firm_id (path, string, required) Request body (CreateTeamRequest): - name (string, required) - description (string, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/teams/{team_id} Delete team Delete a team and all its member/business associations. Parameters: - firm_id (path, string, required) - team_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/teams/{team_id} Get team details Get a team with full member and business lists. Parameters: - firm_id (path, string, required) - team_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/advisory-firms/{firm_id}/teams/{team_id} Update team Update a team's name or description. Parameters: - firm_id (path, string, required) - team_id (path, string, required) Request body (UpdateTeamRequest): - name (string, optional) - description (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/teams/{team_id}/businesses Add business to team Assign a firm business to a team. Parameters: - firm_id (path, string, required) - team_id (path, string, required) Request body (TeamBusinessRequest): - business_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/teams/{team_id}/businesses/{business_id} Remove business from team Remove a business from a team. Parameters: - firm_id (path, string, required) - team_id (path, string, required) - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/teams/{team_id}/members Add team member Add a firm staff member to a team. Parameters: - firm_id (path, string, required) - team_id (path, string, required) Request body (TeamMemberRequest): - user_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/teams/{team_id}/members/{user_id} Remove team member Remove a member from a team. Parameters: - firm_id (path, string, required) - team_id (path, string, required) - user_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/users/{user_id}/accessible-businesses List accessible businesses Returns the resolved list of businesses a member can see, with source info. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Responses: 200, 401, 403, 422 ### GET /api/v1/advisory-firms/{firm_id}/users/{user_id}/assignments List staff assignments List all business assignments for a firm member, with engagement metadata. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Responses: 200, 401, 403, 422 ### POST /api/v1/advisory-firms/{firm_id}/users/{user_id}/assignments Create staff assignment Assign a member to a business with optional engagement metadata. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Request body (CreateStaffAssignmentRequest): - business_id (string, required) - engagement_type (string, optional) - start_date (string, optional) - end_date (string, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 409, 422 ### PUT /api/v1/advisory-firms/{firm_id}/users/{user_id}/assignments Bulk set assignments Replace all business assignments for a member with the given set. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Request body (BulkSetAssignmentsRequest): - business_ids (array · string, required) Responses: 200, 400, 401, 403, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/users/{user_id}/assignments/{assignment_id} Delete assignment Remove a business assignment from a member. Parameters: - firm_id (path, string, required) - user_id (path, string, required) - assignment_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/advisory-firms/{firm_id}/users/{user_id}/assignments/{assignment_id} Update assignment Update engagement metadata on an assignment. Parameters: - firm_id (path, string, required) - user_id (path, string, required) - assignment_id (path, string, required) Request body (UpdateStaffAssignmentRequest): - engagement_type (string, optional) - start_date (string, optional) - end_date (string, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ## advisory-firm-templates ### GET /api/v1/advisory-firms/{firm_id}/templates List firm templates List firm-level COA / Bank Rules / Review Checklist templates. Optionally filter by ``kind``. Parameters: - firm_id (path, string, required) - kind (query, string, optional): Filter by template kind Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/templates Create a firm template Parameters: - firm_id (path, string, required) Request body (FirmTemplateCreate): - kind (string, required) - name (string, required) - description (string, optional) - payload (JsonValue, required) → JsonValue Responses: 201, 401, 403, 404, 409, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/templates/{template_id} Delete a firm template Parameters: - firm_id (path, string, required) - template_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/templates/{template_id} Get a firm template Parameters: - firm_id (path, string, required) - template_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/advisory-firms/{firm_id}/templates/{template_id} Update a firm template Parameters: - firm_id (path, string, required) - template_id (path, string, required) Request body (FirmTemplateUpdate): - name (string, optional) - description (string, optional) - payload (JsonValue, optional) Responses: 200, 401, 403, 404, 409, 422 ### POST /api/v1/advisory-firms/{firm_id}/templates/{template_id}/deploy Deploy a firm template to selected client businesses Bulk-applies the template to each business in ``business_ids``. COA templates use ``LedgerService.apply_coa``; bank-rule templates create new ``BankRule`` rows (duplicates by name are treated as skips so re-deploying is idempotent); review-checklist templates are recorded informationally. Parameters: - firm_id (path, string, required) - template_id (path, string, required) Request body (FirmTemplateDeployRequest): - business_ids (array · string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/templates/{template_id}/deployments List deployment history for a template Parameters: - firm_id (path, string, required) - template_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## advisory-firms — Advisory firm management for multi-tenant accounting. ### GET /api/v1/advisory-firms List advisory firms List advisory firms for a user. Defaults to the authenticated user; pass `user_id` to filter by a different user. Parameters: - user_id (query, string, optional): Filter firms by this user's memberships - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/advisory-firms Create advisory firm Create a new advisory firm. Request body (AdvisoryFirmCreateRequest): - name (string, required) - address (string, optional) - ein (string, optional) - logo_url (string, optional) - stripe_customer_id (string, optional) - client_messaging_enabled (boolean, optional) - ap_automation_enabled (boolean, optional) - client_visible_integrations (array · string, optional) - client_ai_assistant_enabled (boolean, optional) - client_digest_email_enabled (boolean, optional) - client_digest_frequency (string, optional) - mfa_enforced (boolean, optional) Responses: 201, 400, 401, 409, 422 ### DELETE /api/v1/advisory-firms/{firm_id} Delete advisory firm Delete an advisory firm and all associations. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id} Get advisory firm Get details of a specific advisory firm. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id} Update advisory firm Update advisory firm details. Parameters: - firm_id (path, string, required) Request body (AdvisoryFirmUpdateRequest): - name (string, optional) - address (string, optional) - ein (string, optional) - logo_url (string, optional) - stripe_customer_id (string, optional) - client_messaging_enabled (boolean, optional) - ap_automation_enabled (boolean, optional) - client_ai_assistant_enabled (boolean, optional) - client_digest_email_enabled (boolean, optional) - client_digest_frequency (string, optional) - slack_webhook_url (string, optional) - client_visible_integrations (array · string, optional) - mfa_enforced (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/ai-insights AI cross-client insights Get AI-powered cross-client insights for an advisory firm. Parameters: - firm_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/businesses List firm businesses List all businesses assigned to an advisory firm. Parameters: - firm_id (path, string, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/businesses Assign business to firm Assign a business to an advisory firm. Parameters: - firm_id (path, string, required) Request body (BusinessFirmAssociationCreateRequest): - business_id (string, required) - notes (string, optional) Responses: 201, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/businesses/{business_id} Unassign business from firm Unassign a business from an advisory firm. Parameters: - firm_id (path, string, required) - business_id (path, string, required) Responses: 200, 422 ### POST /api/v1/advisory-firms/{firm_id}/businesses/{business_id}/invite-client Invite a client user to a business Invite a user by email to access a client business. If the user already exists they are added immediately; otherwise an invitation email is sent and they are added on signup. Parameters: - firm_id (path, string, required) - business_id (path, string, required) Request body (InviteClientRequest): - email (string, required) - first_name (string, optional) - last_name (string, optional) - message (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### GET /api/v1/advisory-firms/{firm_id}/businesses/{business_id}/preferences/notifications/topics Get a business notification topic policy Per-topic channel toggles applied to a client business's users. Parameters: - firm_id (path, string, required) - business_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id}/businesses/{business_id}/preferences/notifications/topics Update a business notification topic policy Replace the per-topic channel policy for a client business. Parameters: - firm_id (path, string, required) - business_id (path, string, required) Request body (TopicChannelOverridesUpdate): - overrides (object, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/clients List firm clients List all users with the 'client' role in this firm, with their assigned businesses. Parameters: - firm_id (path, string, required) - search (query, string, optional): Search by client name or email Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/clients/{user_id}/businesses List client business access List which businesses a specific client can access. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/clients/{user_id}/businesses Grant client business access Grant a client user access to a specific business within the firm. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Request body (ClientBusinessAccessRequest): - business_id (string, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/clients/{user_id}/businesses/{business_id} Revoke client business access Revoke a client's access to a specific business. Parameters: - firm_id (path, string, required) - user_id (path, string, required) - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/invitations List pending invitations List all invitations for a firm, optionally filtered by type (client, staff, or all) and status. Parameters: - firm_id (path, string, required) - type (query, string, optional): Filter by invitation type - status (query, string, optional): Filter by status (e.g. pending) Responses: 200, 401, 403, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/invitations/{invitation_id} Cancel an invitation Cancel a pending invitation. Cannot undo. Parameters: - firm_id (path, string, required) - invitation_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/invitations/{invitation_id}/resend Resend an invitation Resend the invitation email and reset the expiry to 7 days from now. Only works on pending invitations. Parameters: - firm_id (path, string, required) - invitation_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/logo Get firm logo download URL Generate a short-lived presigned download URL for the firm logo. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/preferences/notifications/topics Get firm notification topic policy Per-topic channel toggles applied firm-wide to firm staff. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id}/preferences/notifications/topics Update firm notification topic policy Replace the firm-wide per-topic channel policy. Parameters: - firm_id (path, string, required) Request body (TopicChannelOverridesUpdate): - overrides (object, optional) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id}/slack Save firm Slack webhook Configure a Slack Incoming Webhook URL for the advisory firm. Parameters: - firm_id (path, string, required) Request body (SlackWebhookSaveRequest): - slack_webhook_url (string, optional) - channel_name (string, optional) - preferences (object, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/slack/status Get firm Slack status Check whether a Slack webhook is configured and get notification preferences. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/slack/test Test Slack webhook Send a test message to the firm's first configured Slack webhook (legacy). Parameters: - firm_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/slack/{webhook_id} Disconnect Slack Remove the firm's Slack webhook URL and notification preferences. Parameters: - firm_id (path, string, required) - webhook_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id}/slack/{webhook_id}/preferences Update firm Slack notification preferences Toggle which notification categories are sent to the firm's Slack. Parameters: - firm_id (path, string, required) - webhook_id (path, string, required) Request body (SlackPreferencesUpdateRequest): - preferences (object, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/slack/{webhook_id}/test Test Slack webhook Send a test message to the firm's configured Slack webhook. Parameters: - firm_id (path, string, required) - webhook_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id}/stripe-customer Link Stripe customer Link an advisory firm to a Stripe customer for billing. Parameters: - firm_id (path, string, required) Request body (UpdateStripeCustomerRequest): - stripe_customer_id (string, required) Responses: 200, 422 ### GET /api/v1/advisory-firms/{firm_id}/users List firm users List all users in an advisory firm. Parameters: - firm_id (path, string, required) Responses: 200, 422 ### POST /api/v1/advisory-firms/{firm_id}/users Add user to firm Add a user to an advisory firm by user_id or email. Parameters: - firm_id (path, string, required) Request body (UserFirmAssociationCreateRequest): - user_id (string, optional) - email (string, optional) - role (string, optional) Responses: 201, 422 ### POST /api/v1/advisory-firms/{firm_id}/users/invite Invite a staff member to the firm Invite a user by email to join the firm as staff (admin or member). If the user already exists they are added immediately; otherwise an invitation email is sent and they are added on signup. Owner role cannot be assigned via invitation. Parameters: - firm_id (path, string, required) Request body (InviteStaffRequest): - email (string, required) - role (string, optional) - first_name (string, optional) - last_name (string, optional) - message (string, optional) - business_ids (array · string, optional) - team_id (string, optional) - assign_all_firm_businesses (boolean, optional) Responses: 201, 400, 401, 403, 409, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/users/{user_id} Remove user from firm Remove a user from an advisory firm. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Responses: 200, 422 ### PATCH /api/v1/advisory-firms/{firm_id}/users/{user_id}/api-access Set firm user API access Enable or disable DayZero API access for a firm staff member. API access is enabled by default for everyone; firm owners/admins can restrict it per user. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Request body (FirmUserApiAccessRequest): - api_access_enabled (boolean, required) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/advisory-firms/{firm_id}/users/{user_id}/role Update user role in firm Update a user's role in an advisory firm. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Request body (UserRoleUpdateRequest): - role (string, required) Responses: 200, 422 ## ai-reporting ### GET /api/v1/ai-reporting/decks List generated decks Cursor-paginated list of AI-generated decks for the business. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/ai-reporting/decks Generate a new AI deck Build a new deck from live data + AI commentary and persist it. Returns the full DeckSpec ready to render. Request body (GenerateDeckRequest): - prompt (string, required) - period_start (string · date, optional) - period_end (string · date, optional) - slide_types (array · string, optional) - title (string, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/ai-reporting/decks/{deck_id} Delete a deck Soft-delete a deck. Existing share links continue to 404. Parameters: - deck_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/ai-reporting/decks/{deck_id} Get a deck Fetch the full DeckSpec for a single deck. Parameters: - deck_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/ai-reporting/decks/{deck_id}/export Export a deck Export an already-generated deck to PPTX or PDF. The client uploads rendered slide PNGs as multipart parts; the server assembles them via python-pptx (PPTX) or ReportLab (PDF fallback). Returns the file as a streaming response with a content-disposition header. Parameters: - deck_id (path, string, required) - format (query, string, optional) - commentary (query, string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/ai-reporting/decks/{deck_id}/share Create a public share link for a deck Mint a SHA-256 token-protected public share URL. The raw token is returned in this response exactly once — store or display it immediately. Subsequent listings only show metadata (no token). Parameters: - deck_id (path, string, required) Request body (CreateShareRequest): - expires_in_hours (integer, optional) - slug (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/ai-reporting/decks/{deck_id}/shares List share links for a deck All share links (active and revoked) ordered newest first. Parameters: - deck_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/ai-reporting/decks/{deck_id}/slides/reorder Reorder slides Set a new slide order via a list of slide_ids. Parameters: - deck_id (path, string, required) Request body (ReorderSlidesRequest): - slide_ids (array · string, required) Responses: 201, 400, 401, 403, 404, 422 ### PATCH /api/v1/ai-reporting/decks/{deck_id}/slides/{slide_id} Edit a slide Patch the slide title, commentary (headline/claims/recommendation) or hidden flag. Use this for the 'edit commentary' button. Parameters: - deck_id (path, string, required) - slide_id (path, string, required) Request body (UpdateSlideRequest): - title (string, optional) - commentary (SlideCommentary, optional) - hidden (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/ai-reporting/decks/{deck_id}/slides/{slide_id}/toggle-hidden Toggle slide visibility Flip the ``hidden`` flag on a single slide. No body required. Parameters: - deck_id (path, string, required) - slide_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### DELETE /api/v1/ai-reporting/shares/{share_id} Revoke a share link Mark the share as revoked. The public viewer returns 404 after. Parameters: - share_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## anomalies — AI-powered anomaly detection for unusual transactions and patterns. ### GET /api/v1/anomalies List transaction anomalies Return the latest pre-computed anomaly scan for the current business. Results are written by background jobs (daily cron & post-sync). Dismissed anomalies are excluded by default. Parameters: - severity (query, string, optional): Filter by severity: low, medium, high - anomaly_type (query, string, optional): Filter by anomaly type (e.g. duplicate_payment, unusual_amount) - show_dismissed (query, boolean, optional): Include previously dismissed anomalies - start_date (query, string · date, optional): Optional inclusive lower bound for anomaly date window (YYYY-MM-DD). - end_date (query, string · date, optional): Optional inclusive upper bound for anomaly date window (YYYY-MM-DD). Responses: 200, 401, 403, 422 ### POST /api/v1/anomalies/dismiss Dismiss an anomaly Mark a detected anomaly as dismissed with a reason and optional notes. Request body (app__api__v1__schemas__anomaly__DismissRequest): - fingerprint (string, required) - anomaly_type (string, required) - reason (string, required) - description (string, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/anomalies/dismiss-all Bulk dismiss all active anomalies Dismiss every active anomaly in the latest scan for the current business. Used by the notifications inbox when clearing the anomaly topic in bulk. Request body (BulkDismissAllRequest): - reason (string, required) - notes (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/anomalies/dismiss-by-period Bulk dismiss anomalies in a date range Dismiss every active anomaly whose date metadata overlaps the given period. Used by the close checklist's 'Dismiss all anomalies' CTA for historical periods that were reviewed offline. Request body (BulkDismissByPeriodRequest): - start_date (string · date, required) - end_date (string · date, required) - reason (string, required) - notes (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/anomalies/dismiss-type Bulk dismiss all anomalies of a given type Dismiss every active anomaly matching the specified type in one operation. Request body (BulkDismissRequest): - anomaly_type (string, required) - reason (string, required) - notes (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/anomalies/rescan Trigger an on-demand anomaly rescan Start a Temporal workflow that re-runs the anomaly detection engine for the current business and refreshes the cached scan results. Returns immediately; clients should refetch `GET /anomalies` shortly afterwards. Rate-limited to one rescan per business every 5 minutes. Responses: 201, 401, 403, 422, 429 ### POST /api/v1/anomalies/restore Restore a dismissed anomaly Remove a dismissal so the anomaly appears in active results again. Parameters: - fingerprint (query, string, required): Fingerprint of the anomaly to restore Responses: 201, 401, 403, 404, 422 ### GET /api/v1/anomalies/{fingerprint} Get anomaly finding detail Return the hydrated detail for a single anomaly fingerprint, including the recommended_action payload and the evidence rows shown in the side-by-side panel on the AnomaliesPage. Parameters: - fingerprint (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/anomalies/{fingerprint}/resolve Execute a recommended action and dismiss the anomaly Run the `recommended_action` attached to the finding (e.g. Draft void duplicate payments, merge duplicate vendors) and record a `resolved` dismissal in the same call. For void actions the endpoint records the user's intent and stages the dismissal; the actual journal-entry mutation must be confirmed on the transaction itself to keep posting side-effects observable to the audit log. Parameters: - fingerprint (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## ap-approval — Accounts payable approval workflows for bill review and authorization. ### GET /api/v1/ap-approval Get AP approval workflow Get the AP approval workflow for the current business's firm. Responses: 200, 401, 403, 404, 422 ### GET /api/v1/ap-approval/analytics Approval queue analytics Return queue health stats: pending counts by tier, average age, and oldest pending item. Responses: 200, 401, 403, 422 ### GET /api/v1/ap-approval/approvers Get approvers by tier Return the configured approver user IDs grouped by tier. Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/ap-approval/approvers/{tier} Update approvers for a tier Replace the approver list for a tier in the workflow graph. Parameters: - tier (path, string, required) Request body (SetApproversRequest): - user_ids (array · string, optional) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/ap-approval/batch-approve Batch approve pending items Approve multiple suspended workflow executions in a single request. Request body (BatchApproveRequest): - execution_ids (array · string, required) - comment (string, optional) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/ap-approval/pending List pending AP approvals List bills currently suspended at an AP approval gate. Parameters: - tier (query, string, optional): Filter to a specific tier: 'ops', 'advisor', or 'owner'. - mine (query, boolean, optional): When true, only show approvals whose gate tier the caller is configured as an approver for. Responses: 200, 401, 403, 422 ### POST /api/v1/ap-approval/provision Provision AP approval workflow for firm Create or retrieve the AP approval workflow for the firm associated with this business. Parameters: - auto_approve_max_cents (query, integer, optional): Max amount in cents for auto-approval (default: $500) - ops_approve_max_cents (query, integer, optional): Max amount in cents for ops approval (default: $5,000) Responses: 201, 401, 403, 422 ### POST /api/v1/ap-approval/risk-score AI approval risk scores Get AI-powered risk scores for pending bill approvals. Responses: 201, 401, 403, 422 ### PATCH /api/v1/ap-approval/thresholds Update approval thresholds Update amount thresholds and/or escalation timeout without re-provisioning. Only provided fields are changed; the rest retain their current values. Request body (UpdateThresholdsRequest): - auto_approve_max_cents (integer, optional) - ops_approve_max_cents (integer, optional) - escalation_hours (integer, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/ap-approval/vendor/{vendor_id}/history Vendor payment history Return recent bill history for a vendor to provide context at approval time. Parameters: - vendor_id (path, string, required) - limit (query, integer, optional): Number of recent bills to return. Responses: 200, 401, 403, 422 ## api-tokens — Create, list, and revoke Bearer API tokens. ### GET /api/v1/tokens List tokens List all API tokens for current user Parameters: - include_revoked (query, boolean, optional): Include revoked tokens - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/tokens Create token Create a new API token Request body (APITokenCreate): - name (string, required) - description (string, optional) - expires_in_days (integer, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/tokens/{token_id} Delete token Permanently delete an API token Parameters: - token_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/tokens/{token_id}/revoke Revoke token Revoke an API token. Parameters: - token_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## attention ### GET /api/v1/attention List attention items Get the merged list of inbox messages and live alerts. Items are sorted with action-required first, then by priority, then newest first. Inbox messages support cursor pagination; live alerts are always returned in full on the first page. Parameters: - action_required (query, boolean, optional): Only show items requiring user action. - category (query, string, optional): Filter by category. - priority (query, string, optional): Filter by priority. - entity_type (query, string, optional): Filter by entity type. - unread_only (query, boolean, optional): Hide read inbox messages and dismissed live alerts. - include_live (query, boolean, optional): Include live (computed) alerts alongside inbox rows. - client_view (query, boolean, optional): If true, action URLs use the ``/client`` shell prefix instead of ``/books``. - cursor (query, string, optional): Inbox pagination cursor. - limit (query, integer, optional): Max inbox items per page. Responses: 200, 401, 422 ### POST /api/v1/attention/clear-dismissals Restore dismissed live alerts Clears the user's per-user live alert dismissals. Responses: 201, 401, 422 ### POST /api/v1/attention/mark-all-read Mark all read + clear dismissals Mark every inbox message read and clear all live alert dismissals so the bell shows zero. Responses: 201, 401, 422 ### GET /api/v1/attention/summary Attention summary counts Counts used by the bell badge and the Action Items card. Combines unread inbox messages with live alert counts. Parameters: - client_view (query, boolean, optional) Responses: 200, 401, 422 ### GET /api/v1/attention/{item_id} Get single attention item Fetch a single attention item (inbox or live) by its prefixed ID. Optionally marks it read/dismissed. Parameters: - item_id (path, string, required) - auto_mark_read (query, boolean, optional) - client_view (query, boolean, optional) Responses: 200, 401, 404, 422 ### POST /api/v1/attention/{item_id}/read Mark item read or dismiss Mark an attention item read. Inbox items get ``read_at`` set; live alerts are dismissed for this user (until they clear dismissals). Parameters: - item_id (path, string, required) Responses: 201, 401, 404, 422 ## bank-deposits ### GET /api/v1/bank-deposits List bank deposits List bank deposits for the business. Parameters: - status (query, string, optional): Filter by status: posted or void. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/bank-deposits Create bank deposit Group undeposited receipts and ad-hoc lines into a bank deposit. Request body (BankDepositCreateRequest): - deposit_to_ledger_id (string, required) - deposit_date (string · date, optional) - sales_receipt_ids (array · string, optional) - other_lines (array · OtherDepositLine, optional) → OtherDepositLine - number (string, optional) - memo (string, optional) - currency (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/bank-deposits/undeposited List undeposited funds List posted sales receipts in Undeposited Funds awaiting deposit. Responses: 200, 401, 403, 422 ### GET /api/v1/bank-deposits/{deposit_id} Get bank deposit Get details of a specific bank deposit. Parameters: - deposit_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/bank-deposits/{deposit_id}/void Void bank deposit Void a deposit, reversing the entry and releasing its receipts. Parameters: - deposit_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## billing — Subscription billing, plan management, and usage tracking. ### POST /api/v1/billing/addon Toggle Addon Add or remove an add-on from the business's Stripe subscription. Request body (AddonToggleRequest): - addon_slug (string, required) - action (string, required) Responses: 201, 422 ### POST /api/v1/billing/addon/preview Preview Addon Change Preview the billing impact of adding or removing an add-on. Request body (AddonPreviewRequest): - addon_slug (string, required) - action (string, required) Responses: 201, 422 ### GET /api/v1/billing/ai-usage Get Ai Usage Return current daily and monthly AI request counts with plan limits. Responses: 200, 422 ### GET /api/v1/billing/ai-usage/details Get Ai Usage Details Return detailed AI usage stats: top users, model breakdown, daily trend. Parameters: - scope (query, string, optional): Set to 'firm' to aggregate across all firm businesses. Responses: 200, 422 ### POST /api/v1/billing/ai/pause Pause Ai Features Pause all AI features for the authenticated business. Responses: 201, 422 ### POST /api/v1/billing/ai/resume Resume Ai Features Resume AI features for the authenticated business. Responses: 201, 422 ### GET /api/v1/billing/catalog Get Billing Catalog Return the Stripe price catalog (slug → price details). Responses: 200 ### POST /api/v1/billing/change-plan Change Plan Switch the base subscription plan to a different price. Request body (PlanChangeRequest): - price_id (string, required) Responses: 201, 422 ### POST /api/v1/billing/checkout Create Checkout Session Create a Stripe Checkout session so the user can subscribe. Request body (CheckoutRequest): - price_id (string, required) - success_url (string, required) - cancel_url (string, required) Responses: 201, 422 ### POST /api/v1/billing/firm/change-tier Change Firm Tier Upgrade or downgrade the firm's plan tier, in-app. Request body (FirmTierChangeRequest): - firm_plan_tier (string, required) Responses: 201, 422 ### GET /api/v1/billing/firm/client-usage Get Firm Client Usage Return the firm's active client count, tier cap, and remaining seats. Responses: 200, 422 ### GET /api/v1/billing/grandfathered-summary Get Grandfathered Summary Return a summary of the grandfathered business's current access and available plans. Responses: 200, 422 ### GET /api/v1/billing/invoices Get Invoice History Return the business's Stripe subscription invoice history. Parameters: - limit (query, integer, optional): Max invoices to return Responses: 200, 422 ### POST /api/v1/billing/migrate-from-grandfathered Migrate grandfathered account to a paid plan Create a Stripe Checkout session to move from a grandfathered plan to a paid plan. Request body (GrandfatheredMigrateRequest): - target_plan (string, required) - success_url (string, required) - cancel_url (string, required) - billing_interval (string, optional) Responses: 201, 400, 403, 422 ### POST /api/v1/billing/portal Create Portal Session Create a Stripe Billing Portal session for subscription management. Request body (PortalRequest): - return_url (string, required) Responses: 201, 422 ### GET /api/v1/billing/pricing-config Get Pricing Config Return billing pricing configuration derived from the single discount knob. Responses: 200 ### GET /api/v1/billing/status Get Billing Status Return the current billing state for the authenticated business. Responses: 200, 422 ## bills — Accounts payable — create and manage vendor bills. ### GET /api/v1/bills List bills Retrieve all bills for a business with cursor-based pagination. Can filter by vendor name (fuzzy search) or associated journal entry. Parameters: - vendor_search (query, string, optional): Fuzzy search bills by vendor name (uses PostgreSQL trigram matching) - journal_entry_id (query, string, optional): Filter bills by associated journal entry UUID - payment_method (query, string, optional): Filter bills by payment method: 'Wire Transfer', 'ACH', 'Check', 'Credit Card' - vendor_id (query, string, optional): Filter bills by vendor UUID - status (query, string, optional): Filter by bill status (e.g. draft, approved, received, paid, canceled) - search (query, string, optional): Search bills by bill number, description, or vendor name (case-insensitive partial match) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/bills Create bill Create a new bill in draft status. Optionally include 'recurring' config to also create a recurring template. Request body (BillCreateRequest): - description (string, optional) - currency (string, optional) - vendor_id (string, optional) - type (string, optional) - expected_amount (integer, optional) - expected_paid_on_date (string · date-time | string, optional) - s3_key (string, optional) - recurring (RecurringConfig, optional) - submitted_for_review (boolean, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/bills/aging AP aging summary Returns accounts payable aging data grouped by vendor with Current, 1-30, 31-60, 61-90, and 91+ day buckets. Includes per-bill detail for drill-down. Responses: 200, 401, 403, 422 ### POST /api/v1/bills/ai-create AI-extract draft bills from documents Upload pasted text, PDFs, images, Excel files, or a CSV to extract draft bill entities using AI. Returns draft bills in 'received' status format ready for user review before creation. Request body (BillAICreatePayloadRequest): - pasted_text (string, optional) - pasted_text_s3_key (string, optional) - single_bill_s3_keys (array · string, optional) - multi_bill_s3_key (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/bills/bulk Bulk create bills Create multiple bills from CSV data in a single request. Request body (BillBulkRequest): - s3_key (string, optional) - approve (boolean, optional) - create_as_received (boolean, optional) - attachments (object, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/bills/expense-candidates List expense candidates Return outgoing transactions that may represent bill payments. Filters to posted expense transactions with unallocated balances, useful for the Record Payment modal on the bills page. Parameters: - limit (query, integer, optional): Max results Responses: 200, 401, 403, 422 ### GET /api/v1/bills/metrics Get bill metrics Retrieve aggregated bill metrics for the business: total due and total balance due across all received and partially paid bills. Responses: 200, 401, 403, 422 ### GET /api/v1/bills/payment-suggestions List bill payment suggestions List auto-detected matches between bank transactions and open bills. Parameters: - bill_id (query, string, optional): Filter by bill UUID - status (query, string, optional): Filter by status - min_confidence (query, number, optional): Minimum confidence threshold - start_date (query, string · date, optional): Only include suggestions whose transaction date is on or after this date (YYYY-MM-DD) - end_date (query, string · date, optional): Only include suggestions whose transaction date is on or before this date (YYYY-MM-DD) - limit (query, integer, optional): Max results Responses: 200, 401, 403, 422 ### POST /api/v1/bills/payment-suggestions/bulk-approve Bulk approve bill payment suggestions Accept all pending suggestions above a confidence threshold. Request body (BulkApproveSuggestionsRequest): - min_confidence (number, optional) Responses: 201, 401, 403, 422 ### POST /api/v1/bills/payment-suggestions/trigger Trigger bill payment matching Run the AP matching engine to find bank transactions that match open bills. Request body (TriggerBillMatchingRequest): - min_confidence (number, optional) - auto_match (boolean, optional) - bill_ids (array · string, optional) Responses: 201, 401, 403, 422 ### POST /api/v1/bills/payment-suggestions/{suggestion_id}/accept Accept a bill payment suggestion Accept a suggestion and create the actual BillPayment. Parameters: - suggestion_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/payment-suggestions/{suggestion_id}/reject Reject a bill payment suggestion Reject a suggestion so it is no longer shown. Parameters: - suggestion_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/payments/batch Split one transaction across multiple bills Apply a single bank transaction to multiple bills in one atomic request. Validates the total allocation against the transaction's remaining unallocated balance before creating any payments. Request body (BillBatchPaymentRequest): - transaction_id (string, required) - allocations (array · BillBatchPaymentAllocation, required) → BillBatchPaymentAllocation - paid_on (string · date-time, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/received Create bill in received status Create a new bill directly in 'received' status, ready for payment. Creates both the bill AND journal entry when ledger_id is provided. Request body (BillReceivedCreateRequest): - vendor_id (string, required) - amount (integer, required) - description (string, optional) - bill_number (string, optional) - currency (string, optional) - type (string, optional) - received_on (string · date-time | string, optional) - due_on (string · date-time | string, optional) - s3_key (string, optional) - ledger_id (string, optional) - line_items (array · JournalEntryLineItem, optional) - journal_entry_date (string · date-time | string · date | string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/suggest-payment-priority AI payment priority suggestions Get AI-powered bill payment priority recommendations. Responses: 201, 401, 403, 422 ### POST /api/v1/bills/upload Upload bill document Upload a PDF or image file for a bill. Returns s3_key to use when creating/updating bills. Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/bills/{bill_id} Delete bill Delete a bill and its associated records. Parameters: - bill_id (path, string, required) - force (query, boolean, optional): Force delete even if bill has associated records Responses: 200, 401, 403, 404, 409, 422 ### GET /api/v1/bills/{bill_id} Get bill Retrieve a specific bill by ID. Parameters: - bill_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/bills/{bill_id} Update bill Update an existing bill. Optionally include 'recurring' config to create a recurring template. Parameters: - bill_id (path, string, required) Request body (BillUpdateRequest): - status (integer, optional) - description (string, optional) - bill_number (string, optional) - vendor_id (string, optional) - type (string, optional) - expected_amount (integer, optional) - expected_paid_on_date (string · date-time | string · date | string, optional) - amount (integer, optional) - due_on (string · date-time | string · date | string, optional) - received_on (string · date-time | string · date | string, optional) - s3_key (string, optional) - source_inventory_order_id (string, optional) - payment_method (string, optional) - recurring (RecurringConfig, optional) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/bills/{bill_id}/approval Approve bill Approve a bill and change status to 'forecasted' Parameters: - bill_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/bills/{bill_id}/attachment Attach file to existing bill Attach a document file to an existing bill by updating its s3_key. Parameters: - bill_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/bills/{bill_id}/cancellation Cancel bill Cancel a bill and change status to 'canceled' Parameters: - bill_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/bills/{bill_id}/contract-comparison Compare bill against vendor contract Compare a bill's amount against the vendor's active contract/MSA. Parameters: - bill_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/bills/{bill_id}/journal-entry Generate bill journal entry Create the outstanding AP journal entry for a received bill that has none linked. Parameters: - bill_id (path, string, required) Request body (BillJournalEntryGenerateRequest): - ledger_id (string, required) - journal_entry_date (string · date-time | string · date | string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### POST /api/v1/bills/{bill_id}/journal-entry/link Attach journal entry to bill Link an existing journal entry to a bill. Parameters: - bill_id (path, string, required) Request body (JournalEntryLinkRequest): - journal_entry_id (string, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### GET /api/v1/bills/{bill_id}/payments List bill payments Retrieve all payments for a specific bill. Parameters: - bill_id (path, string, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 404, 422 ### POST /api/v1/bills/{bill_id}/payments Add payment to bill Create a new payment for a specific bill. Parameters: - bill_id (path, string, required) Request body (BillPaymentRequest): - transaction_id (string, required) - amount (integer, required) - paid_on (string · date-time, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/bills/{bill_id}/payments/{payment_id} Remove payment from bill Un-link a payment record from a bill and recalculate status. Parameters: - bill_id (path, string, required) - payment_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/bills/{bill_id}/receipt Mark bill as received Mark a bill as received and create journal entry Parameters: - bill_id (path, string, required) Request body (BillReceivedRequest): - bill_id (string, optional) - amount (integer, required) - ledger_id (string, required) - s3_key (string, optional) - journal_entry_date (string · date-time | string · date | string, optional) - received_on (string · date-time | string · date | string, optional) - due_on (string · date-time | string · date | string, optional) Responses: 200, 400, 401, 403, 404, 422 ## budgets — Financial budgets and forecasts with variance tracking. ### GET /api/v1/budgets List budgets List all budgets for the business with optional filtering. Parameters: - status (query, string, optional): Filter by status: draft, active, closed - fiscal_year (query, integer, optional): Filter by fiscal year - search (query, string, optional): Search by budget name - offset (query, integer, optional): Number of records to skip - limit (query, integer, optional): Maximum records to return - include_total_count (query, boolean, optional): Include total count (expensive - avoid if possible) Responses: 200, 401, 403, 422 ### POST /api/v1/budgets Create budget Create a new budget for the business. Request body (BudgetCreateRequest): - name (string, required) - fiscal_year (integer, required) - start_date (string · date, required) - end_date (string · date, required) - period_type (BudgetPeriodTypeEnum, optional) → BudgetPeriodTypeEnum - description (string, optional) - notes (string, optional) - source_scenario_id (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/budgets/forecast Generate forecast Generate a financial forecast based on historical data. Request body (GenerateForecastRequest): - method (ForecastMethodEnum, optional) → ForecastMethodEnum - lookback_months (integer, optional) - forecast_months (integer, optional) - growth_rate_percent (integer, optional) - use_seasonality (boolean, optional) - include_cash_flow (boolean, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/budgets/forecast/configs List forecast configs List saved forecast configurations. Responses: 200, 401, 403, 422 ### POST /api/v1/budgets/forecast/configs Create forecast config Save a forecast configuration for reuse. Request body (ForecastConfigCreateRequest): - name (string, required) - method (ForecastMethodEnum, optional) → ForecastMethodEnum - lookback_months (integer, optional) - forecast_months (integer, optional) - growth_rate_percent (integer, optional) - use_seasonality (boolean, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/budgets/{budget_id} Delete budget Delete a draft budget. Parameters: - budget_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/budgets/{budget_id} Get budget Get a budget with all its line items. Parameters: - budget_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/budgets/{budget_id} Update budget Update a draft budget's metadata. Parameters: - budget_id (path, string, required) Request body (BudgetUpdateRequest): - name (string, optional) - description (string, optional) - notes (string, optional) - start_date (string · date, optional) - end_date (string · date, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/budgets/{budget_id}/activate Activate budget Set a budget as the active budget (deactivates current active). Parameters: - budget_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/budgets/{budget_id}/copy Copy budget Create a copy of a budget with optional percentage adjustment. Parameters: - budget_id (path, string, required) Request body (BudgetCopyRequest): - name (string, required) - fiscal_year (integer, required) - start_date (string · date, required) - end_date (string · date, required) - adjustment_percent (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/budgets/{budget_id}/lines List budget lines Get all line items for a budget. Parameters: - budget_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/budgets/{budget_id}/lines Add budget line Add a budget line item for a ledger account. Parameters: - budget_id (path, string, required) Request body (BudgetLineCreateRequest): - ledger_id (string, required) - jan_amount (integer, optional) - feb_amount (integer, optional) - mar_amount (integer, optional) - apr_amount (integer, optional) - may_amount (integer, optional) - jun_amount (integer, optional) - jul_amount (integer, optional) - aug_amount (integer, optional) - sep_amount (integer, optional) - oct_amount (integer, optional) - nov_amount (integer, optional) - dec_amount (integer, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/budgets/{budget_id}/lines/{line_id} Delete budget line Remove a budget line. Parameters: - budget_id (path, string, required) - line_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/budgets/{budget_id}/lines/{line_id} Update budget line Update amounts on a budget line. Parameters: - budget_id (path, string, required) - line_id (path, string, required) Request body (BudgetLineUpdateRequest): - jan_amount (integer, optional) - feb_amount (integer, optional) - mar_amount (integer, optional) - apr_amount (integer, optional) - may_amount (integer, optional) - jun_amount (integer, optional) - jul_amount (integer, optional) - aug_amount (integer, optional) - sep_amount (integer, optional) - oct_amount (integer, optional) - nov_amount (integer, optional) - dec_amount (integer, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/budgets/{budget_id}/vs-actual Budget vs Actual Compare budgeted amounts to actual performance. Parameters: - budget_id (path, string, required) - start_date (query, string · date, required): Comparison period start date - end_date (query, string · date, required): Comparison period end date Responses: 200, 401, 403, 404, 422 ## businesses — Business entities — the core tenant in DayZero. ### GET /api/v1/businesses List businesses Retrieve businesses the authenticated user has access to. Parameters: - id (query, string, optional) - name (query, string, optional) - teal_instance (query, string, optional) - stripe_account_id (query, string, optional) - shopify_shop_domain (query, string, optional) - user_id (query, string, optional) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/businesses Create business Create a new business entity with optional integrations. Request body (BusinessCreateRequest): - name (string, required) - default_currency (string, optional) - tax_year_end_month (integer, optional) - mailbox (string, optional) - user_id (string, optional) - create_teal_instance (boolean, optional) - teal_instance (string, optional) - coa_template_id (string, optional) - custom_coa_template (CustomCoaTemplate, optional) - entries_start (string, optional) - firm_id (string, optional) - stripe_account_id (string, optional) - stripe_subscription_id (string, optional) - stripe_customer_id (string, optional) - subscription_items (object, optional) - accounting_engine_type (string, optional) Responses: 201, 400, 401, 403, 409, 422 ### GET /api/v1/businesses/auto-numbering Get auto-numbering settings Get the current auto-numbering configuration for bills and invoices. Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/auto-numbering Update auto-numbering settings Configure automatic bill and invoice number generation. Request body (AutoNumberingSettingsUpdateRequest): - auto_number_bills (boolean, optional) - auto_number_invoices (boolean, optional) - bill_number_prefix (string, optional) - invoice_number_prefix (string, optional) - next_bill_number (integer, optional) - next_invoice_number (integer, optional) - journal_entry_number_prefix (string, optional) - next_journal_entry_number (integer, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/auto-numbering/preview Preview next auto-generated numbers See what the next bill and invoice numbers would be without incrementing. Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/entries-start Update entries start date Set the date from which accounting entries should begin. Request body (UpdateEntriesStartRequest): - business_id (string, optional) - entries_start (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/generate-coa Generate a COA template using AI Uses AI to generate a complete chart of accounts template based on a business description. Request body (CoaGenerateRequest): - business_description (string, required) - accounting_basis (string, optional) - currency (string, optional) - include_inventory (boolean, optional) Responses: 201, 400, 401, 422 ### GET /api/v1/businesses/invoice-settings Get invoice branding settings Get invoice branding configuration including footer text and logo sync status. Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/invoice-settings Update invoice branding settings Update the default footer text shown on invoice PDFs. Request body (InvoiceSettingsUpdateRequest): - invoice_footer (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/invoice-settings/sync-logo Sync logo to Stripe branding Upload the business logo to Stripe for invoice branding. Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/mailbox Check mailbox uniqueness Check if a mailbox is unique; validation and conflicts are handled by the service. Parameters: - mailbox (query, string, required): Mailbox to check for uniqueness Responses: 200, 400, 409, 422, 429 ### GET /api/v1/businesses/nav-preferences Get sidebar navigation preferences Get the per-business set of hidden nav items. Empty = show everything entitled. Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/nav-preferences Update sidebar navigation preferences Replace the per-business hidden nav set. Send an empty list to show everything. Request body (NavPreferencesUpdateRequest): - hidden (array · string, optional) - profile (NavPreferencesProfile, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/nav-preferences/suggest AI-suggest which nav items to hide Given a business description and the hideable nav catalog, returns a proposed set of items to hide (with reasons) plus a one-line profile. Proposal only — nothing is saved; apply via PUT /nav-preferences. Request body (NavSuggestRequest): - description (string, required) - items (array · NavCatalogItem, optional) → NavCatalogItem - signals (object, optional) Responses: 201, 400, 401, 403, 422, 500 ### PUT /api/v1/businesses/slack Save Slack webhook URL Configure a Slack Incoming Webhook URL for this business. Request body (SlackWebhookSaveRequest): - slack_webhook_url (string, optional) - channel_name (string, optional) - preferences (object, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/slack/status Get Slack integration status Check whether a Slack webhook is configured for this business. Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/slack/test Test Slack webhook Send a test message to the business's first configured Slack webhook (legacy). Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/businesses/slack/{webhook_id} Disconnect Slack Remove the business's Slack webhook URL. Parameters: - webhook_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/slack/{webhook_id}/preferences Update Slack notification preferences Toggle which notification categories are sent to Slack. Parameters: - webhook_id (path, string, required) Request body (SlackPreferencesUpdateRequest): - preferences (object, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/slack/{webhook_id}/test Test Slack webhook Send a test message to the business's configured Slack webhook. Parameters: - webhook_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/teal-instances Get Teal instances List Teal instances with cursor-based pagination. Parameters: - enriched (query, boolean, optional): Whether to enrich instances with business information - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 422 ### GET /api/v1/businesses/teal-source-accounts Get Teal source accounts List source accounts connected to Teal, including Plaid-linked and manually uploaded. Responses: 200, 401, 403, 422 ### GET /api/v1/businesses/user-preferences/table-views List saved table views Return all DataTable saved views for the current user in the active business. Responses: 200, 401, 403, 422 ### PATCH /api/v1/businesses/user-preferences/table-views Merge saved table views Upsert one or more table view blobs. Send empty views to remove a key. Include the expected ``version`` (or ``If-Match`` header) to avoid lost updates when multiple clients patch concurrently. Parameters: - If-Match (header, string, optional) Request body (TableViewsPatchRequest): - tables (object, optional) - version (integer, optional) Responses: 200, 400, 401, 403, 409, 422 ### GET /api/v1/businesses/user-preferences/table-views/{table_key} Get saved views for one table Return the saved-views blob for a single DataTable persistKey. Parameters: - table_key (path, string, required) Responses: 200, 401, 403, 422 ### GET /api/v1/businesses/users List business users Get users associated with a business, optionally including advisory firm users. Parameters: - include_advisory_users (query, boolean, optional): Include users from the parent advisory firm if one exists. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 422 ### POST /api/v1/businesses/users Add user to business Grant a user access to this business with a specified role. Request body (BusinessUserAddRequest): - id (string, required) - role (Role, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### DELETE /api/v1/businesses/users/{user_id} Remove user from business Remove a user from a specific business. Parameters: - user_id (path, string, required) Responses: 200, 422 ### PUT /api/v1/businesses/users/{user_id} Update user role Change a user's access level in this business. Parameters: - user_id (path, string, required) Request body (BusinessUserRoleUpdateRequest): - role (Role, required) → Role Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/users/{user_id}/role Get user role Check a user's access level in this business. Parameters: - user_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/validate-coa Validate a custom COA template Validates a custom chart of accounts template against Teal API requirements without creating anything. Responses: 201, 401, 422 ### DELETE /api/v1/businesses/{business_id} Delete business Delete a business and all associated resources (async via Temporal). Parameters: - business_id (path, string, required) - confirm_delete (query, boolean, optional) Responses: 200, 422 ### GET /api/v1/businesses/{business_id} Get business Retrieve full business details including integrations and settings. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id} Update business Update business details. Parameters: - business_id (path, string, required) Request body (BusinessUpdateRequest): - name (string, optional) - logo_url (string, optional) - default_currency (string, optional) - tax_year_end_month (integer, optional) - accounting_basis (string, optional) - mailbox (string, optional) - inventory_mode (string, optional) Responses: 200, 422 ### GET /api/v1/businesses/{business_id}/balance-sheet Get balance sheet (sectioned) Balance sheet grouped by section (Assets, Liabilities, Equity). Each section contains ledger line items with ledger_id, ledger_name, sort_code, and total_amount. Parameters: - business_id (path, string, required) - as_of_date (query, string, required): As-of date (YYYY-MM-DD) - hide_zero_rows (query, boolean, optional): Exclude line items with a zero balance Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/balance-sheet/analyze AI analysis of balance sheet Uses AI to analyze the balance sheet as of a given date and return structured insights. Parameters: - business_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cash-flow-statement Get cash flow statement Cash flow statement (indirect method) grouped by operating, investing, and financing activities. The operating section walks Net Income through non-cash add-backs and working-capital changes to Net Cash from Operating Activities. Includes ASC 230 supplemental disclosures and §5 validation results. All amounts in cents. Parameters: - business_id (path, string, required) - start_date (query, string · date, required): Start date (YYYY-MM-DD) - end_date (query, string · date, required): End date (YYYY-MM-DD) - hide_zero_rows (query, boolean, optional): Exclude line items with zero cash impact Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cash-flow-statement/outflow-breakdown Top categories and vendors by cash outflow Returns the top N ledger categories and counterparties that powered cash outflows during the requested period, ranked by absolute cash leaving. Powers the 'Where the cash went' tile on /books/cash-flow. Parameters: - business_id (path, string, required) - start_date (query, string · date, required): Start date (YYYY-MM-DD) - end_date (query, string · date, required): End date (YYYY-MM-DD) - limit (query, integer, optional): Max rows per list (categories and vendors are returned separately) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cash-flow-statement/periodic Get cash flow statements for multiple periods Batch endpoint returning a cash flow statement for each period in the requested date range. Avoids N round-trips for monthly/quarterly views. All amounts in cents. Parameters: - business_id (path, string, required) - start_date (query, string · date, required): Start date (YYYY-MM-DD) - end_date (query, string · date, required): End date (YYYY-MM-DD) - grouping (query, string, optional): Period grouping: 'monthly' or 'quarterly' - hide_zero_rows (query, boolean, optional): Exclude line items with zero cash impact Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cash-flow/analyze AI analysis of cash flow statement Uses AI to analyze the cash flow statement for the given period and return structured insights. Parameters: - business_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/income-statement Get income statement (sectioned) Income statement grouped by section. Each section contains ledger line items with period values as top-level keys. Parameters: - business_id (path, string, required) - start_date (query, string, required): Start date (YYYY-MM-DD) - end_date (query, string, required): End date (YYYY-MM-DD) - hide_zero_rows (query, boolean, optional): Exclude line items with all-zero values - period_granularity (query, string, optional): Period grouping: monthly, quarterly, or yearly - tag_ids (query, array · string, optional): Filter by tag UUIDs — only line entries tagged with at least one of these tags are included Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/income-statement-flat Get income statement (flat) Pre-flattened income statement rows for display. No transformation needed. Parameters: - business_id (path, string, required) - start_date (query, string, required): Start date (YYYY-MM-DD) - end_date (query, string, required): End date (YYYY-MM-DD) - hide_zero_rows (query, boolean, optional): Exclude line items with all-zero values Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/income-statement/analyze AI analysis of income statement Uses AI to analyze the income statement for the given period and return structured insights. Parameters: - business_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/ledger/{ledger_id}/statement Get ledger statement for a date range Returns beginning balance, transactions within the range, and ending balance for a specific ledger account. Parameters: - business_id (path, string, required) - ledger_id (path, string, required) - start_date (query, string, required): Start date (YYYY-MM-DD) - end_date (query, string, required): End date (YYYY-MM-DD) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/ledger/{ledger_id}/vendor-breakdown Get vendor/counterparty breakdown for a ledger account Returns vendors/counterparties that contributed to a ledger account total in the given date range, with amounts and percentages. Parameters: - business_id (path, string, required) - ledger_id (path, string, required) - start_date (query, string, required): Start date (YYYY-MM-DD) - end_date (query, string, required): End date (YYYY-MM-DD) - cash_flow (query, boolean, optional): When true, restrict to cash-touching journal entries and use cash flow sign convention Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/ledger/{ledger_id}/vendor-period-matrix Get vendor breakdown across all periods for a ledger Returns vendor/counterparty breakdown with per-period amounts for pivot-table display. Parameters: - business_id (path, string, required) - ledger_id (path, string, required) - start_date (query, string, required): Start date (YYYY-MM-DD) - end_date (query, string, required): End date (YYYY-MM-DD) - period_granularity (query, string, optional): monthly, quarterly, or yearly - cash_flow (query, boolean, optional): When true, restrict to cash-touching journal entries and use cash flow sign convention Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/ledger/{ledger_id}/vendor-transactions Get individual transactions for a vendor within a ledger Returns individual line entries for a specific counterparty within a ledger and date range. Parameters: - business_id (path, string, required) - ledger_id (path, string, required) - counterparty_name (query, string, required): Counterparty/vendor name - start_date (query, string, required): Start date (YYYY-MM-DD) - end_date (query, string, required): End date (YYYY-MM-DD) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/{business_id}/line-entries/recategorize Recategorize line entries Move line entries to a different ledger. Used for journal-entry lines that have no bank transaction. Parameters: - business_id (path, string, required) Request body (RecategorizeLineEntriesRequest): - line_entry_ids (array · string, required) - ledger_id (string, required) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/stripe-subscription Update Stripe subscription Link a Stripe subscription to a business. Parameters: - business_id (path, string, required) Request body (UpdateStripeSubscriptionRequest): - stripe_subscription_id (string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/sync Get sync status Get the current Teal sync status for a business. Lightweight endpoint for polling. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## catalog-items ### GET /api/v1/catalog-items List catalog items List Products & Services catalog items for the business. Parameters: - active_only (query, boolean, optional): Only show active items. - item_type (query, string, optional): Filter by type: service, non_inventory, inventory, bundle. - search (query, string, optional): Fuzzy match on name or SKU. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/catalog-items Create catalog item Create a new Products & Services catalog item. Request body (CatalogItemCreateRequest): - name (string, required) - item_type (string, optional) - sku (string, optional) - description (string, optional) - unit_price (integer, optional) - purchase_cost (integer, optional) - is_sold (boolean, optional) - is_purchased (boolean, optional) - taxable (boolean, optional) - income_ledger_id (string, optional) - expense_ledger_id (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### DELETE /api/v1/catalog-items/{item_id} Deactivate catalog item Deactivate a catalog item (soft delete). Parameters: - item_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/catalog-items/{item_id} Get catalog item Get details of a specific catalog item. Parameters: - item_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/catalog-items/{item_id} Update catalog item Update an existing catalog item. Parameters: - item_id (path, string, required) Request body (CatalogItemUpdateRequest): - name (string, optional) - item_type (string, optional) - sku (string, optional) - description (string, optional) - unit_price (integer, optional) - purchase_cost (integer, optional) - is_sold (boolean, optional) - is_purchased (boolean, optional) - taxable (boolean, optional) - income_ledger_id (string, optional) - expense_ledger_id (string, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 409, 422 ## categorization-profile ### GET /api/v1/categorization-profile Get the current business's categorization profile Returns the categorization profile (accounting basis, key personnel, convention overrides, etc.) used by the engine and AI prompt for this business. Returns ``{}`` if no profile has been configured. Responses: 200, 401, 403, 422 ### PUT /api/v1/categorization-profile Upsert the current business's categorization profile Partial-update or create the categorization profile. Accepted keys: accounting_basis, industry, key_personnel, convention_overrides, tax_remittance_ledger_id, notes. Responses: 200, 400, 401, 403, 422 ## cfo-portal ### GET /api/v1/businesses/{business_id}/cfo-portal/alerts Get CFO financial alerts Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/ap-aging Get CFO AP aging Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/ar-aging Get CFO AR aging Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/ask Ask the CFO portal AI assistant a question Parameters: - business_id (path, string, required) Request body (CfoPortalAskRequest): - question (string, required) - context_section (string, optional) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/benchmarks Get CFO benchmarks Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/board-deck Get CFO board deck summary Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/board-deck/memo Generate an AI board memo/report from CFO board-deck data Generate a board-ready memo using the latest board-deck metrics. Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/board-deck/memo/export Export AI board memo as PDF or HTML Export the current AI board memo in a user-selected format. Parameters: - business_id (path, string, required) Request body (CfoPortalBoardMemoExportRequest): - title (string, optional) - memo_markdown (string, required) - generated_at (string · date-time, optional) - format (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/board-deck/pdf Download board deck summary as PDF Build and download the board deck summary PDF on demand. Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/buckets List all managerial buckets (default + custom) Return all managerial buckets available for this business. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/buckets Create a custom managerial bucket Create a new custom managerial bucket for categorizing GL accounts. Parameters: - business_id (path, string, required) Request body (CustomBucketCreateRequest): - key (string, required) - label (string, required) - color (string, optional) - classification (string, optional) - sort_order (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/buckets/{bucket_key} Delete or disable a managerial bucket Delete or disable a managerial bucket. Parameters: - business_id (path, string, required) - bucket_key (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/cfo-portal/buckets/{bucket_key} Update a managerial bucket Update a managerial bucket. Parameters: - business_id (path, string, required) - bucket_key (path, string, required) Request body (CustomBucketUpdateRequest): - label (string, optional) - color (string, optional) - classification (string, optional) - sort_order (integer, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/budget-vs-actual Get CFO budget vs actual Get the budget-vs-actual section with optional budget/date filters. Parameters: - business_id (path, string, required) - budget_id (query, string, optional) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/cash-forecast Get CFO 13-week cash forecast Get the cash forecast section with optional budget selection. Parameters: - business_id (path, string, required) - budget_id (query, string, optional) - apply_overrides (query, boolean, optional): When true, the working override scope is folded into the computed cell values. Used by the Demand Planner tab. The live Cash Flow Forecast tab leaves this off so unsaved what-if values don't bleed across views. - include_projected_replenishment (query, boolean, optional): When true, projected (not-yet-placed) replenishment POs from the SKU demand-forecast engine are added to a separate Projected Inventory row so operators see the cash impact of reorders they'll need to make. Off by default to keep the live Forecast tab byte-identical. Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/demand-plan Evaluate a what-if cash forecast scenario Run the canonical forecast twice (clean baseline + scenario) and Parameters: - business_id (path, string, required) Request body (DemandPlanScenarioRequest): - scenario_description (string, required) - overrides (array · DemandPlanScenarioOverride, optional) → DemandPlanScenarioOverride - inline_one_time_items (array · DemandPlanScenarioOneTime, optional) → DemandPlanScenarioOneTime - weeks (integer, optional) - budget_id (string, optional) - excluded_budget_ledger_ids (array · string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/one-time-items List one-time payment items applied to the cash forecast List all one-time payment items for this business. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/one-time-items Create a one-time payment item Create a one-time payment item for the cash forecast. Parameters: - business_id (path, string, required) Request body (CashForecastOneTimeItemCreate): - label (string, required) - amount_cents (integer, required) - applies_on (string · date, required) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/one-time-items/{item_id} Delete a one-time payment item Delete a one-time payment item. Parameters: - business_id (path, string, required) - item_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/one-time-items/{item_id} Update a one-time payment item Partially update a one-time payment item. Parameters: - business_id (path, string, required) - item_id (path, string, required) Request body (CashForecastOneTimeItemUpdate): - label (string, optional) - amount_cents (integer, optional) - applies_on (string · date, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/overrides Clear all cash forecast overrides in a scope Clear every override in the matching scope (working set or version). Parameters: - business_id (path, string, required) - version_id (query, string, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/overrides List cell-level overrides for the cash forecast List overrides for either the working set or a saved version. Parameters: - business_id (path, string, required) - version_id (query, string, optional): Saved version id to scope overrides to. Omit for the user's working override set. Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/overrides Replace all cash forecast overrides in a scope Atomically replace every override row in the matching scope. Parameters: - business_id (path, string, required) Request body (CashForecastOverridesUpsert): - version_id (string, optional) - overrides (array · CashForecastOverrideInput, optional) → CashForecastOverrideInput Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/overrides/{override_id} Delete a single cash forecast override row Delete a single override row by id. Parameters: - business_id (path, string, required) - override_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/settings Update Cash Forecast configuration Save Cash Forecast settings (excluded budget ledgers). Parameters: - business_id (path, string, required) Request body (CashForecastSettingsUpdateRequest): - excluded_budget_ledger_ids (array · string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/versions List saved cash forecast versions List saved versions for this business (summary view, no snapshot). Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/versions Save the current cash forecast configuration as a named version Snapshot the current cash forecast configuration + output. Parameters: - business_id (path, string, required) Request body (CashForecastVersionCreate): - name (string, required) - description (string, optional) - config (CashForecastVersionConfig, optional) → CashForecastVersionConfig Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/versions/{version_id} Delete a saved cash forecast version Delete a saved version and any overrides pinned to it. Parameters: - business_id (path, string, required) - version_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/versions/{version_id} Get a saved cash forecast version Get full detail for a saved version, including config + snapshot. Parameters: - business_id (path, string, required) - version_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/versions/{version_id} Rename or describe a saved version Update a saved version's name or description. Parameters: - business_id (path, string, required) - version_id (path, string, required) Request body (CashForecastVersionUpdate): - name (string, optional) - description (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/cash-forecast/versions/{version_id}/apply Re-apply a saved version's config to the live forecast Load a saved version into the user's working scope. Parameters: - business_id (path, string, required) - version_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/configuration Get CFO configuration Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/debt-schedule Get CFO debt schedule Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/approve-contract Approve parsed debt contract and create debt instrument Create a debt instrument from approved parsed contract terms. Parameters: - business_id (path, string, required) Request body (DebtContractApproveRequest): - contract (ParsedDebtContract, required) → ParsedDebtContract - contract_s3_key (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id}/payments Record a payment against a debt instrument Record a payment on a loan, posting a balanced principal/interest entry. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Request body (DebtPaymentRequest): - payment_amount_cents (integer, required) - cash_ledger_id (string, required) - payment_date (string · date, optional) - memo (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/parse-contract Parse debt contract into a draft payment schedule Parse an uploaded lending contract into structured terms and schedule. Parameters: - business_id (path, string, required) Request body (DebtContractParseRequest): - s3_key (string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/flux Get CFO flux analysis Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/landed-cogs Get CFO landed COGS Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/managerial-pl Get CFO managerial P&L Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/monthly-close Get CFO monthly close status Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/po-forecast-settings List open POs and which forecast column they flow through List every open PO with its forecast attribution. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/purchase-orders Get CFO purchase order pipeline Parameters: - business_id (path, string, required) - status (query, string, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/recipe-book Get CFO recipe book Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/recipe-book/auto-assign AI auto-assign ledgers to managerial buckets AI auto-assign ledgers to managerial buckets based on account names. Parameters: - business_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/cfo-portal/recipe-book/managerial-builders Upsert managerial builder line account selections Save account selections for CM1/CM2 and cash-outflow builder lines. Parameters: - business_id (path, string, required) Request body (ManagerialBuildersUpdateRequest): - builders (array · ManagerialBuilderItem, optional) → ManagerialBuilderItem Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/cfo-portal/recipe-book/managerial-mappings Upsert managerial bucket assignments for GL accounts Save bucket assignments edited on the Recipe Book page. Parameters: - business_id (path, string, required) Request body (ManagerialMappingUpsertRequest): - mappings (array · ManagerialMappingItem, optional) → ManagerialMappingItem Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/reports-documents Get CFO reports and documents Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/shipping-analysis Get CFO shipping analysis Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/shopify Get CFO Shopify metrics Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/statements Get CFO statement bundle Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/summary Get CFO portal summary Parameters: - business_id (path, string, required) - include_series (query, boolean, optional): When true, include the 13-week trailing P&L series in ``data.pulse.weekly_series`` and run the heavier DB fan-out. - include_weekly_pl (query, boolean, optional): When true, include the 4-week P&L table rows in ``data.weekly_pl_rows``. Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/unit-economics Get CFO unit economics Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/cfo-portal/unit-economics/settings Update Unit Economics configuration Save Unit Economics settings (Shopify vs manual AOV override). Parameters: - business_id (path, string, required) Request body (UnitEconomicsSettingsUpdateRequest): - aov_mode (string, required) - manual_aov_cents (integer, optional) - shopify_sections (array · string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/valuation Get CFO valuation inputs Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/vendor-spend Get CFO vendor spend Get vendor spend with optional date range, grouping mode, and source filters. Parameters: - business_id (path, string, required) - start_date (query, string · date, optional) - end_date (query, string · date, optional) - group_by (query, string, optional) - sources (query, array · string, optional) Responses: 200, 401, 403, 404, 422 ## cfo-projection ### GET /api/v1/cfo-projection/actuals Get trailing actuals Trailing income statement actuals shaped for the projection page. Pass `start_month`/`end_month` (YYYY-MM) to pin an explicit window; otherwise the last `trailing_months` closed months are returned. Parameters: - trailing_months (query, integer, optional): Number of trailing months (used when start/end unset) - start_month (query, string, optional): Inclusive window start in YYYY-MM. Required together with end_month. - end_month (query, string, optional): Inclusive window end in YYYY-MM. Required together with start_month. - hide_zero_rows (query, boolean, optional): Exclude line items with all-zero values Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/cfo-projection/scenarios List scenarios List all projection scenarios for the business. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/cfo-projection/scenarios Create scenario Create a new projection scenario. Drivers are auto-seeded from trailing actuals when omitted. Request body (ScenarioCreateRequest): - name (string, optional) - base_month (string · date, optional) - projection_months (integer, optional) - revenue_drivers (object, optional) - cost_classifications (object, optional) - variable_drivers (object, optional) - fixed_drivers (object, optional) - monthly_overrides (object, optional) - tax_rate (number, optional) - interest_income_monthly (integer, optional) - interest_expense_monthly (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/cfo-projection/scenarios/active Get active scenario Get the currently active projection scenario, or null. Responses: 200, 401, 403, 422 ### POST /api/v1/cfo-projection/scenarios/from-budget/{budget_id} Create scenario from budget Derive a new projection scenario from an existing budget's line items. Parameters: - budget_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/cfo-projection/scenarios/{scenario_id} Delete scenario Soft delete a projection scenario. Parameters: - scenario_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/cfo-projection/scenarios/{scenario_id} Get scenario Get a projection scenario by ID. Parameters: - scenario_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/cfo-projection/scenarios/{scenario_id} Update scenario Partial update of scenario drivers. Parameters: - scenario_id (path, string, required) Request body (ScenarioUpdateRequest): - name (string, optional) - projection_months (integer, optional) - revenue_drivers (object, optional) - cost_classifications (object, optional) - variable_drivers (object, optional) - fixed_drivers (object, optional) - monthly_overrides (object, optional) - tax_rate (number, optional) - interest_income_monthly (integer, optional) - interest_expense_monthly (integer, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/cfo-projection/scenarios/{scenario_id}/projection Compute projection Compute a full N-month projected income statement from scenario drivers. Parameters: - scenario_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/cfo-projection/scenarios/{scenario_id}/suggest-tax-rate Suggest tax rates Use AI to suggest effective income tax rates for a projection scenario. Parameters: - scenario_id (path, string, required) Responses: 201, 401, 403, 404, 422 ## client-messages — Messaging between advisory firms and their clients. ### GET /api/v1/client-messages/conversations List client conversations List conversations for the current business. Parameters: - status (query, string, optional): Filter by status - category (query, string, optional): Filter by category - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional) - include_total_count (query, boolean, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/client-messages/conversations Create a conversation Start a new conversation with the advisory firm. Request body (CreateConversationRequest): - subject (string, required) - category (ConversationCategory, optional) → ConversationCategory - message (string, required) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/client-messages/conversations/{conversation_id} Get conversation detail Get a conversation with all messages. Marks messages as read. Parameters: - conversation_id (path, string, required) Responses: 200, 401, 404, 422 ### POST /api/v1/client-messages/conversations/{conversation_id}/messages Reply to a conversation (client) Add a message to an existing conversation as a client. Parameters: - conversation_id (path, string, required) Request body (CreateMessageRequest): - body (string, required) Responses: 201, 400, 401, 404, 422 ### GET /api/v1/client-messages/firm/conversations List client conversations (firm) List all client conversations across the firm's businesses. Parameters: - firm_id (query, string, optional): Advisory firm ID (defaults to user's first firm) - business_id (query, string, optional): Filter by business - status (query, string, optional): Filter by status - category (query, string, optional): Filter by category - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional) - include_total_count (query, boolean, optional) Responses: 200, 401, 403, 422 ### GET /api/v1/client-messages/firm/conversations/{conversation_id} Get conversation detail (firm) Get a conversation with all messages as an advisor. Parameters: - conversation_id (path, string, required) - firm_id (query, string, optional): Advisory firm ID Responses: 200, 401, 403, 404, 422 ### POST /api/v1/client-messages/firm/conversations/{conversation_id}/messages Reply to a conversation (advisor) Add a message to a client conversation as an advisor. Parameters: - conversation_id (path, string, required) - firm_id (query, string, optional): Advisory firm ID Request body (CreateMessageRequest): - body (string, required) Responses: 201, 400, 401, 403, 404, 422 ### PUT /api/v1/client-messages/firm/conversations/{conversation_id}/status Update conversation status Change a conversation's status (resolve, reopen, etc.). Parameters: - conversation_id (path, string, required) - firm_id (query, string, optional): Advisory firm ID Request body (UpdateConversationStatusRequest): - status (ConversationStatus, required) → ConversationStatus Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/client-messages/firm/unread-count Unread conversation count (firm) Get unread count for the advisor sidebar badge. Parameters: - firm_id (query, string, optional): Advisory firm ID Responses: 200, 401, 403, 422 ### GET /api/v1/client-messages/unread-count Unread conversation count Get the number of conversations with unread messages for sidebar badge. Responses: 200, 401, 422 ## consolidated-reports ### GET /api/v1/advisory-firms/{firm_id}/consolidated-reports/balance-sheet Get consolidated balance sheet Uneliminated balance sheet roll-up across all businesses assigned to the advisory firm. Parameters: - firm_id (path, string, required) - as_of_date (query, string, required): Point-in-time date (YYYY-MM-DD) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/consolidated-reports/pnl Get consolidated P&L Uneliminated income statement roll-up across all businesses assigned to the advisory firm. Parameters: - firm_id (path, string, required) - start_date (query, string, required): Period start (YYYY-MM-DD) - end_date (query, string, required): Period end (YYYY-MM-DD) Responses: 200, 400, 401, 403, 404, 422 ## contractors ### GET /api/v1/contractors List contractors List vendors, optionally only those flagged as 1099 contractors. Parameters: - only_1099 (query, boolean, optional): Only 1099-flagged vendors. Responses: 200, 401, 403, 422 ### GET /api/v1/contractors/1099-report 1099 year-end report Total payments per 1099 contractor for the tax year. Parameters: - year (query, integer, required): Tax year, e.g. 2025. Responses: 200, 400, 401, 403, 422 ### PUT /api/v1/contractors/{vendor_id} Update contractor 1099 fields Set the 1099 flag, tax id, tax id type, and default box. Parameters: - vendor_id (path, string, required) Request body (ContractorUpdateRequest): - is_1099_contractor (boolean, optional) - tax_id (string, optional) - tax_id_type (string, optional) - default_1099_box (string, optional) Responses: 200, 400, 401, 403, 404, 422 ## counterparty-defaults ### GET /api/v1/counterparty-defaults List counterparty defaults List all counterparty defaults configured for the current business. Responses: 200, 422 ### POST /api/v1/counterparty-defaults Create a counterparty default Create a new counterparty default. Request body (CounterpartyDefaultCreate): - registry_id (string, optional) - counterparty_name (string, optional) - default_ledger_id (string, required) - memo (string, optional) - mark_personal (boolean, optional) Responses: 201, 422 ### DELETE /api/v1/counterparty-defaults/{default_id} Delete a counterparty default Delete a counterparty default. Parameters: - default_id (path, string, required) Responses: 200, 422 ### PATCH /api/v1/counterparty-defaults/{default_id} Update a counterparty default Update fields on an existing counterparty default. Parameters: - default_id (path, string, required) Request body (CounterpartyDefaultUpdate): - default_ledger_id (string, optional) - memo (string, optional) - mark_personal (boolean, optional) Responses: 200, 422 ## credit-memos — Customer credits that can be applied to invoices. ### GET /api/v1/credit-memos List credit memos List all credit memos for the business with optional filtering. Parameters: - customer_id (query, string, optional): Filter by customer ID - status (query, CreditMemoStatusEnum, optional): Filter by status - has_remaining_balance (query, boolean, optional): Only show memos with remaining balance - search (query, string, optional): Search by credit memo number (e.g. CM-0001) - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/credit-memos Create credit memo Create a new credit memo in draft status. Request body (CreditMemoCreate): - customer_id (string, required) - amount_cents (integer, required) - reason (CreditMemoReasonEnum, optional) → CreditMemoReasonEnum - description (string, optional) - invoice_id (string, optional) - issue_date (string · date, optional) - line_items (array · CreditMemoLineItem, optional) - internal_notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/credit-memos/customers/{customer_id}/credits Get customer available credits Get summary of available credits for a customer. Parameters: - customer_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/credit-memos/suggest AI credit memo suggestions Get AI-powered credit memo suggestions based on customer patterns. Responses: 201, 401, 403, 422 ### DELETE /api/v1/credit-memos/{credit_memo_id} Delete credit memo Delete a draft credit memo. Parameters: - credit_memo_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/credit-memos/{credit_memo_id} Get credit memo Get a specific credit memo by ID. Parameters: - credit_memo_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/credit-memos/{credit_memo_id} Update credit memo Update a draft credit memo. Parameters: - credit_memo_id (path, string, required) Request body (CreditMemoUpdate): - amount_cents (integer, optional) - reason (CreditMemoReasonEnum, optional) - description (string, optional) - invoice_id (string, optional) - issue_date (string · date, optional) - line_items (array · CreditMemoLineItem, optional) - internal_notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/credit-memos/{credit_memo_id}/apply Apply credit memo to invoice Apply a portion or all of a credit memo to an invoice. Parameters: - credit_memo_id (path, string, required) Request body (CreditMemoApplicationCreate): - invoice_id (string, required) - amount_cents (integer, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/credit-memos/{credit_memo_id}/issue Issue credit memo Issue a draft credit memo, making it available for application. Parameters: - credit_memo_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/credit-memos/{credit_memo_id}/void Void credit memo Void a credit memo. Requires a reason. Parameters: - credit_memo_id (path, string, required) Request body (CreditMemoVoid): - reason (string, required) Responses: 201, 400, 401, 403, 404, 422 ## customer-statements ### GET /api/v1/customer-statements/{customer_id} Get customer statement Build an A/R statement for a customer over a date range. Parameters: - customer_id (path, string, required) - start_date (query, string · date, optional): Start of the statement period (defaults to 1 year ago). - end_date (query, string · date, optional): End of the statement period (defaults to today). - as_of_date (query, string · date, optional): Aging as-of date (defaults to end_date). Responses: 200, 401, 403, 404, 422 ### POST /api/v1/customer-statements/{customer_id}/send Email customer statement Build and email an A/R statement to the customer. Parameters: - customer_id (path, string, required) Request body (SendStatementRequest): - start_date (string · date, optional) - end_date (string · date, optional) - as_of_date (string · date, optional) - custom_message (string, optional) - cc_email (string, optional) Responses: 201, 400, 401, 403, 404, 422 ## customers — Customer records for invoicing and AR. ### GET /api/v1/customers List customers Retrieve all customers for the business with optional filtering. Parameters: - id (query, string, optional): Filter by specific customer ID - email (query, string, optional): Filter by customer email address - search (query, string, optional): Fuzzy search customers by name (uses trigram matching) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/customers Create customer Create a new customer record for the business. Request body (CustomerCreateRequest): - name (string, required) - email (string · email, required) - address (string, optional) - phone (string, optional) - tax_id (string, optional) - website (string, optional) - notes (string, optional) - credit_limit_cents (integer, optional) - default_payment_term_id (string, optional) - status (CounterpartyStatus, optional) → CounterpartyStatus - category (string, optional) - invoice_footer (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/customers/analyze-payment-behavior AI payment behavior analysis Get AI-powered customer payment behavior analysis. Responses: 201, 401, 403, 422 ### POST /api/v1/customers/auto-categorize Bulk AI auto-categorize uncategorized customers Schedule AI categorization for every customer in the business that currently has no category. Returns immediately; the actual LLM calls and DB writes happen in a background task. Refresh the customer list to observe progress. Responses: 201, 401, 403, 404, 422 ### POST /api/v1/customers/bulk-upload Bulk upload customers from CSV Upload a CSV file to create multiple customers at once. Duplicate emails are skipped. Returns created/skipped counts and per-row errors. Responses: 201, 400, 401, 403, 422 ### GET /api/v1/customers/bulk-upload/template Download bulk customer upload CSV template Get a sample CSV template with the expected columns for bulk customer upload. Responses: 200, 401, 403, 422 ### POST /api/v1/customers/categorize AI categorize customer Use AI to suggest a category for a customer based on their information. Parameters: - name (query, string, required): Customer name - email (query, string, optional): Customer email - website (query, string, optional): Customer website - address (query, string, optional): Customer address Responses: 201, 401, 403, 422 ### POST /api/v1/customers/check-duplicates Check for duplicate customers Check if a customer already exists using name, email, tax ID similarity. Parameters: - name (query, string, required): Customer name to check - email (query, string, optional): Email to check - tax_id (query, string, optional): Tax ID to check - exclude_id (query, string, optional): Customer ID to exclude (for updates) Responses: 201, 401, 403, 422 ### DELETE /api/v1/customers/{customer_id} Delete customer Soft delete a customer record. The customer is marked as deleted and hidden from list endpoints, but remains retrievable by ID so existing references on invoices and other records stay intact. Parameters: - customer_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/customers/{customer_id} Get customer Retrieve a specific customer by their unique ID. Parameters: - customer_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/customers/{customer_id} Update customer Update an existing customer's information. Parameters: - customer_id (path, string, required) Request body (CustomerUpdateRequest): - id (string, optional) - name (string, optional) - email (string · email, optional) - address (string, optional) - phone (string, optional) - tax_id (string, optional) - website (string, optional) - notes (string, optional) - credit_limit_cents (integer, optional) - default_payment_term_id (string, optional) - status (CounterpartyStatus, optional) - category (string, optional) - invoice_footer (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/customers/{customer_id}/contacts List customer contacts Retrieve all contacts for a customer. Parameters: - customer_id (path, string, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 404, 422 ### POST /api/v1/customers/{customer_id}/contacts Create customer contact Add a new contact to a customer. Parameters: - customer_id (path, string, required) Request body (EntityContactCreateRequest): - name (string, required) - email (string · email, optional) - phone (string, optional) - role (string, optional) - is_primary (boolean, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/customers/{customer_id}/contacts/{contact_id} Delete customer contact Delete a customer contact. Parameters: - customer_id (path, string, required) - contact_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/customers/{customer_id}/contacts/{contact_id} Get customer contact Retrieve a specific customer contact. Parameters: - customer_id (path, string, required) - contact_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/customers/{customer_id}/contacts/{contact_id} Update customer contact Update an existing customer contact. Parameters: - customer_id (path, string, required) - contact_id (path, string, required) Request body (EntityContactUpdateRequest): - name (string, optional) - email (string · email, optional) - phone (string, optional) - role (string, optional) - is_primary (boolean, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ## daily-brief ### GET /api/v1/books/daily-brief Books AI Daily Brief Aggregated home-screen payload for the Books shell. Returns four tiles: categorization, AP drafts, anomalies, close score. Each tile is computed independently and degrades gracefully if its upstream data isn't ready. Responses: 200, 401, 403, 422 ### GET /api/v1/books/standup Books AI proactive morning standup Returns the proactive morning post Books AI would publish into the home shell chat: a greeting, a markdown summary, four highlight KPIs, and a list of action cards with pre-wired endpoints (Draft void, Approve bill, View blockers). The frontend renders this as the first message in the home conversation so the user can act without prompting. Responses: 200, 401, 403, 422 ## delayed-charges ### GET /api/v1/delayed-charges List delayed charges List all delayed charges for the business with optional filtering. Parameters: - search (query, string, optional): Search delayed charges by description - customer_id (query, string, optional): Filter by customer ID - status (query, DelayedChargeStatusEnum, optional): Filter by status - type (query, DelayedChargeTypeEnum, optional): Filter by type - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/delayed-charges Create delayed charge Create a new delayed charge or credit in pending status. Request body (DelayedChargeCreate): - type (string, optional) - customer_id (string, required) - amount (integer, required) - description (string, required) - line_items (array · DelayedChargeLineItem, optional) - service_date (string, optional) - internal_notes (string, optional) - currency (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/delayed-charges/customers/{customer_id}/pending Get customer pending charges Get pending delayed charges for a specific customer. Parameters: - customer_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/delayed-charges/{charge_id} Delete delayed charge Delete a pending delayed charge. Parameters: - charge_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/delayed-charges/{charge_id} Get delayed charge Get a specific delayed charge by ID. Parameters: - charge_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/delayed-charges/{charge_id} Update delayed charge Update a pending delayed charge. Parameters: - charge_id (path, string, required) Request body (DelayedChargeUpdate): - type (string, optional) - customer_id (string, optional) - amount (integer, optional) - description (string, optional) - line_items (array · DelayedChargeLineItem, optional) - service_date (string, optional) - internal_notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ## document-requests ### GET /api/v1/document-requests List document requests (client) List documents your accountant has asked you to upload. Parameters: - status (query, string, optional): Filter by status: open, fulfilled, cancelled - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional) - include_total_count (query, boolean, optional) Responses: 200, 401, 403, 422 ### GET /api/v1/document-requests/count Open document request count (client) Number of pending document requests for the sidebar badge. Responses: 200, 401, 422 ### GET /api/v1/document-requests/firm List document requests (firm) List all document requests across the firm's businesses. Parameters: - firm_id (query, string, optional): Advisory firm ID - business_id (query, string, optional): Filter by business - status (query, string, optional): Filter by status - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional) - include_total_count (query, boolean, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/document-requests/firm/businesses/{business_id} Request a document from a client (firm) Ask a client business to upload a specific document. Parameters: - business_id (path, string, required) Request body (CreateDocumentRequest): - title (string, required) - description (string, optional) - due_date (string · date, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/document-requests/firm/count Open document request count (firm) Number of still-pending document requests across the firm. Parameters: - firm_id (query, string, optional): Advisory firm ID Responses: 200, 401, 403, 422 ### POST /api/v1/document-requests/firm/{request_id}/cancel Cancel a document request (firm) Withdraw a request the client no longer needs to fulfill. Parameters: - request_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/document-requests/firm/{request_id}/download Download an uploaded document (firm) Get a short-lived link to the file the client uploaded. Parameters: - request_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/document-requests/{request_id}/download Download an uploaded document (client) Get a short-lived link to the file you uploaded. Parameters: - request_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/document-requests/{request_id}/fulfill Upload a requested document (client) Securely upload the file your accountant requested. Parameters: - request_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## estimates ### GET /api/v1/estimates List estimates List all estimates for the business with optional filtering. Parameters: - search (query, string, optional): Search estimates by description - customer_id (query, string, optional): Filter by customer ID - status (query, EstimateStatusEnum, optional): Filter by status - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/estimates Create estimate Create a new estimate in draft status. Request body (EstimateCreate): - customer_id (string, required) - line_items (array · EstimateLineItem, required) → EstimateLineItem - due_date (string, optional) - expiration_date (string, optional) - description (string, optional) - internal_notes (string, optional) - currency (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/estimates/{estimate_id} Delete estimate Delete a draft estimate. Parameters: - estimate_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/estimates/{estimate_id} Get estimate Get a specific estimate by ID. Parameters: - estimate_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/estimates/{estimate_id} Update estimate Update a draft or sent estimate. Parameters: - estimate_id (path, string, required) Request body (EstimateUpdate): - customer_id (string, optional) - line_items (array · EstimateLineItem, optional) - due_date (string, optional) - expiration_date (string, optional) - description (string, optional) - internal_notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/estimates/{estimate_id}/accept Accept estimate Accept an estimate (sent -> accepted). Parameters: - estimate_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/estimates/{estimate_id}/convert Convert estimate to invoice Convert an accepted or draft estimate to an invoice. Parameters: - estimate_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/estimates/{estimate_id}/decline Decline estimate Decline an estimate (sent -> declined). Parameters: - estimate_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/estimates/{estimate_id}/send Send estimate Send an estimate to the customer (draft -> sent). Parameters: - estimate_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## expense-reports ### GET /api/v1/expense-reports List expense reports List employee expense reports with optional filtering. Parameters: - status (query, ExpenseReportStatusEnum, optional): Filter by status - search (query, string, optional): Search title, description, or number - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/expense-reports Create expense report Create a draft expense report with line items. Request body (ExpenseReportCreateRequest): - title (string, optional) - description (string, optional) - currency (string, optional) - lines (array · ExpenseReportLineInput, required) → ExpenseReportLineInput Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/expense-reports/{report_id} Delete expense report Delete a draft expense report. Parameters: - report_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/expense-reports/{report_id} Get expense report Get a single expense report with line items. Parameters: - report_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/expense-reports/{report_id} Update expense report Update a draft expense report. Parameters: - report_id (path, string, required) Request body (ExpenseReportUpdateRequest): - title (string, optional) - description (string, optional) - lines (array · ExpenseReportLineInput, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/expense-reports/{report_id}/approve Approve expense report Approve a submitted expense report. Parameters: - report_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/expense-reports/{report_id}/reimburse Reimburse expense report Mark an approved expense report as reimbursed. Parameters: - report_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/expense-reports/{report_id}/submit Submit expense report Submit a draft expense report for approval. Parameters: - report_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## fixed-assets — Asset tracking, depreciation schedules, and disposal. ### GET /api/v1/fixed-assets List fixed assets List all fixed assets. Parameters: - status (query, string, optional): Filter by status - category_id (query, string, optional): Filter by category - search (query, string, optional): Search by asset name - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 422 ### POST /api/v1/fixed-assets Create fixed asset Create a new fixed asset. Request body (FixedAssetCreate): - name (string, required) - description (string, optional) - asset_number (string, optional) - serial_number (string, optional) - purchase_date (string · date, required) - original_cost (integer, required) - salvage_value (integer, optional) - useful_life_months (integer, optional) - depreciation_method (DepreciationMethod, optional) → DepreciationMethod - location (string, optional) - notes (string, optional) - category_id (string, optional) - vendor_id (string, optional) - in_service_date (string · date, optional) - total_expected_units (integer, optional) Responses: 201, 422 ### GET /api/v1/fixed-assets/categories List asset categories List all asset categories. Responses: 200, 422 ### POST /api/v1/fixed-assets/categories Create asset category Create a new asset category. Request body (AssetCategoryCreate): - name (string, required) - description (string, optional) - default_useful_life_months (integer, optional) - default_depreciation_method (DepreciationMethod, optional) → DepreciationMethod - default_salvage_percent (integer, optional) - asset_ledger_id (string, optional) - depreciation_expense_ledger_id (string, optional) - accumulated_depreciation_ledger_id (string, optional) Responses: 201, 422 ### POST /api/v1/fixed-assets/categories/seed-defaults Seed default categories Seed default asset categories (Equipment, Vehicles, etc.). Responses: 201, 422 ### DELETE /api/v1/fixed-assets/categories/{category_id} Delete asset category Delete an asset category. Parameters: - category_id (path, string, required) Responses: 200, 422 ### GET /api/v1/fixed-assets/categories/{category_id} Get asset category Get a specific asset category. Parameters: - category_id (path, string, required) Responses: 200, 422 ### PATCH /api/v1/fixed-assets/categories/{category_id} Update asset category Update an asset category. Parameters: - category_id (path, string, required) Request body (AssetCategoryUpdate): - name (string, optional) - description (string, optional) - default_useful_life_months (integer, optional) - default_depreciation_method (DepreciationMethod, optional) - default_salvage_percent (integer, optional) - asset_ledger_id (string, optional) - depreciation_expense_ledger_id (string, optional) - accumulated_depreciation_ledger_id (string, optional) Responses: 200, 422 ### GET /api/v1/fixed-assets/depreciation-preview Preview depreciation Preview the next depreciation run. Parameters: - period_date (query, string · date, required): Period date (first of month) Responses: 200, 422 ### POST /api/v1/fixed-assets/run-depreciation Run depreciation Run depreciation for a period. Request body (RunDepreciationRequest): - period_date (string · date, required) - asset_ids (array · string, optional) - create_journal_entries (boolean, optional) Responses: 201, 422 ### POST /api/v1/fixed-assets/suggest-capitalization AI capitalization suggestions Get AI-powered suggestions for transactions that should be capitalized. Responses: 201, 401, 403, 422 ### DELETE /api/v1/fixed-assets/{asset_id} Delete fixed asset Delete a fixed asset (only draft assets can be deleted). Parameters: - asset_id (path, string, required) Responses: 200, 422 ### GET /api/v1/fixed-assets/{asset_id} Get fixed asset Get a specific fixed asset. Parameters: - asset_id (path, string, required) Responses: 200, 422 ### PATCH /api/v1/fixed-assets/{asset_id} Update fixed asset Update a fixed asset. Parameters: - asset_id (path, string, required) Request body (FixedAssetUpdate): - name (string, optional) - description (string, optional) - asset_number (string, optional) - serial_number (string, optional) - category_id (string, optional) - vendor_id (string, optional) - salvage_value (integer, optional) - useful_life_months (integer, optional) - depreciation_method (DepreciationMethod, optional) - total_expected_units (integer, optional) - location (string, optional) - notes (string, optional) Responses: 200, 422 ### GET /api/v1/fixed-assets/{asset_id}/depreciation-entries Get depreciation entries Get all depreciation entries for an asset. Parameters: - asset_id (path, string, required) Responses: 200, 422 ### GET /api/v1/fixed-assets/{asset_id}/depreciation-schedule Get depreciation schedule Get the full depreciation schedule for an asset. Parameters: - asset_id (path, string, required) Responses: 200, 422 ### POST /api/v1/fixed-assets/{asset_id}/dispose Dispose asset Dispose of an asset (sell, trade, scrap, etc.). Parameters: - asset_id (path, string, required) Request body (DisposeAssetRequest): - disposal_date (string · date, required) - disposal_method (DisposalMethod, required) → DisposalMethod - disposal_amount (integer, optional) - disposal_notes (string, optional) Responses: 201, 422 ### POST /api/v1/fixed-assets/{asset_id}/place-in-service Place asset in service Place an asset in service to begin depreciation. Parameters: - asset_id (path, string, required) Request body (PlaceInServiceRequest): - in_service_date (string · date, required) Responses: 201, 422 ## fx-rates ### GET /api/v1/fx/exposure FX exposure preview Per-currency open AR/AP exposure and unrealized gain/loss. Parameters: - as_of_date (query, string · date, required): Revaluation date. Responses: 200, 400, 401, 403, 422 ### GET /api/v1/fx/rates List FX rates List stored FX rates for the business. Responses: 200, 401, 403, 422 ### POST /api/v1/fx/rates Upsert FX rate Create or update an FX rate for a (base, quote) pair on a date. Request body (FxRateUpsertRequest): - base_currency (string, required) - quote_currency (string, required) - rate (number | string, required) - as_of_date (string · date, required) - source (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/fx/revalue Post FX revaluation Revalue open foreign AR/AP and post the adjusting JE. Request body (FxRevaluationRequest): - as_of_date (string · date, required) - post (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ## intents ### POST /api/v1/intents/route Route user intent Classify a Command-K query and return the appropriate action. Request body (IntentRequest): - query (string, required) Responses: 201, 401, 403, 422 ## inventory:accounts_payable ### GET /api/v1/inventory/accounts-payable List Inventory AP bills List bills generated by a Purchase Order (those with source_inventory_order_id set). Supports optional status filtering. Parameters: - status (query, string, optional): Comma-separated list of bill statuses to include. - limit (query, integer, optional): Max bills to return. Responses: 200, 401, 403, 422 ### POST /api/v1/inventory/accounts-payable/{bill_id}/three-way-match Recompute the three-way match for a bill Forces a fresh PO -> Receipt -> Invoice comparison for the bill and writes the outcome onto `bill.match_status` and the variance columns. Idempotent. Parameters: - bill_id (path, string, required) - qty_tolerance (query, integer, optional): Unit tolerance before a qty drift is flagged. - price_tolerance_cents (query, integer, optional): Cents tolerance before a price drift is flagged. - price_tolerance_pct (query, number, optional): Percent-of-PO-total tolerance (combined with cents floor). Responses: 201, 401, 403, 404, 422 ## inventory:activity ### GET /api/v1/inventory/activity List inventory activity Return the N most recent inventory activity events (PO submit/ready/shipped/received/canceled, etc.). Parameters: - limit (query, integer, optional) Responses: 200, 401, 403, 422 ## inventory:adjustments — Inventory quantity and value adjustments. ### POST /api/v1/inventory/adjustments Create manual inventory adjustment Create a manual inventory adjustment for a variant at a specific location. If no location is specified, the adjustment is applied to the default location. Request body (InventoryAdjustmentRequest): - variant_id (string, required) - quantity_change (integer, required) - reason (string, required) - reason_code (string, optional) - location_id (string, optional) Responses: 201, 422 ## inventory:cost_history ### GET /api/v1/inventory/cost-history Landed COGS history Return cost snapshots grouped by variant for the last N months. Powers the Landed COGS sparkline + material/freight/processing breakdown. Parameters: - months (query, integer, optional): Window size in months (default 12). Responses: 200, 401, 403, 422 ## inventory:counts ### GET /api/v1/inventory/counts List cycle counts List recent cycle counts, optionally filtered by status. Parameters: - status (query, string, optional): Filter by status: draft | in_progress | posted | cancelled. - limit (query, integer, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/inventory/counts Start a cycle count Create a draft cycle / physical count at a location. Request body (StartCountRequest): - location_id (string, required) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/inventory/counts/{count_id} Get a cycle count Return one cycle count with all lines + variant metadata. Parameters: - count_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/inventory/counts/{count_id}/cancel Cancel a cycle count Mark a count as cancelled without posting adjustments. Parameters: - count_id (path, string, required) Responses: 201, 401, 403, 404, 409, 422 ### POST /api/v1/inventory/counts/{count_id}/lines Add or scan a line Add a line by `variant_id`, `barcode`, or `sku`. Re-scans of the same variant update the existing line. Parameters: - count_id (path, string, required) Request body (AddCountLineRequest): - variant_id (string, optional) - barcode (string, optional) - sku (string, optional) - counted_quantity (number, optional) - reason (string, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### PUT /api/v1/inventory/counts/{count_id}/lines/{line_id} Record a counted quantity Record / update the counter's measured quantity on a line. Parameters: - count_id (path, string, required) - line_id (path, string, required) Request body (SubmitCountLineRequest): - counted_quantity (number, required) - reason (string, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 409, 422 ### POST /api/v1/inventory/counts/{count_id}/post Post a cycle count Finalize the count, emitting one manual inventory adjustment per non-zero variance line. Parameters: - count_id (path, string, required) Responses: 201, 401, 403, 404, 409, 422 ## inventory:forecast ### POST /api/v1/inventory/forecast/recompute Recompute SKU demand forecasts Recompute and persist statistical demand forecasts and safety stock for every SKU with recent sales. Normally run nightly by a background job; this endpoint forces an immediate refresh. Responses: 201, 401, 403, 404, 422 ### GET /api/v1/inventory/forecast/skus List per-SKU demand forecasts Return the most recently computed statistical demand forecast for each SKU, including the weekly forecast curve, service-level safety stock, suggested reorder point, order-up-to level, and backtest accuracy. Read-only. Responses: 200, 401, 403, 404, 422 ### GET /api/v1/inventory/forecast/skus/{variant_id} Get one SKU's demand forecast detail Return a single SKU's statistical forecast (curve, safety stock, reorder point, order-up-to, accuracy) together with its recent actual weekly demand history, so the UI can overlay history vs forecast with a confidence band. Read-only. Parameters: - variant_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## inventory:import — Bulk import inventory data from CSV or external systems. ### POST /api/v1/inventory/import/analyze Analyze spreadsheet (async) Upload an Excel or CSV file. Returns a task_id immediately; the AI analysis runs in the background. Poll GET /analyze/{task_id} for the result. Responses: 201, 400, 401, 403, 422 ### GET /api/v1/inventory/import/analyze/{task_id} Poll analysis status Returns 'pending', 'completed' (with result), or 'failed'. Parameters: - task_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/inventory/import/apply Apply import Execute the confirmed import: create locations, products, variants, and set inventory levels. All changes happen in a single transaction. Request body (ApplyRequest): - file_id (string, required) - locations (array · ApplyLocationDecision, optional) → ApplyLocationDecision - products (array · ApplyProductDecision, optional) → ApplyProductDecision - levels (array · ApplyLevelDecision, optional) → ApplyLevelDecision Responses: 201, 400, 401, 403, 422 ### POST /api/v1/inventory/import/preview Preview import After reviewing the AI analysis, confirm column mappings and sheet selections. Returns a full preview of locations, products, and inventory levels to be created. Request body (PreviewRequest): - file_id (string, required) - selected_sheets (array · string, required) - sheet_mappings (array · SheetMappingConfirmation, required) → SheetMappingConfirmation - location_decisions (array · LocationDecision, optional) → LocationDecision Responses: 201, 400, 401, 403, 422 ## inventory:locations — Warehouse and storage location management. ### GET /api/v1/inventory/locations List inventory locations List all inventory locations for the business with optional filtering. Parameters: - location_type (query, LocationTypeEnum, optional): Filter by location type - include_archived (query, boolean, optional): Include archived locations - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size - include_total_count (query, boolean, optional): Include total count Responses: 200, 401, 403, 422 ### POST /api/v1/inventory/locations Create location Create a new inventory location. Request body (LocationCreate): - name (string, required) - code (string, optional) - location_type (LocationTypeEnum, optional) → LocationTypeEnum - address_line1 (string, optional) - address_line2 (string, optional) - city (string, optional) - state (string, optional) - postal_code (string, optional) - country (string, optional) - is_default (boolean, optional) - is_active (boolean, optional) - contact_name (string, optional) - contact_email (string, optional) - contact_phone (string, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/inventory/locations/default Get default location Get the default location for the business. Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/inventory/locations/{location_id} Delete location Delete a location. Cannot delete if it has inventory or is the default. Parameters: - location_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/inventory/locations/{location_id} Get location Get a specific location by ID. Parameters: - location_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/inventory/locations/{location_id} Update location Update an existing location. Parameters: - location_id (path, string, required) Request body (LocationUpdate): - name (string, optional) - code (string, optional) - location_type (LocationTypeEnum, optional) - address_line1 (string, optional) - address_line2 (string, optional) - city (string, optional) - state (string, optional) - postal_code (string, optional) - country (string, optional) - is_default (boolean, optional) - is_active (boolean, optional) - archived (boolean, optional) - contact_name (string, optional) - contact_email (string, optional) - contact_phone (string, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/inventory/locations/{location_id}/archive Archive location Archive a location (soft delete). Cannot archive the default location. Parameters: - location_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/inventory/locations/{location_id}/inventory Get inventory at location Get all inventory levels at a specific location. Parameters: - location_id (path, string, required) - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 404, 422 ### POST /api/v1/inventory/locations/{location_id}/set-default Set as default location Set a location as the default for new inventory. Parameters: - location_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/inventory/locations/{location_id}/summary Get location inventory summary Get summary of inventory at a location. Parameters: - location_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## inventory:lots ### GET /api/v1/inventory/lots/expiring List expiring cost lots Return non-exhausted cost lots with an `expiration_date` within the next `within_days` days. Powers the 'Expiring soon' badge on the inventory overview. Parameters: - within_days (query, integer, optional): Lookahead window in days (default 30). - include_expired (query, boolean, optional): When false, exclude lots whose `expiration_date` is already in the past. - limit (query, integer, optional) Responses: 200, 401, 403, 422 ## inventory:payments — Payments linked to inventory orders. ### GET /api/v1/inventory/payments List inventory payments Retrieve payments made to vendors for inventory orders. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ## inventory:po_import ### POST /api/v1/inventory/po-import/analyze Analyze manufacturer PO spreadsheet (async) Upload an Excel or CSV manufacturer purchase order. Returns a task_id immediately; poll GET /analyze/{task_id} for column mappings and metadata. Responses: 201, 400, 401, 403, 422 ### GET /api/v1/inventory/po-import/analyze/{task_id} Poll PO analysis status Returns 'pending', 'completed' (with result), or 'failed'. Parameters: - task_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/inventory/po-import/apply Create purchase order from import Create a draft purchase order from confirmed line items and vendor. Unmatched SKUs require variant_id overrides or must be skipped. Request body (POApplyRequest): - file_id (string, required) - vendor_id (string, required) - po_number (string, required) - name (string, required) - description (string, optional) - line_decisions (array · POLineDecision, optional) → POLineDecision - selected_sheets (array · string, required) - sheet_mappings (array · POSheetMappingConfirmation, required) → POSheetMappingConfirmation Responses: 201, 400, 401, 403, 422 ### POST /api/v1/inventory/po-import/preview Preview PO import with SKU matching After reviewing AI column mappings, returns line items with SKU match status against the product catalog. Request body (POPreviewRequest): - file_id (string, required) - selected_sheets (array · string, required) - sheet_mappings (array · POSheetMappingConfirmation, required) → POSheetMappingConfirmation Responses: 201, 400, 401, 403, 422 ## inventory:production — Production orders and bill-of-materials management. ### GET /api/v1/inventory/production/recipes List recipes List production recipes for the business. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 422 ### POST /api/v1/inventory/production/recipes Create recipe Create a new production recipe with materials, output variants, and processing steps. Request body (RecipeCreateRequest): - name (string, required) - description (string, optional) - output_unit_name (string, optional) - output_unit_weight (number, optional) - output_unit_weight_unit (string, optional) - conversion_params (object, optional) - materials (array · RecipeMaterialBase, optional) → RecipeMaterialBase - output_variants (array · RecipeOutputVariantBase, optional) → RecipeOutputVariantBase - processing_steps (array · RecipeProcessingStepBase, optional) → RecipeProcessingStepBase Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/inventory/production/recipes/{recipe_id} Delete recipe Soft-delete a production recipe. Parameters: - recipe_id (path, string, required) Responses: 200, 404, 422 ### GET /api/v1/inventory/production/recipes/{recipe_id} Get recipe Get a production recipe with all children. Parameters: - recipe_id (path, string, required) Responses: 200, 404, 422 ### PUT /api/v1/inventory/production/recipes/{recipe_id} Update recipe Update a production recipe. Children are replaced if provided. Parameters: - recipe_id (path, string, required) Request body (RecipeUpdateRequest): - name (string, optional) - description (string, optional) - output_unit_name (string, optional) - output_unit_weight (number, optional) - output_unit_weight_unit (string, optional) - conversion_params (object, optional) - materials (array · RecipeMaterialBase, optional) - output_variants (array · RecipeOutputVariantBase, optional) - processing_steps (array · RecipeProcessingStepBase, optional) Responses: 200, 400, 404, 422 ### POST /api/v1/inventory/production/recipes/{recipe_id}/duplicate Duplicate recipe Deep-clone a recipe and all its children. Parameters: - recipe_id (path, string, required) Responses: 201, 404, 422 ### GET /api/v1/inventory/production/runs List production runs List production runs for the business. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 422 ### POST /api/v1/inventory/production/runs Create production run Create a new production run from a recipe. Costs are computed immediately. Request body (RunCreateRequest): - recipe_id (string, required) - name (string, optional) - total_units (integer, required) - unit_sale_price (integer, optional) Responses: 201, 400, 404, 422 ### GET /api/v1/inventory/production/runs/{run_id} Get production run Get a production run with its cost breakdown. Parameters: - run_id (path, string, required) Responses: 200, 404, 422 ### PUT /api/v1/inventory/production/runs/{run_id} Update production run Update run parameters and recalculate costs. Parameters: - run_id (path, string, required) Request body (RunUpdateRequest): - name (string, optional) - total_units (integer, optional) - unit_sale_price (integer, optional) Responses: 200, 400, 404, 422 ### POST /api/v1/inventory/production/runs/{run_id}/calculate What-if calculation Recalculate costs without persisting. Override total_units and/or unit_sale_price. Parameters: - run_id (path, string, required) Request body (RunCalculateRequest): - total_units (integer, optional) - unit_sale_price (integer, optional) Responses: 201, 404, 422 ### POST /api/v1/inventory/production/runs/{run_id}/complete Complete production run Mark a run as completed. Parameters: - run_id (path, string, required) Responses: 201, 400, 404, 422 ## inventory:products — Product catalog with SKUs, costs, and variants. ### GET /api/v1/inventory/products List products Retrieve all products with optional filtering and pagination. Parameters: - type (query, string, optional): Filter by product type ('manual' or 'shopify') - category (query, string, optional): Filter by category (e.g. 'Finished Goods', 'Ingredients', 'Packaging') - include_variants (query, boolean, optional): Whether to include variant details - archived (query, boolean, optional): Filter by archived status (true/false/null for all) - search (query, string, optional): Fuzzy search term (trigram-similarity matching against the entity's primary text columns). Trimmed; ignored when blank. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 404, 422 ### POST /api/v1/inventory/products Create product Create a new product for the specified business. A connected Stripe account is **not** required; Stripe Catalog sync happens later when variants are added with Stripe connected. Request body (ProductCreateRequest): - name (string, required) - type (string, required) - category (string, optional) - tracks_expiration (boolean, optional) Responses: 201, 400, 404, 409, 422 ### GET /api/v1/inventory/products/lookup Lookup variant by barcode or SKU Resolve a single variant by a scannable identifier. Tries barcode first, then falls back to SKU. Used by receiving, cycle counting, and point-of-sale flows. Parameters: - barcode (query, string, optional): Scannable barcode (UPC/EAN/QR). - sku (query, string, optional): Stock Keeping Unit fallback. Responses: 200, 400, 404, 422 ### POST /api/v1/inventory/products/unit-costs/apply Apply unit cost bulk update Apply confirmed unit-cost updates from a validated CSV upload. Request body (UnitCostBulkApplyRequest): - updates (array · UnitCostBulkUpdateRowInput, required) → UnitCostBulkUpdateRowInput Responses: 201, 400, 422 ### GET /api/v1/inventory/products/unit-costs/export Export unit costs CSV Download a CSV of active variants with current unit costs. Edit the new_unit_cost column and re-upload via the bulk update flow. Responses: 200, 404, 422 ### POST /api/v1/inventory/products/unit-costs/preview Preview unit cost bulk update Validate parsed CSV rows and preview cost changes before applying. Request body (UnitCostBulkPreviewRequest): - rows (array · UnitCostBulkUpdateRowInput, required) → UnitCostBulkUpdateRowInput Responses: 201, 400, 422 ### GET /api/v1/inventory/products/variants List product variants Retrieve all variants with optional filtering by product. Parameters: - product_id (query, string, optional): Filter by parent product ID - archived (query, boolean, optional): Filter by archived status - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 404, 422 ### POST /api/v1/inventory/products/variants Create product variant Create a new product variant. When the business has Stripe connected, the variant is mirrored to Stripe Catalog (Product + Price); otherwise the variant is created locally with empty Stripe IDs and can be synced later. Request body (VariantCreateRequest): - product_id (string, optional) - unit_price (integer, optional) - unit_cost (integer, optional) - unit_cost_precise (number, optional) - name (string, optional) - sku (string, optional) - manufacturer_sku (string, optional) - barcode (string, optional) - uom (string, optional) - default_vendor_id (string, optional) - initial_inventory_quantity (integer, optional) - initial_inventory_quantity_date (string · date-time, optional) Responses: 201, 400, 404, 409, 422 ### DELETE /api/v1/inventory/products/variants/{id} Delete product variant Delete or archive a variant. Parameters: - id (path, string · uuid, required) - product_id (query, string, optional) Responses: 200, 404, 422 ### GET /api/v1/inventory/products/variants/{id} Get product variant Retrieve a specific variant by ID. Parameters: - id (path, string · uuid, required) Responses: 200, 404, 422 ### PUT /api/v1/inventory/products/variants/{id} Update product variant Update an existing variant. Parameters: - id (path, string · uuid, required) Request body (VariantUpdateRequest): - id (string, optional) - unit_price (integer, optional) - name (string, optional) - sku (string, optional) - manufacturer_sku (string, optional) - barcode (string, optional) - unit_cost (integer, optional) - unit_cost_precise (number, optional) - uom (string, optional) - default_vendor_id (string, optional) - archived (boolean, optional) - initial_inventory_quantity (integer, optional) - initial_inventory_quantity_date (string · date-time, optional) Responses: 200, 400, 404, 422 ### PUT /api/v1/inventory/products/variants/{id}/location-quantities Set variant location opening quantities Set absolute on-hand quantities for this variant at each location without creating inventory adjustment journal entries. Parameters: - id (path, string · uuid, required) Request body (SetVariantLocationQuantitiesRequest): - location_quantities (array · VariantLocationQuantityInput, required) → VariantLocationQuantityInput - effective_at (string · date-time, optional) Responses: 200, 400, 404, 422 ### PATCH /api/v1/inventory/products/variants/{variant_id}/locations/{location_id}/reorder-settings Update variant reorder settings at a location Set or clear the per-location reorder point and reorder quantity for a variant. Parameters: - variant_id (path, string · uuid, required) - location_id (path, string · uuid, required) Request body (UpdateReorderSettingsRequest): - reorder_point (number, optional) - reorder_quantity (number, optional) Responses: 200, 400, 404, 422 ### DELETE /api/v1/inventory/products/{id} Delete product Delete or archive a product. Parameters: - id (path, string · uuid, required) Responses: 200, 404, 422 ### GET /api/v1/inventory/products/{id} Get product Retrieve a specific product by ID. Parameters: - id (path, string · uuid, required) - include_variants (query, boolean, optional): Whether to include variant details Responses: 200, 404, 422 ### PUT /api/v1/inventory/products/{id} Update product Update an existing product. Parameters: - id (path, string · uuid, required) - include_variants (query, boolean, optional): Whether to include variant details Request body (ProductUpdateRequest): - id (string, optional) - name (string, optional) - type (string, optional) - category (string, optional) - tracks_expiration (boolean, optional) - archived (boolean, optional) Responses: 200, 400, 404, 422 ### POST /api/v1/inventory/products/{id}/convert-to-variant Convert product to variant Move a product's variants under another product and archive the source. Parameters: - id (path, string · uuid, required) Request body (ConvertProductToVariantRequest): - target_product_id (string, required) Responses: 201, 400, 404, 422 ## inventory:reorder ### POST /api/v1/inventory/reorder/cash-impact Preview the cash impact of a reorder Model the effect of placing one or more (not-yet-created) purchase orders on the 13-week cash forecast. Runs the live forecast with and without the proposed POs and returns the delta -- including how far the projected cash low point moves -- so the operator can see the cash impact of a reorder before committing to it. Request body (ReorderCashImpactRequest): - proposed_pos (array · ProposedPurchaseOrder, optional) → ProposedPurchaseOrder - weeks (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/inventory/reorder/draft Draft purchase orders from reorder suggestions Materialize reorder suggestions as draft purchase orders, one per vendor. Variants without a preferred vendor are skipped. Responses: 201, 400, 401, 403, 404, 409, 422 ### GET /api/v1/inventory/reorder/suggestions Suggest reorders from reorder points Return vendor-grouped reorder suggestions for variants whose available quantity has fallen to or below the per-location reorder point. Read-only. Responses: 200, 401, 403, 404, 422 ## inventory:reports ### GET /api/v1/inventory/reports/adjustment-reasons Adjustments grouped by reason Aggregates `inventory` ledger rows whose `correction_reason` is set (i.e. manual adjustments and cycle-count posts) by reason code. Returns count, signed units, and a $-impact estimate using each variant's stored `unit_cost`. Use ``since``/``until`` ISO-8601 strings to scope the window (defaults to all-time). Parameters: - since (query, string · date-time, optional): Inclusive lower bound (ISO-8601). Omit for all-time. - until (query, string · date-time, optional): Exclusive upper bound (ISO-8601). Omit for now. Responses: 200, 400, 422 ### GET /api/v1/inventory/reports/aging Inventory aging report Bucket remaining cost-lot quantities (and dollars) by lot age (0-30 / 31-60 / 61-90 / 91-180 / 180+ days). Optionally returns the per-variant detail with the oldest received_at. Parameters: - detail (query, boolean, optional): Include per-variant breakdown sorted by oldest lot age. - detail_limit (query, integer, optional): Cap on per-variant detail rows when `detail=true`. Responses: 200, 401, 403, 422 ### GET /api/v1/inventory/reports/margin-by-sku Margin-by-SKU report (landed cost + sales velocity) Returns per-variant landed cost (latest cost snapshot), current sell price, gross margin, and trailing Shopify sales over the configured lookback window. Powers the Landed COGS 'Truth' tab. Parameters: - lookback_days (query, integer, optional): Trailing window (days) used to compute revenue + units sold. - limit (query, integer, optional): Max variants returned (sorted by trailing revenue desc). Responses: 200, 401, 403, 422 ### GET /api/v1/inventory/reports/valuation Current inventory valuation Return the current on-hand inventory value (in cents) computed from remaining FIFO cost lots, grouped by location, category, or vendor. Parameters: - group_by (query, string, optional): Bucketing dimension: 'location' (default), 'category', or 'vendor'. Responses: 200, 401, 403, 422 ## inventory:shipments — Inbound and outbound shipment tracking. ### GET /api/v1/inventory/shipments List shipments Retrieve all shipments for inventory orders. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/inventory/shipments Create shipment Record a received shipment and update inventory. Request body (ShipmentCreateRequest): - fees (array · Fee, optional) - status (string, optional) - line_items (array · LineItem, optional) - description (string, optional) - tracking_number (string, optional) - expected_arrival_date (string · date-time, optional) - shipped_date (string · date-time, optional) - location_id (string, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/inventory/shipments/{id} Delete shipment Delete a shipment record. Parameters: - id (path, string, required) Responses: 200, 422 ### GET /api/v1/inventory/shipments/{id} Get shipment Retrieve a specific shipment with line items. Parameters: - id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/inventory/shipments/{id} Update shipment Update a shipment's details. Parameters: - id (path, string, required) Request body (ShipmentUpdateRequest): - fees (array · Fee, optional) - status (string, optional) - line_items (array · LineItem, optional) - description (string, optional) - tracking_number (string, optional) - expected_arrival_date (string · date-time, optional) - shipped_date (string · date-time, optional) - location_id (string, optional) - version (integer, optional) Responses: 200, 422 ## inventory:supply_chain ### GET /api/v1/inventory/supply-chain Get inventory supply chain map Return a single payload of nodes (locations with on-hand + alerts) and edges (open POs + in-transit transfers) for the Supply Chain Map. Responses: 200, 401, 403, 422 ## inventory:transfer_suggestions ### GET /api/v1/inventory/transfer-suggestions/suggestions Suggest site-to-site transfers Return (donor, receiver, quantity) suggestions for variants with surplus at one location and deficit at another. Parameters: - surplus_pct (query, number, optional): Donor must hold this much above its own reorder point. - limit (query, integer, optional) Responses: 200, 401, 403, 404, 422 ## inventory:transfers — Inter-location inventory transfers. ### GET /api/v1/inventory/transfers List inventory transfers List all inventory transfers for the business with optional filtering. Parameters: - status (query, TransferStatusEnum, optional): Filter by status - from_location_id (query, string, optional): Filter by source location - to_location_id (query, string, optional): Filter by destination location - search (query, string, optional): Search transfer number, locations, tracking, carrier, or notes - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/inventory/transfers Create transfer Create a new inventory transfer between locations. Request body (TransferCreate): - transfer_number (string, optional) - from_location_id (string, required) - to_location_id (string, required) - transfer_date (string · date, optional) - expected_arrival_date (string · date, optional) - tracking_number (string, optional) - carrier (string, optional) - shipping_cost (integer, optional) - expected_freight_cost (integer, optional) - notes (string, optional) - items (array · TransferItemCreate, required) → TransferItemCreate Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/inventory/transfers/in-transit List in-transit transfers Get all transfers currently in transit with cursor-based pagination. Parameters: - search (query, string, optional): Search transfer number, locations, tracking, carrier, or notes - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### GET /api/v1/inventory/transfers/pending List pending transfers Get all draft and pending transfers awaiting shipment with cursor-based pagination. Parameters: - search (query, string, optional): Search transfer number, locations, tracking, carrier, or notes - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### GET /api/v1/inventory/transfers/summary Get transfer summary Get summary of transfers by status and in-transit inventory. Responses: 200, 401, 403, 422 ### DELETE /api/v1/inventory/transfers/{transfer_id} Delete transfer Delete a transfer. Only draft/pending transfers can be deleted. Parameters: - transfer_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/inventory/transfers/{transfer_id} Get transfer Get a specific transfer by ID. Parameters: - transfer_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/inventory/transfers/{transfer_id} Update transfer Update an existing transfer. Only draft/pending transfers can be updated. Parameters: - transfer_id (path, string, required) Request body (TransferUpdate): - transfer_number (string, optional) - transfer_date (string · date, optional) - expected_arrival_date (string · date, optional) - tracking_number (string, optional) - carrier (string, optional) - shipping_cost (integer, optional) - expected_freight_cost (integer, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/inventory/transfers/{transfer_id}/cancel Cancel transfer Cancel a transfer. Reverses inventory changes if already shipped. Parameters: - transfer_id (path, string, required) Request body (TransferCancelRequest): - reason (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/inventory/transfers/{transfer_id}/items Add item to transfer Add an item to an existing transfer. Only works for draft/pending transfers. Parameters: - transfer_id (path, string, required) Request body (TransferItemAddRequest): - variant_id (string, required) - quantity (integer, required) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/inventory/transfers/{transfer_id}/items/{item_id} Remove item from transfer Remove an item from a transfer. Only works for draft/pending transfers. Parameters: - transfer_id (path, string, required) - item_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/inventory/transfers/{transfer_id}/receive Receive transfer Mark transfer as received. Adds inventory to destination location. Parameters: - transfer_id (path, string, required) Request body (TransferReceiveRequest): - received_date (string · date, optional) - items (array · TransferItemReceive, required) → TransferItemReceive - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/inventory/transfers/{transfer_id}/ship Ship transfer Mark transfer as shipped (in-transit). Deducts inventory from source location. Parameters: - transfer_id (path, string, required) Request body (TransferShipRequest): - transfer_date (string · date, optional) - tracking_number (string, optional) - carrier (string, optional) - expected_freight_cost (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ## invitations ### POST /api/v1/invitations/{token}/accept Accept a pending invitation Process a pending invitation for the authenticated user. Adds the user to the firm (and grants business access for client invitations) then marks the invitation as accepted. Idempotent: returns success if already accepted. Parameters: - token (path, string, required) Responses: 201, 400, 401, 404, 422 ### GET /api/v1/invitations/{token}/preview Preview invitation details (public) Returns non-sensitive invitation details for rendering the invite signup page. No authentication required -- the token itself is the proof. Parameters: - token (path, string, required) Responses: 200, 422 ## invoices — Accounts receivable — create, finalize, and track invoice payments. ### GET /api/v1/invoices List invoices Retrieve invoices with optional filtering by customer, product, or status. Parameters: - id (query, string, optional): Filter by specific invoice UUID - customer_id (query, string, optional): Filter by customer UUID - product_id (query, string, optional): Filter by product UUID - variant_id (query, string, optional): Filter by variant UUID - status (query, array · string, optional): Filter by status. Supports repeated params (?status=open&status=partially-paid) or bracket notation (?status[]=open&status[]=partially-paid). - search (query, string, optional): Search invoices by number, description, or customer name (case-insensitive partial match) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/invoices Create invoice Create a new invoice for a customer. Optionally include 'recurring' config to also create a recurring template. Request body (InvoiceCreateRequest): - customer_id (string, required) - currency (string, optional) - line_items (array · LineItemRequest, required) → LineItemRequest - due_date (string, required) - issue_date (string, optional) - description (string, optional) - fulfillment_location_id (string, optional) - recurring (InvoiceRecurringConfig, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/invoices/aging AR aging summary Returns accounts receivable aging data grouped by customer with Current, 1-30, 31-60, 61-90, and 91+ day buckets. Includes per-invoice detail for drill-down. Responses: 200, 401, 403, 422 ### POST /api/v1/invoices/ai-create AI-extract draft invoices from documents Upload pasted text, PDFs, images, Excel files, or a CSV to extract draft invoice entities using AI. Returns draft invoices ready for user review before creation. Request body (InvoiceAICreatePayloadRequest): - pasted_text (string, optional) - pasted_text_s3_key (string, optional) - single_invoice_s3_keys (array · string, optional) - multi_invoice_s3_key (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/invoices/batch Batch create invoices Create invoices for multiple customers in one action, optionally sweeping delayed charges. Request body (BatchInvoiceRequest): - items (array · BatchInvoiceItem, required) → BatchInvoiceItem - include_delayed_charges (boolean, optional) - finalize (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/invoices/bulk List all invoices (bulk) Retrieve all invoices with efficient cursor-based pagination. Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/invoices/bulk Bulk create invoices Create multiple invoices from CSV data in a single request. Request body (InvoiceBulkRequest): - s3_key (string, optional) - finalize (boolean, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/invoices/cash-receipts Weekly cash receipts reconciliation Returns a weekly breakdown of cash received (bank deposits) versus amounts applied to invoices and bills. Highlights unapplied deposits to aid reconciliation. Parameters: - start_date (query, string, optional): Report start date (YYYY-MM-DD). Default: 12 weeks ago. - end_date (query, string, optional): Report end date (YYYY-MM-DD). Default: today. Responses: 200, 401, 403, 422 ### POST /api/v1/invoices/cash-receipts/dismiss Dismiss a transaction from cash receipts Mark a bank transaction as not a customer deposit so it is excluded from the Cash Receipts Reconciliation totals and weekly breakdown. Request body (CashReceiptDismissRequest): - transaction_id (string, required) - reason (string, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/invoices/cash-receipts/restore Restore a dismissed cash receipt Un-dismiss a transaction so it reappears in the Cash Receipts Reconciliation report and totals. Parameters: - transaction_id (query, string, required): UUID of the transaction to restore. Responses: 201, 401, 403, 404, 422 ### GET /api/v1/invoices/cash-receipts/week-detail Cash receipts week detail Returns individual deposit transactions for a given week, including which invoices and bills each deposit was applied to. Parameters: - week_start (query, string, required): Monday of the target week (YYYY-MM-DD). Responses: 200, 400, 401, 403, 422 ### GET /api/v1/invoices/collections AR collections dashboard Returns overdue invoices plus AR collection workflow run history and recorded collection reminder emails when present in the database. Responses: 200, 401, 403, 422 ### GET /api/v1/invoices/deposit-candidates List deposit candidates Return income transactions that may represent customer deposits. Filters to posted income transactions with unallocated balances, useful for the AI deposit sidebar on the invoices page. Parameters: - limit (query, integer, optional): Max results Responses: 200, 401, 403, 422 ### POST /api/v1/invoices/match Trigger payment matching Run the payment matching engine to find likely bank transactions for open invoices. When auto_match is true, high-confidence matches (>= 0.85) are auto-linked; otherwise all matches are surfaced as suggestions. Responses: 201, 400, 401, 403, 422 ### GET /api/v1/invoices/metrics Get invoice metrics Retrieve aggregated invoice metrics for the business: total outstanding and total overdue across all open invoices. Responses: 200, 401, 403, 422 ### GET /api/v1/invoices/suggestions List payment suggestions List auto-detected payment match suggestions for the business. Parameters: - status (query, string, optional): Filter by status: pending, accepted, rejected, expired - min_confidence (query, number, optional): Minimum confidence - limit (query, integer, optional): Max results Responses: 200, 401, 403, 422 ### POST /api/v1/invoices/suggestions/{suggestion_id}/accept Accept payment suggestion Accept an auto-detected payment suggestion. Creates an InvoicePayment record and updates the invoice status. Parameters: - suggestion_id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/invoices/suggestions/{suggestion_id}/reject Reject payment suggestion Dismiss an auto-detected payment suggestion. Parameters: - suggestion_id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/invoices/{invoice_id} Delete invoice Permanently delete a draft invoice. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/invoices/{invoice_id} Get invoice Retrieve a specific invoice with all details and line items. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/invoices/{invoice_id} Update invoice Update invoice details. Optionally include 'recurring' config to create a recurring template. Parameters: - invoice_id (path, string · uuid, required) Request body (InvoiceUpdateRequest): - id (string, required) - customer_id (string, optional) - line_items (array · LineItemRequest, optional) - due_date (string, optional) - description (string, optional) - fulfillment_location_id (string, optional) - recurring (InvoiceRecurringConfig, optional) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/invoices/{invoice_id}/delivered Mark Invoice Delivered Mark an invoice as delivered to the customer. Parameters: - invoice_id (path, string · uuid, required) - delivered_on (query, string, required): Delivered date (ISO 8601 format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/invoices/{invoice_id}/finalize Finalize invoice Lock an invoice and change status from draft to pending. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/invoices/{invoice_id}/journal-entry Generate invoice journal entry Create the finalization journal entry for an invoice that has none linked. Parameters: - invoice_id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### POST /api/v1/invoices/{invoice_id}/journal-entry/link Attach journal entry to invoice Link an existing journal entry to an invoice. Parameters: - invoice_id (path, string · uuid, required) Request body (JournalEntryLinkRequest): - journal_entry_id (string, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### PUT /api/v1/invoices/{invoice_id}/paid Mark Invoice Paid Mark an invoice as paid. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/invoices/{invoice_id}/payment-link Get invoice payment link Return the Stripe-hosted payment link for an invoice so it can be paid online. Read-only — available once the invoice has been finalized/sent. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/invoices/{invoice_id}/payments List invoice payments Retrieve all payment records for a specific invoice. Parameters: - invoice_id (path, string · uuid, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 404, 422 ### POST /api/v1/invoices/{invoice_id}/payments Add payment to invoice Link a bank transaction as payment against an invoice. Supports split payments (one transaction paying multiple invoices) and partial payments (multiple transactions paying one invoice). Parameters: - invoice_id (path, string · uuid, required) Request body (InvoicePaymentRequest): - transaction_id (string, required) - amount (integer, required) - paid_on (string · date-time, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/invoices/{invoice_id}/payments/{payment_id} Remove payment from invoice Un-link a payment record from an invoice and recalculate status. Parameters: - invoice_id (path, string · uuid, required) - payment_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/invoices/{invoice_id}/payments/{payment_id} Update payment on invoice Edit the amount or date of an existing payment record. Parameters: - invoice_id (path, string · uuid, required) - payment_id (path, string · uuid, required) Request body (InvoicePaymentUpdateRequest): - amount (integer, optional) - paid_on (string · date-time, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/invoices/{invoice_id}/pdf Get fresh invoice PDF URL Get a fresh PDF download URL for this invoice from Stripe. URLs expire, so call this when you need to download. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/invoices/{invoice_id}/send Send invoice via email Send an invoice to the customer via email. Optionally attach the PDF and CC the sender. Parameters: - invoice_id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### GET /api/v1/invoices/{invoice_id}/suggestions List suggestions for invoice List auto-detected payment match suggestions for a specific invoice. Parameters: - invoice_id (path, string · uuid, required) - status (query, string, optional): Filter by status Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/invoices/{invoice_id}/uncollectible Mark invoice uncollectible Write off an invoice's remaining balance as bad debt. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/invoices/{invoice_id}/void Void invoice Cancel an invoice by setting its status to void. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ## journal-entries — Double-entry bookkeeping with balanced debit/credit lines. ### GET /api/v1/journal-entries List journal entries Retrieve journal entries with filtering by date, ledger, source, and text search. Parameters: - id (query, array · string, optional): Filter by journal entry IDs (comma-separated UUIDs) - description (query, string, optional): Filter by description (partial match) - invoice_id (query, string, optional): Filter by linked invoice UUID - ledger_id (query, string, optional): Filter by ledger account UUID (returns entries with line entries affecting this ledger) - source (query, array · string, optional): Filter by one or more backend origins: manual, invoice, bill, credit_memo, transaction, stripe, shopify, plaid, ramp, square, system, teal - creation_method (query, JournalEntryCreationMethodEnum, optional): Filter by creation method: ai (system-generated) or manual (user-typed) - start_date (query, string, optional): Filter entries on or after this date (ISO 8601: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ) - end_date (query, string, optional): Filter entries on or before this date (ISO 8601: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ) - search (query, string, optional): Search journal entries by description (case-insensitive partial match) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/journal-entries Create journal entry Create a new double-entry journal entry with balanced line entries. Request body (JournalEntryCreateRequest): - description (string, optional) - currency (string, optional) - date (string · date, optional) - line_entries (array · object, optional) - invoice_id (string, optional) - inventory_order_id (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/journal-entries/bulk-upload/post Post bulk journal entries Create all journal entries from a previously validated bulk upload file. Call the preview endpoint first to validate. Request body (BulkJournalEntryUploadRequest): - s3_key (string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/journal-entries/bulk-upload/preview Preview bulk journal entry upload Parse and validate a CSV/Excel file of journal entries without creating anything. Returns a structured preview with per-entry validation. Request body (BulkJournalEntryUploadRequest): - s3_key (string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/journal-entries/bulk-upload/template Download bulk JE upload template Get a bulk journal entry XLS template pre-populated with the business's chart of accounts. Responses: 200, 401, 403, 422 ### DELETE /api/v1/journal-entries/{journal_entry_id} Delete journal entry Delete a journal entry and all its associated line entries. Parameters: - journal_entry_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/journal-entries/{journal_entry_id} Get journal entry Retrieve a single journal entry with all its line entries. Parameters: - journal_entry_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/journal-entries/{journal_entry_id} Update journal entry Update an existing journal entry's description, date, or line entries. Parameters: - journal_entry_id (path, string, required) Request body (JournalEntryUpdateRequest): - description (string, optional) - date (string, optional) - line_entry_changes (object, optional) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/journal-entries/{journal_entry_id}/attachment Attach file to journal entry Upload and attach a document file to a journal entry. Parameters: - journal_entry_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/journal-entries/{journal_entry_id}/reverse Reverse journal entry Create an offsetting journal entry that mirrors the original with flipped debits and credits. Parameters: - journal_entry_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## ledgers — Chart of accounts — asset, liability, equity, revenue, and expense accounts. ### GET /api/v1/ledgers List ledgers Retrieve chart of accounts (ledgers) for the business. Parameters: - id (query, array · string, optional): Filter by specific ledger UUIDs - name (query, string, optional): Filter by ledger name (partial match) - type (query, string, optional): Filter by type: asset, liability, equity, revenue, expense - status (query, string, optional): Filter by status: active, inactive - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/ledgers Create ledger Create a new account in the chart of accounts. Request body (LedgerCreateRequest): - name (string, required) - currency (string, optional) - editable (boolean, optional) - type (string, optional) - description (string, optional) - account_number (string, optional) - status (string, optional) - debit_credit (string, optional) - sort_code (integer, optional) - sub_type (string, optional) - report_cash_flow (boolean, optional) - financial_account_type (string, optional) - parent_id (string, optional) - cfs_class (CashFlowClassEnum, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/ledgers/apply-coa Apply AI-generated COA Apply an AI-generated or custom chart of accounts template to the business, upserting over existing ledgers. Protected ledgers (system, required, bank-linked) are skipped. Existing custom ledgers not present in the template are deactivated. Request body (CustomCoaTemplate): - ledgers (array · CustomCoaLedger, required) → CustomCoaLedger Responses: 201, 400, 401, 403, 422 ### POST /api/v1/ledgers/bulk-update Bulk update ledgers Update multiple ledgers in a single request. Request body (LedgerBulkUpdateRequest): - updates (array · object, required) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/ledgers/merge Merge ledgers Merge one or more source ledgers into a target ledger, reassigning all transactions and journal entries. Request body (LedgerMergeRequest): - source_ledger_ids (array · string, required) - target_ledger_id (string, required) - new_name (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### POST /api/v1/ledgers/suggest-cash-flow Suggest cash flow classifications Get deterministic suggestions for cash flow statement classifications based on ledger types and sub-types. Responses: 201, 401, 403, 422 ### POST /api/v1/ledgers/suggest-cleanup AI account cleanup suggestions Get AI-powered suggestions for chart of accounts cleanup. Read-only; rate limited per user and gated on monthly AI quota. Responses: 200, 401, 403, 422, 429 ### DELETE /api/v1/ledgers/{ledger_id} Delete ledger Permanently delete a ledger/account. Parameters: - ledger_id (path, string, required) Responses: 200, 401, 403, 404, 409, 422 ### GET /api/v1/ledgers/{ledger_id} Get ledger Retrieve a specific ledger/account with full details. Parameters: - ledger_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/ledgers/{ledger_id} Update ledger Update an existing ledger account. Parameters: - ledger_id (path, string, required) Request body (LedgerUpdateRequest): - name (string, optional) - description (string, optional) - account_number (string, optional) - status (string, optional) - debit_credit (string, optional) - sort_code (integer, optional) - sub_type (string, optional) - report_cash_flow (boolean, optional) - cash_flow_section (CashFlowSectionEnum, optional) - cfs_class (CashFlowClassEnum, optional) - editable (boolean, optional) - parent_id (string, optional) - financial_account_type (string, optional) Responses: 200, 400, 401, 403, 404, 422 ## month-end-packages ### GET /api/v1/month-end-packages List month-end packages List all month-end packages requested for the business. Parameters: - status (query, string, optional): Filter by status: queued, in_progress, ready, failed - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/month-end-packages Queue a month-end package build Persist a queued package row and start the Temporal build workflow. Request body (MonthEndPackageCreateRequest): - accounting_period_id (string, optional) - period_start (string · date, optional) - period_end (string · date, optional) - report_format (MonthEndPackageFormatEnum, optional) → MonthEndPackageFormatEnum - selected_reports (array · string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/month-end-packages/{package_id} Get month-end package Get a single month-end package by id (poll for build progress). Parameters: - package_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/month-end-packages/{package_id}/download Get download URL Generate a short-lived presigned S3 URL for the package zip. Parameters: - package_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ## notes — Internal notes attached to businesses and entities. ### GET /api/v1/notes List notes for a business Retrieve all notes for the current business, ordered by pinned then most recently updated. Responses: 200, 401, 403, 422 ### POST /api/v1/notes Create a note Create a new note for the current business. Request body (NoteCreateRequest): - title (string, optional) - body (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/notes/bulk Bulk-create notes (localStorage migration) Create multiple notes at once, used to migrate existing localStorage notes to the database. Request body (NoteBulkCreateRequest): - notes (array · NoteCreateRequest, required) → NoteCreateRequest Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/notes/reminders/{reminder_id} Cancel a reminder Deactivate a scheduled reminder. Parameters: - reminder_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/notes/{note_id} Delete a note Soft-delete a note. Parameters: - note_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/notes/{note_id} Get a single note Retrieve a single note by ID. Parameters: - note_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/notes/{note_id} Update a note Partially update a note's title, body, or pinned status. Parameters: - note_id (path, string, required) Request body (NoteUpdateRequest): - title (string, optional) - body (string, optional) - pinned (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/notes/{note_id}/attachments Register note attachment Register a new attachment for a note after successful S3 upload. Parameters: - note_id (path, string, required) Request body (NoteAttachmentCreateRequest): - file_name (string, required) - file_size (integer, required) - mime_type (string, required) - s3_key (string, required) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/notes/{note_id}/attachments/upload-url Get upload URL for note attachment Generate a presigned URL to upload a file to S3 for a note. Parameters: - note_id (path, string, required) Request body (NoteAttachmentUploadUrlRequest): - file_name (string, required) - mime_type (string, required) - file_size (integer, required) Responses: 201, 401, 403, 404, 422 ### DELETE /api/v1/notes/{note_id}/attachments/{attachment_id} Delete note attachment Soft-delete a note attachment. Parameters: - note_id (path, string, required) - attachment_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/notes/{note_id}/attachments/{attachment_id}/download-url Get download URL for note attachment Generate a presigned URL to download a note attachment. Parameters: - note_id (path, string, required) - attachment_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/notes/{note_id}/notify Notify business users about a note Send an inbox notification about this note to all users of the business. The note must have a non-empty body. Parameters: - note_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/notes/{note_id}/reminders List reminders for a note Get all active reminders for a specific note. Parameters: - note_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/notes/{note_id}/reminders Schedule a reminder for a note Set a one-time or recurring notification reminder for a note. Pick a date/time and optionally set recurrence (daily, weekly, monthly). Parameters: - note_id (path, string, required) Request body (ReminderCreateRequest): - scheduled_at (string · date-time, required) - recurrence (ReminderRecurrence, optional) → ReminderRecurrence Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/notes/{note_id}/rewrite AI rewrite of note body Use AI to rewrite the note body. Returns a preview; the caller must PATCH to apply. Parameters: - note_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## notification-preferences — Manage email and in-app notification settings. ### GET /api/v1/preferences/notifications/topics Get Topic Overrides Endpoint Return the per-topic channel overrides for the current user. Responses: 200, 422 ### PUT /api/v1/preferences/notifications/topics Update Topic Overrides Endpoint Replace the per-topic channel override map for the current user. Request body (TopicChannelOverridesUpdate): - overrides (object, optional) Responses: 200, 422 ## notifications ### GET /api/v1/notifications List notifications Get the caller's notification feed across all scopes Parameters: - scope (query, NotificationScopeEnum, optional): Scope: 'me' (default), 'business', or 'firm' - scope_id (query, string, optional): Required when scope is 'business' or 'firm' - status (query, array · NotificationStatusEnum, optional): Filter to one or more statuses - topic (query, array · NotificationTopicEnum, optional): Filter to one or more topics - severity (query, array · NotificationSeverityEnum, optional): Filter to one or more severities - include_terminal (query, boolean, optional): Include resolved + dismissed rows - include_snoozed (query, boolean, optional): Include snoozed rows whose timer hasn't elapsed - include_live (query, boolean, optional): Merge live alerts (low cash, plaid stale, etc.) as virtual rows when a business context is available. - live_business_id (query, string, optional): When scope='me', merge live alerts for this specific business id. Ignored for scope='business' which uses scope_id as the business context. - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 400, 401, 403, 422 ### POST /api/v1/notifications/bulk/dismiss Bulk dismiss Dismiss a batch of notifications. Request body (BulkIdsRequest): - notification_ids (array · string, required) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/notifications/bulk/seen Bulk mark seen Mark a batch of notifications as seen. Request body (BulkIdsRequest): - notification_ids (array · string, required) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/notifications/count Notification count Unread (``new`` only) count for the bell badge by default. Pass ``unread_only=false`` for the legacy open (new + seen) count. Parameters: - scope_business_id (query, string, optional): Optional: scope to a single business - business_id (query, string, optional): Deprecated alias for `scope_business_id` - unread_only (query, boolean, optional): When true (default), count only unread (``new``) rows. When false, count open rows (``new`` + ``seen`` + expired snoozes). Responses: 200, 401, 403, 422 ### POST /api/v1/notifications/dismiss-all Dismiss all matching notifications Server-authoritative: dismisses EVERY open notification the caller can see that matches the optional scope/topic/severity filters (plus matching live alerts) — not just the page the client has loaded. Request body (BulkAllRequest): - scope (NotificationScopeEnum, optional) → NotificationScopeEnum - scope_id (string, optional) - topic (array · NotificationTopicEnum, optional) - severity (array · NotificationSeverityEnum, optional) - live_business_id (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/notifications/mark-all-seen Mark all matching notifications seen Server-authoritative: marks EVERY notification the caller can see that matches the optional scope/topic/severity filters as seen — not just the page the client has loaded. Request body (BulkAllRequest): - scope (NotificationScopeEnum, optional) → NotificationScopeEnum - scope_id (string, optional) - topic (array · NotificationTopicEnum, optional) - severity (array · NotificationSeverityEnum, optional) - live_business_id (string, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/notifications/stream Stream notifications (SSE) Server-Sent Events stream of new notifications for the caller. Responses: 200, 401, 403, 422 ### GET /api/v1/notifications/topic-counts Per-topic notification counts Counts grouped by topic for the filter chip row Parameters: - scope_business_id (query, string, optional): Optional: scope to a single business - business_id (query, string, optional): Deprecated alias for `scope_business_id` Responses: 200, 401, 403, 422 ### GET /api/v1/notifications/{notification_id} Get a single notification Fetch a notification by ID. Auto-marks 'new' rows as 'seen'. Parameters: - notification_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/notifications/{notification_id}/dismiss Dismiss a notification Dismiss (terminal). Virtual notifications use the dismissal table. Parameters: - notification_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### PUT /api/v1/notifications/{notification_id}/pin Pin or unpin a notification Toggle whether a notification is pinned to the top of the feed. Parameters: - notification_id (path, string, required) Request body (SetPinnedRequest): - pinned (boolean, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/notifications/{notification_id}/resolve Resolve a notification Mark resolved (terminal — no further transitions) Parameters: - notification_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/notifications/{notification_id}/seen Mark notification as seen Manually mark a notification as seen Parameters: - notification_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/notifications/{notification_id}/snooze Snooze a notification Snooze a notification until a specific timestamp. Parameters: - notification_id (path, string, required) Request body (SnoozeRequest): - until (string · date-time, required) Responses: 201, 400, 401, 403, 404, 422 ## oauth — OAuth 2.0 authorization code flow for third-party integrations. ### POST /api/v1/oauth2/exchange Exchange Code Exchange a single-use code or refresh token for access + refresh tokens. Responses: 200, 422 ### POST /api/v1/oauth2/refresh Refresh Token Exchange refresh token for new access token. Responses: 200, 422 ### POST /api/v1/oauth2/token Get Token Issue OAuth access + refresh tokens for a user. Responses: 200, 422 ## onboarding — User and business onboarding workflows. ### POST /api/v1/onboarding/ai-discover Get next AI discovery question Given the account type and conversation so far, returns the next personalised question for the onboarding discovery flow. Request body (AIDiscoverRequest): - account_type (string, required) - conversation (array · object, optional) Responses: 201, 400, 422, 429, 500 ### POST /api/v1/onboarding/ai-recommend Generate personalised add-on recommendations Analyses the full discovery conversation, extracts a structured business profile, and returns personalised add-on recommendations. Request body (AIRecommendRequest): - account_type (string, required) - conversation (array · object, required) Responses: 201, 400, 422, 429, 500 ### POST /api/v1/onboarding/complete Complete onboarding after Stripe Checkout Called after the user completes Stripe Checkout. Retrieves the session, creates the business, firm, and HubSpot records. Request body (CompleteOnboardingRequest): - stripe_session_id (string, required) Responses: 201, 400, 409, 422, 429 ### POST /api/v1/onboarding/prepare-checkout Create Stripe Checkout session for onboarding Collects plan and add-on selections, creates a Stripe Checkout session with a 30-day trial. Business is NOT created until /complete is called. Request body (PrepareCheckoutRequest): - account_type (string, required) - business_name (string, required) - mailbox (string, required) - default_currency (string, optional) - tax_year_end_month (integer, optional) - selected_addons (array · string, optional) - trial_agreed (boolean, required) - billing_interval (string, optional) - firm_name (string, optional) - firm_plan_tier (string, optional) - ein (string, optional) - logo_url (string, optional) - address (string, optional) - num_full_time_accountants (integer, optional) - num_part_time_accountants (integer, optional) - current_tools (array · string, optional) - success_path (string, optional) - cancel_path (string, optional) Responses: 201, 400, 409, 422, 429 ### GET /api/v1/onboarding/profile Resume in-progress onboarding Returns the persisted onboarding profile (conversation, learned profile fields, recommendations, account_type) for the current user. Used by the Welcome wizard to restore wizard state after a refresh. Responses: 200, 401, 422 ### POST /api/v1/onboarding/upload-url Get pre-signed upload URL for onboarding assets Returns a pre-signed S3 PUT URL for uploading a firm logo during onboarding (no business context required). Request body (OnboardingUploadUrlRequest): - filename (string, required) - content_type (string, optional) Responses: 201, 400, 422, 429 ## payment-advisory — AI-powered payment recommendations and cash flow optimization. ### GET /api/v1/payment-advisory/firm-summary Get consolidated AP summary for firm Get AP outstanding/overdue summary across all firm client businesses. Responses: 200, 401, 403, 422 ### GET /api/v1/payment-advisory/recommendations Get payment recommendations Get prioritized payment recommendations for outstanding bills. Responses: 200, 401, 403, 422 ## payment-terms — Net payment terms (Net 30, Due on Receipt, etc.). ### GET /api/v1/payment-terms List payment terms List all payment terms for the business. Parameters: - active_only (query, boolean, optional): Only show active terms - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/payment-terms Create payment term Create a new payment term. Request body (PaymentTermCreateRequest): - name (string, required) - days_until_due (integer, required) - discount_percent (number | string, optional) - discount_days (integer, optional) - is_default (boolean, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/payment-terms/default Get default payment term Get the default payment term for the business. Responses: 200, 401, 403, 404, 422 ### POST /api/v1/payment-terms/seed-defaults Seed default payment terms Seed default payment terms for the business (if none exist). Responses: 201, 401, 403, 422 ### DELETE /api/v1/payment-terms/{term_id} Deactivate payment term Deactivate a payment term (soft delete). Parameters: - term_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/payment-terms/{term_id} Get payment term Get details of a specific payment term. Parameters: - term_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/payment-terms/{term_id} Update payment term Update an existing payment term. Parameters: - term_id (path, string, required) Request body (PaymentTermUpdateRequest): - name (string, optional) - days_until_due (integer, optional) - discount_percent (number | string, optional) - discount_days (integer, optional) - is_default (boolean, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ## projects ### GET /api/v1/projects List projects List projects, optionally filtered by status or customer. Parameters: - status (query, string, optional): Filter by status: active, completed, on_hold. - customer_id (query, string, optional): Filter by customer. - include_inactive (query, boolean, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/projects Create project Create a new project. Request body (ProjectCreateRequest): - name (string, required) - code (string, optional) - description (string, optional) - status (string, optional) - customer_id (string, optional) - start_date (string · date, optional) - end_date (string · date, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/projects/profitability Project profitability Revenue, expense, and net profit broken down by project. Parameters: - start_date (query, string · date, required): Period start (inclusive). - end_date (query, string · date, required): Period end (inclusive). Responses: 200, 400, 401, 403, 422 ### DELETE /api/v1/projects/{project_id} Deactivate project Soft-delete (deactivate) a project. Parameters: - project_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/projects/{project_id} Get project Get a single project. Parameters: - project_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/projects/{project_id} Update project Update an existing project. All fields optional. Parameters: - project_id (path, string, required) Request body (ProjectUpdateRequest): - name (string, optional) - code (string, optional) - description (string, optional) - status (string, optional) - customer_id (string, optional) - start_date (string · date, optional) - end_date (string · date, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/projects/{project_id}/profitability Single project profitability Revenue, expense, and net profit for one project. Parameters: - project_id (path, string, required) - start_date (query, string · date, required): Period start (inclusive). - end_date (query, string · date, required): Period end (inclusive). Responses: 200, 400, 401, 403, 404, 422 ## purchase-orders ### GET /api/v1/purchase-orders List inventory orders Retrieve all purchase orders with their status and totals. Parameters: - status (query, string, optional): Filter by order status - vendor_id (query, string, optional): Filter by vendor ID - search (query, string, optional): Search PO number, name, description, vendor, or status - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/purchase-orders Create inventory order Create a new purchase order for inventory restocking. Request body (InventoryOrderCreate): - vendor_id (string, required) - status (string, required) - name (string, required) - po_number (string, required) - line_items (array · LineItem, optional) - fees (array · Fee, optional) - payments (array · Payment-Input, optional) - description (string, optional) - total (integer, optional) - balance (integer, optional) - shipping_cost (integer, optional) - ship_from_location_id (string, optional) - ship_to_location_id (string, optional) - production_run_id (string, optional) - expected_delivery_date (string · date-time, optional) - payment_term_id (string, optional) - expected_paid_on (string · date-time, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/purchase-orders/pdf Get order PDFs Get download URLs for order PDFs. Parameters: - order_ids (query, string, optional): Comma-separated order UUIDs Responses: 200, 401, 403, 422 ### DELETE /api/v1/purchase-orders/{id} Delete inventory order Permanently delete a purchase order. Parameters: - id (path, string · uuid, required) Responses: 200, 401, 403, 404, 409, 422 ### GET /api/v1/purchase-orders/{id} Get inventory order Retrieve a specific purchase order with line items. Parameters: - id (path, string · uuid, required) - order_id (query, string, optional) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/purchase-orders/{id} Update inventory order Update purchase order details with optimistic locking support. Parameters: - id (path, string · uuid, required) Request body (InventoryOrderUpdate): - vendor_id (string, optional) - status (string, optional) - name (string, optional) - po_number (string, optional) - line_items (array · LineItem, optional) - fees (array · Fee, optional) - payments (array · Payment-Input, optional) - description (string, optional) - total (integer, optional) - balance (integer, optional) - version (integer, optional) - shipping_cost (integer, optional) - ship_from_location_id (string, optional) - ship_to_location_id (string, optional) - production_run_id (string, optional) - expected_delivery_date (string · date-time, optional) - payment_term_id (string, optional) - expected_paid_on (string · date-time, optional) Responses: 200, 400, 401, 403, 404, 409, 422 ### POST /api/v1/purchase-orders/{id}/cancel Cancel purchase order Cancel a PO and reverse any in-transit transfer. Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/mark-in-production Mark purchase order in production Transition PO to 'in_production'. Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/mark-partially-received Mark purchase order partially received Transition PO to 'partially_received'. Set by the shipment flow when at least one shipment has been received but more is still pending against the PO. Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/mark-ready Mark purchase order ready Transition PO to 'ready' (goods produced, not yet shipped). Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/mark-shipped Mark purchase order shipped Transition PO to 'shipped' and create an in-transit transfer. Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/payments Record payment for purchase order Link a bank transaction to a purchase order as a payment. Parameters: - id (path, string · uuid, required) Request body (OrderPaymentRequest): - transaction_id (string, required) - amount (integer, required) - paid_on (string · date-time, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/recalculate-totals Recalculate order totals Recalculate order total from line items and payments. Parameters: - id (path, string · uuid, required) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/receive Receive purchase order Transition PO to 'received', flip linked Bill from forecasted to received, mark linked transfer as received, write cost snapshots, and post received quantities to on-hand inventory at ship_to_location_id (skipped when a received shipment is already associated with the PO, since that path writes inventory itself). Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{id}/submit Submit purchase order Transition PO to 'submitted' and create a forecasted bill. Parameters: - id (path, string · uuid, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-orders/{order_id}/pdf Generate order PDF Generate or regenerate a PDF purchase order document. Parameters: - order_id (path, string · uuid, required) Responses: 201, 401, 403, 404, 422 ## purchase-requisitions ### GET /api/v1/purchase-requisitions List purchase requisitions List purchase requisitions with optional filtering. Parameters: - status (query, PurchaseRequisitionStatusEnum, optional): Filter by status - vendor_id (query, string, optional): Filter by vendor ID - search (query, string, optional): Search description or number - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/purchase-requisitions Create purchase requisition Create a draft purchase requisition with line items. Request body (PurchaseRequisitionCreateRequest): - description (string, optional) - vendor_id (string, optional) - currency (string, optional) - lines (array · PurchaseRequisitionLineInput, required) → PurchaseRequisitionLineInput Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/purchase-requisitions/{requisition_id} Delete purchase requisition Soft-delete a draft purchase requisition. Parameters: - requisition_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/purchase-requisitions/{requisition_id} Get purchase requisition Get a single purchase requisition with line items. Parameters: - requisition_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/purchase-requisitions/{requisition_id} Update purchase requisition Update a draft purchase requisition. Parameters: - requisition_id (path, string, required) Request body (PurchaseRequisitionUpdateRequest): - description (string, optional) - vendor_id (string, optional) - lines (array · PurchaseRequisitionLineInput, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-requisitions/{requisition_id}/approve Approve purchase requisition Approve a submitted purchase requisition. Parameters: - requisition_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-requisitions/{requisition_id}/convert Convert purchase requisition Convert an approved requisition to a purchase order or bill. Parameters: - requisition_id (path, string, required) Request body (PurchaseRequisitionConvertRequest): - target (PurchaseRequisitionConvertTargetEnum, required) → PurchaseRequisitionConvertTargetEnum Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-requisitions/{requisition_id}/reject Reject purchase requisition Reject a submitted purchase requisition. Parameters: - requisition_id (path, string, required) Request body (PurchaseRequisitionRejectRequest): - reason (string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/purchase-requisitions/{requisition_id}/submit Submit purchase requisition Submit a draft requisition for approval. Parameters: - requisition_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## quickbooks ### GET /api/v1/quickbooks/connect-url Get QuickBooks OAuth connect URL Generate a URL to redirect the user to Intuit for authorization. Responses: 200, 400, 401, 403, 422 ### GET /api/v1/quickbooks/coverage-report QuickBooks import coverage report Enumerate every supported QuickBooks object type, its DayZero native target model, and how many were imported. Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/quickbooks/disconnect Disconnect QuickBooks Remove the QuickBooks integration and revoke stored credentials. Responses: 200, 400, 401, 403, 422 ### POST /api/v1/quickbooks/import Start QuickBooks migration import Import everything from the chosen start date forward (accounts, customers, vendors, items, documents, transactions, payments, and journals) plus a single opening-balance JE as of the day before. Parameters: - start_date (query, string, optional): Import everything dated on/after this date (YYYY-MM-DD). The opening balance is posted as of the day before. - phases (query, array · string, optional): Optional subset of document/posting sections to import (e.g. invoices, bills, payments). Omit to import everything. Master data and the opening balance always import. Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/quickbooks/migration-report QuickBooks migration QA report Compare the QuickBooks Trial Balance against local ledger balances. Parameters: - as_of_date (query, string, optional): As-of date (YYYY-MM-DD) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/quickbooks/oauth/callback QuickBooks OAuth callback Handle the Intuit OAuth redirect. Called by Intuit, not the frontend. Parameters: - code (query, string, required): Authorization code from Intuit - state (query, string, required): OAuth state nonce - realmId (query, string, required): QBO company id Responses: 200, 422 ### GET /api/v1/quickbooks/status QuickBooks connection status Returns connection health and migration import status. Responses: 200, 401, 403, 422 ## reconciliations — Bank statement reconciliation with AI-assisted matching. ### GET /api/v1/reconciliations List reconciliations List reconciliations for the business. Parameters: - status (query, string, optional): Filter by status - limit (query, integer, optional): Page size - offset (query, integer, optional): Pagination offset - include_total_count (query, boolean, optional): Include total count (expensive - avoid if possible) Responses: 200, 422 ### POST /api/v1/reconciliations Create reconciliation Create a new bank reconciliation (with S3 key). Request body (ReconciliationCreateRequest): - ledger_id (string, required) - source_type (string, optional) - statement_s3_key (string, optional) - start_date (string · date-time, required) - end_date (string · date-time, required) - statement_filename (string, optional) - opening_balance (integer, optional) - closing_balance (integer, optional) - auto_start (boolean, optional) Responses: 201, 422 ### POST /api/v1/reconciliations/upload Create reconciliation with upload Create a new bank reconciliation with direct file upload. Parameters: - ledger_id (query, string, required): ID of ledger to reconcile against - start_date (query, string · date-time, required): Statement period start date - end_date (query, string · date-time, required): Statement period end date - opening_balance (query, integer, optional): Opening balance in cents - closing_balance (query, integer, optional): Closing balance in cents - auto_start (query, boolean, optional): Auto-start processing workflow Responses: 201, 422 ### GET /api/v1/reconciliations/{reconciliation_id} Get reconciliation Get reconciliation details. Parameters: - reconciliation_id (path, string, required) Responses: 200, 422 ### GET /api/v1/reconciliations/{reconciliation_id}/balance Get balance validation Get balance validation for the reconciliation. Parameters: - reconciliation_id (path, string, required) Responses: 200, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/complete Complete reconciliation Complete and lock the reconciliation. Parameters: - reconciliation_id (path, string, required) Responses: 201, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/explain-matches AI match explanations Get AI-powered explanations for reconciliation matches. Parameters: - reconciliation_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/matches/approve Approve match Approve a transaction match. Parameters: - reconciliation_id (path, string, required) Request body (MatchApprovalRequest): - statement_txn_id (string, required) - ledger_txn_ids (array · string, required) Responses: 201, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/matches/bulk-approve Bulk approve matches Bulk approve transaction matches. Parameters: - reconciliation_id (path, string, required) Request body (BulkApproveRequest): - min_confidence (number, optional) - statement_txn_ids (array · string, optional) Responses: 201, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/matches/bulk-reject Bulk reject matches Bulk reject/remove approved matches. Parameters: - reconciliation_id (path, string, required) Request body (BulkRejectRequest): - statement_txn_ids (array · string, required) Responses: 201, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/matches/reject Reject match Reject/remove an approved match. Parameters: - reconciliation_id (path, string, required) Request body (MatchRejectionRequest): - statement_txn_id (string, required) Responses: 201, 422 ### GET /api/v1/reconciliations/{reconciliation_id}/pdf Download reconciliation PDF Download reconciliation report as PDF. Parameters: - reconciliation_id (path, string, required) Responses: 200, 422 ### POST /api/v1/reconciliations/{reconciliation_id}/start Start reconciliation processing Start or restart reconciliation processing. Parameters: - reconciliation_id (path, string, required) Responses: 201, 422 ## recurring — Recurring invoice and bill templates with auto-generation. ### GET /api/v1/recurring List recurring templates List all recurring templates for the business with optional filtering. Parameters: - recurrence_type (query, app__models__recurring_templates__RecurrenceTypeEnum, optional): Filter by type (invoice or bill) - status (query, RecurringStatusEnum, optional): Filter by status - customer_id (query, string, optional): Filter by customer ID (for invoice templates) - vendor_id (query, string, optional): Filter by vendor ID (for bill templates) - search (query, string, optional): Search name, description, frequency, status, or counterparty - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/recurring Create recurring template Create a new recurring invoice or bill template. Request body (RecurringTemplateCreate): - name (string, required) - currency (string, optional) - frequency (FrequencyEnum, required) → FrequencyEnum - days_until_due (integer, optional) - day_of_month (integer, optional) - day_of_week (integer, optional) - end_date (string · date, optional) - max_occurrences (integer, optional) - notes (string, optional) - recurrence_type (app__api__v1__schemas__recurring__recurring__RecurrenceTypeEnum, required) → app__api__v1__schemas__recurring__recurring__RecurrenceTypeEnum - start_date (string · date, required) - customer_id (string, optional) - auto_send (boolean, optional) - vendor_id (string, optional) - template_data (object, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/recurring/bills List recurring bill templates List all recurring bill templates for the business. Parameters: - status (query, RecurringStatusEnum, optional): Filter by status - vendor_id (query, string, optional): Filter by vendor ID - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### GET /api/v1/recurring/invoices List recurring invoice templates List all recurring invoice templates for the business. Parameters: - status (query, RecurringStatusEnum, optional): Filter by status - customer_id (query, string, optional): Filter by customer ID - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/recurring/suggest-templates AI recurring template suggestions Get AI-powered suggestions for recurring transaction templates. Responses: 201, 401, 403, 422 ### DELETE /api/v1/recurring/{template_id} Delete recurring template Delete a recurring template. This action cannot be undone. Parameters: - template_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/recurring/{template_id} Get recurring template Get a specific recurring template by ID. Parameters: - template_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/recurring/{template_id} Update recurring template Update an existing recurring template. Parameters: - template_id (path, string, required) Request body (RecurringTemplateUpdate): - name (string, optional) - currency (string, optional) - frequency (FrequencyEnum, optional) - days_until_due (integer, optional) - day_of_month (integer, optional) - day_of_week (integer, optional) - end_date (string · date, optional) - max_occurrences (integer, optional) - notes (string, optional) - template_data (object, optional) - auto_send (boolean, optional) - customer_id (string, optional) - vendor_id (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/recurring/{template_id}/cancel Cancel recurring template Cancel a recurring template. This cannot be undone. Parameters: - template_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/recurring/{template_id}/clone Clone recurring template Create a copy of an existing recurring template with optional overrides. Parameters: - template_id (path, string, required) Request body (RecurringTemplateClone): - name (string, optional) - start_date (string · date, optional) - customer_id (string, optional) - vendor_id (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/recurring/{template_id}/history Get recurring template history Get all bills or invoices generated from this recurring template. Parameters: - template_id (path, string, required) - limit (query, integer, optional): Maximum items to return Responses: 200, 401, 403, 404, 422 ### POST /api/v1/recurring/{template_id}/pause Pause recurring template Pause an active recurring template. No new documents will be generated until resumed. Parameters: - template_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/recurring/{template_id}/resume Resume recurring template Resume a paused recurring template. Next occurrence will be recalculated. Parameters: - template_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/recurring/{template_id}/skip Skip next occurrence Skip the next scheduled occurrence without generating a document. Parameters: - template_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## reports — Financial report generation (P&L, Balance Sheet, Trial Balance, etc.). ### GET /api/v1/reports List reports Retrieve all reports for a business with pagination. Parameters: - client_visible_only (query, boolean, optional) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/reports Generate report Queue a report for generation and return task ID for tracking. Request body (ReportCreateRequest): - report_name (string, required) - period_type (ReportPeriodType, optional) - tax_year (integer, optional) - tax_quarter (integer, optional) - start_date (string, optional) - end_date (string, optional) - comparison_period_type (ReportPeriodType, optional) - comparison_tax_year (integer, optional) - comparison_tax_quarter (integer, optional) - comparison_start_date (string, optional) - comparison_end_date (string, optional) - ledger_id (string, optional) - instance_id (string, optional) - file_s3_key (string, optional) - s3_keys (array · string, optional) - tag_group_id (string, optional) - tag_ids (array · string, optional) - additional_params (object, optional) - output_format (ReportOutputFormat, optional) → ReportOutputFormat Responses: 202, 400, 401, 403, 422 ### GET /api/v1/reports/download Download report (proxy) Stream a report file from S3. Same-origin proxy avoids CORS issues when opening links in new tabs. Parameters: - s3_key (query, string, required) - filename (query, string, optional) Responses: 200, 400, 401, 403, 422 ### GET /api/v1/reports/subledger-gl-reconciliation Sub-ledger to GL reconciliation Compare AR/AP sub-ledger open balances against their GL control-account balances. Responses: 200, 401, 403, 404, 422 ### POST /api/v1/reports/suggest AI report suggestions Get AI-powered report suggestions for the business. Responses: 201, 401, 403, 422 ### GET /api/v1/reports/types Get supported report types Retrieve a list of all supported report types with their metadata. Responses: 200, 401, 403, 422 ### GET /api/v1/reports/{report_id} Get report Retrieve a specific report by ID. Parameters: - report_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## sales-receipts ### GET /api/v1/sales-receipts List sales receipts List sales receipts and refund receipts for the business. Parameters: - receipt_type (query, string, optional): Filter by type: sale or refund. - status (query, string, optional): Filter by status: posted or void. - customer_id (query, string, optional): Filter by customer. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/sales-receipts Create sales receipt Create and post a sales receipt (or refund receipt). Request body (SalesReceiptCreateRequest): - receipt_type (string, optional) - customer_id (string, optional) - receipt_date (string · date, optional) - deposit_to_ledger_id (string, required) - income_ledger_id (string, optional) - line_items (array · SalesReceiptLineItem, required) → SalesReceiptLineItem - tax_total (integer, optional) - number (string, optional) - memo (string, optional) - currency (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/sales-receipts/{receipt_id} Get sales receipt Get details of a specific sales/refund receipt. Parameters: - receipt_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/sales-receipts/{receipt_id}/void Void sales receipt Void a sales/refund receipt and post a reversing journal entry. Parameters: - receipt_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## search — Full-text search across transactions, contacts, and entities. ### GET /api/v1/search Global search Search across products, variants, customers, invoices, vendors, and more. Parameters: - entity_type (query, string, optional): Limit to: products, variants, customers, invoices, vendors, transactions, journal_entries, ledgers, emails - date_from (query, string, optional): Results after this date (ISO 8601: YYYY-MM-DD) - date_to (query, string, optional): Results before this date (ISO 8601: YYYY-MM-DD) - search_term (query, string, required) - limit (query, integer, optional): Pagination limit - cursor (query, string, optional): Cursor for pagination - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/search/natural AI natural language search Search using natural language queries. Responses: 201, 401, 403, 422 ## subscriptions — Manage recurring subscription billing for customers. ### GET /api/v1/subscriptions/ List detected recurring transactions Detect recurring transactions from transaction history using counterparty and frequency analysis. Parameters: - lookback_days (query, integer, optional): Days to look back - sort_by (query, string, optional): Sort: cost_desc, cost_asc, frequency, confidence_desc, recent, occurrences_desc Responses: 200, 401, 403, 422 ### PUT /api/v1/subscriptions/ Update a detected recurring transaction Override the display name, monthly cost, frequency, or add notes to a detected recurring transaction. Request body (UpdateSubscriptionRequest): - counterparty_key (string, required) - registry_id (string, optional) - custom_name (string, optional) - custom_amount_cents (integer, optional) - custom_frequency (string, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 422 ### POST /api/v1/subscriptions/analyze AI subscription analysis Get AI-powered subscription analysis for optimization opportunities. Responses: 201, 401, 403, 422 ### POST /api/v1/subscriptions/dismiss Dismiss a detected recurring transaction Mark a counterparty as not recurring. It won't appear in future results. Request body (app__api__v1__schemas__subscriptions__DismissRequest): - counterparty_key (string, required) - registry_id (string, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/subscriptions/history Monthly spend history for recurring transactions Returns per-month totals and per-counterparty breakdown for charting. Parameters: - lookback_days (query, integer, optional): Days to look back Responses: 200, 401, 403, 422 ### POST /api/v1/subscriptions/restore Restore a dismissed recurring transaction Undo a previous dismissal so the recurring transaction appears again. Request body (RestoreRequest): - counterparty_key (string, required) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/subscriptions/settings Get recurring transaction budget settings Responses: 200, 401, 403, 422 ### PUT /api/v1/subscriptions/settings Set monthly recurring transaction limit Set or remove the monthly recurring transaction budget. Pass null to remove the limit. Request body (SetLimitRequest): - monthly_limit_cents (integer, optional) Responses: 200, 400, 401, 403, 422 ### GET /api/v1/subscriptions/summary Recurring transactions summary for dashboard Returns total monthly cost, budget limit, and top 5 recurring transactions. Responses: 200, 401, 403, 422 ## tags — Custom tag groups and tags for categorizing entities. ### POST /api/v1/entity-tags Attach tag to entity Attach a tag to a specific entity (transaction, invoice, bill, etc.). Idempotent: re-attaching the same tag returns the existing attachment. Request body (EntityTagCreate): - tag_id (string, required) - model_type (string, required) - model_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/entity-tags/batch Batch fetch tags for multiple entities Fetch tags for many entities of the same type in a single request. Returns a mapping of entity id -> its tags; entities with no tags are omitted. Replaces per-row GET /entity-tags/{model_type}/{model_id} calls to avoid request fan-out (and the edge rate limiting it causes). Request body (BulkEntityTagsRequest): - model_type (string, required) - model_ids (array · string, required) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/entity-tags/bulk-attach Bulk attach tags to an entity Attach multiple tags to a single entity at once Request body (BulkEntityTagCreate): - tag_ids (array · string, required) - model_type (string, required) - model_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/entity-tags/bulk-detach Bulk detach tags from an entity Remove multiple tags from a single entity at once Request body (BulkEntityTagRemove): - tag_ids (array · string, required) - model_type (string, required) - model_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/entity-tags/{model_type}/{model_id} Get tags for an entity Retrieve all tags attached to a specific entity with cursor-based pagination Parameters: - model_type (path, string, required) - model_id (path, string, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 400, 401, 403, 422 ### DELETE /api/v1/entity-tags/{tag_id}/{model_type}/{model_id} Detach tag from entity Remove a tag from a specific entity Parameters: - tag_id (path, string, required) - model_type (path, string, required) - model_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/tag-groups List tag groups Retrieve all tag groups for the current business Parameters: - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/tag-groups Create tag group Create a new tag group for organizing tags Request body (TagGroupCreate): - name (string, required) - description (string, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/tag-groups/{tag_group_id} Delete tag group Delete a tag group. This will also delete all tags in the group. Parameters: - tag_group_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/tag-groups/{tag_group_id} Get tag group Retrieve a specific tag group by ID Parameters: - tag_group_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/tag-groups/{tag_group_id} Update tag group Update an existing tag group Parameters: - tag_group_id (path, string, required) Request body (TagGroupUpdate): - name (string, optional) - description (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/tags List tags Retrieve all tags for the current business, optionally filtered by tag group or fuzzy name search Parameters: - tag_group_id (query, string, optional): Filter tags by tag group ID - search (query, string, optional): Fuzzy search tags by name (trigram similarity) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/tags Create tag Create a new tag, optionally in a tag group Request body (TagCreate): - name (string, required) - description (string, optional) - tag_group_id (string, optional) Responses: 201, 400, 401, 403, 409, 422 ### POST /api/v1/tags/suggest AI tag suggestions Get AI-powered tag name suggestions based on existing tags and optional context Request body (TagSuggestRequest): - existing_tags (array · string, optional) - context (string, optional) Responses: 201, 401, 403, 422 ### DELETE /api/v1/tags/{tag_id} Delete tag Delete a tag. This will remove the tag from all entities. Parameters: - tag_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/tags/{tag_id} Get tag Retrieve a specific tag by ID Parameters: - tag_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/tags/{tag_id} Update tag Update an existing tag Parameters: - tag_id (path, string, required) Request body (TagUpdate): - name (string, optional) - description (string, optional) - tag_group_id (string, optional) Responses: 200, 400, 401, 403, 404, 409, 422 ### GET /api/v1/tags/{tag_id}/entities/{model_type} Get entities with a tag Retrieve all entities of a specific type that have a particular tag Parameters: - tag_id (path, string, required) - model_type (path, string, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 400, 401, 403, 404, 422 ## tax — Tax rate configuration and tax calculations. ### GET /api/v1/tax/codes List tax codes List all tax codes for the business. Parameters: - active_only (query, boolean, optional): Only show active codes Responses: 200, 401, 403, 422 ### POST /api/v1/tax/codes Create tax code Create a new tax code. Request body (TaxCodeCreateRequest): - code (string, required) - name (string, required) - description (string, optional) - is_taxable (boolean, optional) Responses: 201, 400, 401, 403, 409, 422 ### POST /api/v1/tax/codes/seed-defaults Seed default tax codes Seed default tax codes for the business (if none exist). Responses: 200, 401, 403, 422 ### DELETE /api/v1/tax/codes/{code_id} Deactivate tax code Deactivate a tax code (soft delete). Parameters: - code_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/tax/codes/{code_id} Get tax code Get details of a specific tax code. Parameters: - code_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/tax/codes/{code_id} Update tax code Update an existing tax code. Parameters: - code_id (path, string, required) Request body (TaxCodeUpdateRequest): - code (string, optional) - name (string, optional) - description (string, optional) - is_taxable (boolean, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/tax/dedupe-imported Dedupe imported tax data Deactivate duplicate tax rates and codes created by integration imports. Responses: 200, 401, 403, 422 ### GET /api/v1/tax/rates List tax rates List all tax rates for the business. Parameters: - active_only (query, boolean, optional): Only show active rates - country (query, string, optional): Filter by country - state (query, string, optional): Filter by state Responses: 200, 401, 403, 422 ### POST /api/v1/tax/rates Create tax rate Create a new tax rate. Request body (TaxRateCreateRequest): - name (string, required) - rate (number | string, required) - tax_type (string, optional) - country (string, optional) - state (string, optional) - county (string, optional) - city (string, optional) - tax_liability_ledger_id (string, optional) - is_default (boolean, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/tax/rates/default Get default tax rate Get the default tax rate for the business. Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/tax/rates/{rate_id} Deactivate tax rate Deactivate a tax rate (soft delete). Parameters: - rate_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/tax/rates/{rate_id} Get tax rate Get details of a specific tax rate. Parameters: - rate_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/tax/rates/{rate_id} Update tax rate Update an existing tax rate. Parameters: - rate_id (path, string, required) Request body (TaxRateUpdateRequest): - name (string, optional) - rate (number | string, optional) - tax_type (string, optional) - country (string, optional) - state (string, optional) - county (string, optional) - city (string, optional) - tax_liability_ledger_id (string, optional) - is_default (boolean, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/tax/suggest-rates AI tax rate suggestions Get AI-powered tax rate suggestions for the business. Responses: 201, 401, 403, 422 ## time-entries ### GET /api/v1/time-entries List time entries List time entries, optionally filtered by customer/project/status. Parameters: - customer_id (query, string, optional): Filter by customer. - project_id (query, string, optional): Filter by project. - status (query, string, optional): Filter by status: unbilled, billed. - include_inactive (query, boolean, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/time-entries Create time entry Log a new time entry. Request body (TimeEntryCreateRequest): - entry_date (string · date, required) - duration_minutes (integer, required) - description (string, optional) - person_name (string, optional) - customer_id (string, optional) - project_id (string, optional) - catalog_item_id (string, optional) - billable (boolean, optional) - rate_cents (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/time-entries/add-to-invoice Add time to invoice Append billable time entries to a draft invoice and mark billed. Request body (AddToInvoiceRequest): - invoice_id (string, required) - time_entry_ids (array · string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/time-entries/billable List billable time Unbilled, billable time entries with computed billable amount. Parameters: - customer_id (query, string, optional): Filter by customer. - project_id (query, string, optional): Filter by project. Responses: 200, 401, 403, 422 ### DELETE /api/v1/time-entries/{time_entry_id} Delete time entry Soft-delete (deactivate) a time entry. Parameters: - time_entry_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/time-entries/{time_entry_id} Get time entry Get a single time entry. Parameters: - time_entry_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/time-entries/{time_entry_id} Update time entry Update a time entry. All fields optional. Parameters: - time_entry_id (path, string, required) Request body (TimeEntryUpdateRequest): - entry_date (string · date, optional) - duration_minutes (integer, optional) - description (string, optional) - person_name (string, optional) - customer_id (string, optional) - project_id (string, optional) - catalog_item_id (string, optional) - billable (boolean, optional) - rate_cents (integer, optional) - status (string, optional) - is_active (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ## transaction-clarifications ### GET /api/v1/transaction-clarifications List transaction clarifications (client) List questions your bookkeeper has asked about your transactions. Parameters: - status (query, string, optional): Filter by status: open, answered, resolved - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional) - include_total_count (query, boolean, optional) Responses: 200, 401, 403, 422 ### GET /api/v1/transaction-clarifications/count Open clarification count (client) Number of unanswered questions for the sidebar badge. Responses: 200, 401, 422 ### GET /api/v1/transaction-clarifications/firm List clarifications (firm) List all transaction questions across the firm's businesses. Parameters: - firm_id (query, string, optional): Advisory firm ID - business_id (query, string, optional): Filter by business - status (query, string, optional): Filter by status - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional) - include_total_count (query, boolean, optional) Responses: 200, 401, 403, 422 ### GET /api/v1/transaction-clarifications/firm/count Open clarification count (firm) Number of unanswered questions across the firm. Parameters: - firm_id (query, string, optional): Advisory firm ID Responses: 200, 401, 403, 422 ### POST /api/v1/transaction-clarifications/firm/transactions/{transaction_id} Ask the client about a transaction (firm) Attach a question to a transaction for the client to answer. Parameters: - transaction_id (path, string, required) Request body (CreateClarificationRequest): - question (string, required) - options (array · string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/transaction-clarifications/firm/{clarification_id}/resolve Resolve a clarification (firm) Close the loop after applying the client's answer. Parameters: - clarification_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### POST /api/v1/transaction-clarifications/{clarification_id}/answer Answer a clarification (client) Provide an answer to a question your bookkeeper asked. Parameters: - clarification_id (path, string, required) Request body (AnswerClarificationRequest): - answer (string, required) Responses: 201, 400, 401, 403, 404, 422 ## transactions — Bank transactions — sync, categorize, review, and reconcile. ### GET /api/v1/transactions List transactions Retrieve bank transactions with filtering by date, ledger, source account, counter ledger, journal entry, or tags. Parameters: - id (query, array · string, optional): Filter by specific transaction UUIDs - ledger_id (query, array · string, optional): Filter by bank account ledger UUIDs - opposing_ledger_id (query, string, optional): Filter by opposing ledger UUID (the category/expense ledger from journal entry line entries) - source_account_id (query, array · string, optional): Filter by Teal source account IDs (bank accounts from Plaid) - tag_id (query, array · string, optional): Filter by assigned tag UUIDs - journal_entry_id (query, string, optional): Filter by associated journal entry UUID - start_date (query, string · date-time, optional): Include transactions from this date (ISO 8601) - end_date (query, string · date-time, optional): Include transactions up to this date (ISO 8601) - search (query, string, optional): Search transactions by description or counterparty name (case-insensitive partial match) - amount_direction (query, string, optional): Filter by amount direction: 'inflow' for positive amounts (money in), 'outflow' for negative amounts (money out) - only_uncategorized (query, boolean, optional): Filter to show only uncategorized transactions (those without a journal entry or linked to system uncategorized ledgers) - missing_counterparty (query, boolean, optional): Filter to show only transactions with no counterparty assigned (empty or NULL). Useful for surfacing wires / miscellaneous deposits that the auto-detection pipeline intentionally left blank for the bookkeeper to fill in. - review_status (query, string, optional): Filter by review status. Accepts 'reviewed', 'unreviewed', or a comma-separated combination. Must mirror the same flag on the export endpoint so the visible list and the exported file stay in sync. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/transactions Create transaction Manually create a transaction record (not synced from bank). Request body (TransactionCreateRequest): - amount (integer, optional) - currency (string, optional) - datetime (string · date, optional) - description (string, optional) - source_account_id (string, optional) - reconciled (boolean, optional) Responses: 201, 400, 401, 403, 422 ### PATCH /api/v1/transactions/batch/category Batch update category Batch update category for multiple transactions. Updates the opposing line entries' ledger to the specified ledger, effectively recategorizing the transactions. Request body (TransactionBulkUpdateCategoryRequest): - transaction_ids (array · string, required) - ledger_id (string, required) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/transactions/batch/counterparty Batch update counterparty Set the counterparty name for multiple transactions at once. Request body (TransactionBulkUpdateCounterpartyRequest): - transaction_ids (array · string, required) - counterparty (string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/transactions/batch/delete Delete selected transactions Permanently delete manual/bulk-uploaded transactions, hard-delete Plaid transactions on the Teal engine (via Teal API), or soft-delete (hide) Plaid transactions on the DayZero local engine, by explicit IDs. Request body (TransactionBulkDeleteRequest): - transaction_ids (array · string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/transactions/batch/mark-reviewed-by-period Mark all unreviewed transactions in a period as reviewed Bulk-mark every unreviewed transaction whose date falls within [period_start, period_end] as reviewed. Used by the monthly close checklist's 'Mark all reviewed' CTA. Request body (TransactionMarkReviewedByPeriodRequest): - period_start (string · date, required) - period_end (string · date, required) Responses: 201, 400, 401, 403, 422 ### PATCH /api/v1/transactions/batch/reconciliation-status Batch update reconciliation status Mark multiple transactions as reconciled or unreconciled. Request body (TransactionMarkReconciledRequest): - transaction_ids (array · string, optional) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/transactions/batch/review-status Batch update review status Mark multiple transactions as reviewed or unreviewed in one request. Request body (TransactionMarkReviewedRequest): - transaction_ids (array · string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/transactions/bulk Bulk create transactions Create multiple manual transactions in a single request. Request body (BulkTransactionCreateRequest): - transactions (array · TransactionCreateRequest, required) → TransactionCreateRequest Responses: 201, 400, 401, 403, 422 ### POST /api/v1/transactions/bulk-upload Bulk upload transactions to new source account Upload historical transactions from a CSV/Excel file to a new source account. Request body (BulkTransactionUploadRequest): - ledger_id (string, optional) - ledger_name (string, optional) - source_account_name (string, optional) - business_personal (string, optional) - invert_transaction_amounts (boolean, optional) - s3_key (string, required) - start_date (string · date, optional) - end_date (string · date, optional) - check_duplicates (boolean, optional) - duplicate_match_fields (array · string, optional) - skip_categorization (boolean, optional) - generate_bank_rules (boolean, optional) - currency (string, optional) - editable (boolean, optional) - type (string, optional) - description (string, optional) - status (string, optional) - debit_credit (string, optional) - sort_code (string, optional) - sub_type (string, optional) - report_cash_flow (boolean, optional) - financial_account_type (string, optional) - parent_id (string, optional) - column_mapping (object, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/transactions/bulk-upload/headers Get CSV/Excel column headers for mapping Return the column headers, sample rows, and a suggested mapping for a previously Request body (BulkUploadHeadersRequest): - s3_key (string, required) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/transactions/bulk-upload/paste Upload pasted transaction text Accept raw CSV/TSV text (e.g. copied from a bank export or spreadsheet) and Request body (PasteTransactionTextRequest): - pasted_text (string, required) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/transactions/bulk-upload/preview Preview bulk transaction upload Preview and validate a CSV/Excel file before uploading transactions. Request body (BulkTransactionUploadPreviewRequest): - s3_key (string, required) - start_date (string · date, optional) - end_date (string · date, optional) - check_duplicates (boolean, optional) - ledger_id (string, optional) - financial_account_type (string, optional) - column_mapping (object, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/transactions/bulk-upload/template Download bulk transaction upload CSV template Get a sample CSV template with the expected columns for bulk transaction upload. Responses: 200, 401, 403, 422 ### DELETE /api/v1/transactions/by-date-range Delete transactions by date range Permanently delete every transaction for the current business whose date falls between ``start_date`` and ``end_date`` (inclusive). Journal entries that become orphaned are deleted along with their line entries, and for businesses still backed by the legacy Teal engine the matching Teal transactions and journal entries are removed remotely as well. This action is irreversible. Parameters: - start_date (query, string · date, required): Inclusive start date (YYYY-MM-DD). Transactions on or after this date are deleted. - end_date (query, string · date, required): Inclusive end date (YYYY-MM-DD). Transactions on or before this date are deleted. - ledger_id (query, string, optional): Optional ledger ID to restrict the deletion to a single bank account. Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/transactions/categorize-all AI-categorize all uncategorized transactions Triggers the full AI categorization workflow (Temporal) which processes all uncategorized transactions using bank rules, historical patterns, and LLM categorization. High-confidence results are auto-applied and bank rules are created for future transactions. Returns immediately with a workflow ID. Responses: 201, 400, 401, 403, 422 ### POST /api/v1/transactions/recategorize-by-counterparty Recategorize all transactions for a counterparty Move all uncategorized transactions for a given counterparty to the specified ledger. Request body (RecategorizeByCounterpartyRequest): - counterparty (string, required) - ledger_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/transactions/suggest-categories Category suggestions Suggest ledger categories for uncategorized transactions. By default only fast pattern-matching layers run (<1 s). Pass include_ai=true to also run LLM categorization for transactions the pattern layers could not resolve. Parameters: - limit (query, integer, optional) - only_uncategorized (query, boolean, optional) - include_ai (query, boolean, optional) Responses: 201, 401, 403, 422 ### POST /api/v1/transactions/suggested-matches/batch Suggested matches for a batch of transactions Score a batch of bank transactions against open invoices (for money in) and open bills (for money out) and return ranked suggested matches, QBO-style. Used to render the inline 'N matches found' badge for the visible page of the Transactions list. Results are ephemeral (no suggestion records are persisted). Request body (BulkSuggestedMatchesRequest): - transaction_ids (array · string, required) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/transactions/suggested-matches/dismiss Dismiss a suggested match Hide a specific invoice or bill suggestion for a bank transaction. Dismissed pairs are excluded from future inline suggested-match scoring until restored. Request body (DismissSuggestedMatchRequest): - transaction_id (string, required) - type (string, required) - target_id (string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/transactions/{transaction_id} Delete or hide transaction Permanently delete a manual/CSV-imported transaction, or soft-delete (hide) a Plaid-synced transaction on the DayZero local engine. Parameters: - transaction_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/transactions/{transaction_id} Get transaction Retrieve a specific transaction with all details. Parameters: - transaction_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/transactions/{transaction_id} Update transaction Update transaction metadata like tags, review status, or description. Parameters: - transaction_id (path, string, required) Request body (TransactionUpdateRequest): - amount (integer, optional) - datetime (string · date, optional) - description (string, optional) - counterparty (string, optional) - meta (object, optional) - categorization_method (string, optional) - posted_status (string, optional) - review_status (string, optional) - opposing_line_entry_ids (array · string, optional) - personal (boolean, optional) - journal_entry_id (string, optional) - teal_source_account_id (string, optional) - ledger_id (string, optional) - reconciled (boolean, optional) - invoice_id (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/transactions/{transaction_id}/allocations Get transaction allocations Return how much of a transaction has been allocated to invoices/bills and the remaining unallocated balance. All amounts in cents. Parameters: - transaction_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/transactions/{transaction_id}/attachment Attach file to transaction Upload and attach a document file (receipt, invoice, etc.) to a transaction. Parameters: - transaction_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/transactions/{transaction_id}/restore Restore a hidden transaction Un-hide a previously soft-deleted Plaid transaction on the DayZero local accounting engine. Re-creates the auto journal entry using the same categorization rules a fresh sync would apply. Parameters: - transaction_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/transactions/{transaction_id}/suggested-matches Suggested matches for a transaction Return ranked suggested matches for a single bank transaction. Income transactions are matched against open invoices; expense transactions against open bills. Matches are scored by amount, date proximity, memo, and counterparty similarity. Parameters: - transaction_id (path, string, required) - limit (query, integer, optional): Max suggested matches Responses: 200, 401, 403, 404, 422 ## user — User profile and preferences. ### GET /api/v1/users List users List users with optional filtering. Pass x-business-id header or business_id for business-scoped view; firm_id for firm-wide view. Parameters: - search (query, string, optional): Search users by name or email - id (query, string, optional): Filter by user UUID - email (query, string, optional): Filter by exact email address - business_id (query, string, optional): Filter by business UUID. Returns users in that business only. - firm_id (query, string, optional): Filter by advisory firm UUID. Firm-wide: all users in that firm. - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending - x-firm-id (header, string, optional): Advisory firm UUID for firm-wide view. Same as firm_id query param. Responses: 200, 401, 403, 422 ### POST /api/v1/users Create user Create a new user account in the system. Request body (UserCreate): - email (string · email, required) - first_name (string, optional) - last_name (string, optional) - photo_url (string, optional) - onboarded (boolean, optional) - props (object, optional) Responses: 201, 400, 403, 409, 422 ### GET /api/v1/users/me/last-business Get last-selected business Return the authenticated user's last-selected business and firm IDs. Responses: 200, 401, 404, 422 ### PUT /api/v1/users/me/last-business Set last-selected business Persist the authenticated user's last-selected business and optional firm ID. Request body (LastBusinessUpdate): - business_id (string, required) - firm_id (string, optional) Responses: 200, 400, 401, 404, 422 ### GET /api/v1/users/me/mfa-status Get MFA enforcement status Return whether MFA is required and satisfied for the authenticated user based on Clerk session claims and firm policy. Responses: 200, 401, 422 ### DELETE /api/v1/users/{user_id} Delete user Permanently delete a user and all their business associations. Parameters: - user_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/users/{user_id} Get user Retrieve a specific user's profile by their UUID. Parameters: - user_id (path, string · uuid, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/users/{user_id} Update user Update a user's profile information. Parameters: - user_id (path, string, required) Request body (UserUpdate): - first_name (string, optional) - last_name (string, optional) - photo_url (string, optional) - onboarded (boolean, optional) - props (object, optional) Responses: 200, 400, 401, 403, 404, 422 ## vendor-contracts — Vendor contract tracking with renewal alerts and spend analysis. ### GET /api/v1/vendor-contracts List vendor contracts List all vendor contracts/MSAs for the business. Parameters: - vendor_id (query, string, optional): Filter by vendor UUID - status (query, string, optional): Filter by status: draft, active, expired, terminated - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/vendor-contracts Create vendor contract Create a new vendor contract/MSA. Request body (VendorContractCreateRequest): - name (string, required) - vendor_id (string, required) - status (string, optional) - effective_date (string · date, optional) - expiration_date (string · date, optional) - contracted_amount_cents (integer, optional) - billing_frequency (string, optional) - terms_summary (string, optional) - s3_key (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/vendor-contracts/analyze AI contract analysis Get AI-powered vendor contract analysis. Responses: 201, 401, 403, 422 ### DELETE /api/v1/vendor-contracts/{contract_id} Delete vendor contract Delete a vendor contract. Parameters: - contract_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/vendor-contracts/{contract_id} Get vendor contract Get details of a specific vendor contract. Parameters: - contract_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/vendor-contracts/{contract_id} Update vendor contract Update an existing vendor contract. Parameters: - contract_id (path, string, required) Request body (VendorContractUpdateRequest): - name (string, optional) - status (string, optional) - effective_date (string · date, optional) - expiration_date (string · date, optional) - contracted_amount_cents (integer, optional) - billing_frequency (string, optional) - terms_summary (string, optional) - s3_key (string, optional) Responses: 200, 400, 401, 403, 404, 422 ## vendor-credits ### GET /api/v1/vendor-credits List vendor credits List all vendor credits for the business with optional filtering. Parameters: - vendor_id (query, string, optional): Filter by vendor ID - status (query, VendorCreditStatusEnum, optional): Filter by status - has_remaining_balance (query, boolean, optional): Only show credits with remaining balance - search (query, string, optional): Search by vendor credit number (e.g. VC-0001) - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/vendor-credits Create vendor credit Create a new vendor credit in draft status. Request body (VendorCreditCreate): - vendor_id (string, required) - amount_cents (integer, required) - reason (VendorCreditReasonEnum, optional) → VendorCreditReasonEnum - description (string, optional) - bill_id (string, optional) - ledger_id (string, optional) - issue_date (string · date, optional) - line_items (array · VendorCreditLineItem, optional) - internal_notes (string, optional) - vendor_credit_number (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/vendor-credits/vendors/{vendor_id}/credits Get vendor available credits Get summary of available vendor credits for a vendor. Parameters: - vendor_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/vendor-credits/{vendor_credit_id} Delete vendor credit Delete a vendor credit. Applied credits are removed from linked bills and those bills are recalculated. Void credits cannot be deleted. Parameters: - vendor_credit_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/vendor-credits/{vendor_credit_id} Get vendor credit Get a specific vendor credit by ID. Parameters: - vendor_credit_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/vendor-credits/{vendor_credit_id} Update vendor credit Update a draft vendor credit. Parameters: - vendor_credit_id (path, string, required) Request body (VendorCreditUpdate): - amount_cents (integer, optional) - reason (VendorCreditReasonEnum, optional) - description (string, optional) - bill_id (string, optional) - ledger_id (string, optional) - issue_date (string · date, optional) - line_items (array · VendorCreditLineItem, optional) - internal_notes (string, optional) - vendor_credit_number (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/vendor-credits/{vendor_credit_id}/apply Apply vendor credit to bill Apply a portion or all of a vendor credit to a bill. Parameters: - vendor_credit_id (path, string, required) Request body (VendorCreditApplicationCreate): - bill_id (string, required) - amount_cents (integer, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/vendor-credits/{vendor_credit_id}/issue Issue vendor credit Issue a draft vendor credit, making it available for application to bills. Parameters: - vendor_credit_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/vendor-credits/{vendor_credit_id}/void Void vendor credit Void a vendor credit. Only allowed for unapplied (draft or issued) credits. Parameters: - vendor_credit_id (path, string, required) Request body (VendorCreditVoid): - reason (string, required) Responses: 201, 400, 401, 403, 404, 422 ## vendors — Vendor management — accounts payable counterparties. ### GET /api/v1/vendors List vendors Retrieve all suppliers/vendors for the business. Parameters: - search (query, string, optional): Fuzzy search vendors by name (uses trigram matching) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 422 ### POST /api/v1/vendors Create vendor Add a new supplier/vendor. Request body (VendorCreateRequest): - name (string, required) - email (string · email, required) - address (string, optional) - phone (string, optional) - tax_id (string, optional) - website (string, optional) - notes (string, optional) - credit_limit_cents (integer, optional) - default_payment_term_id (string, optional) - status (CounterpartyStatus, optional) → CounterpartyStatus - category (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/vendors/bulk-upload Bulk upload vendors from CSV Upload a CSV file to create multiple vendors at once. Duplicate emails are skipped. Returns created/skipped counts and per-row errors. Responses: 201, 400, 401, 403, 422 ### GET /api/v1/vendors/bulk-upload/template Download bulk vendor upload CSV template Get a sample CSV template with the expected columns for bulk vendor upload. Responses: 200, 401, 403, 422 ### GET /api/v1/vendors/check-duplicates Check for duplicate vendors Check if a vendor already exists using name, email, tax ID similarity. Parameters: - name (query, string, required): Vendor name to check - email (query, string, optional): Email to check - tax_id (query, string, optional): Tax ID to check - exclude_id (query, string, optional): Vendor ID to exclude (for updates) Responses: 200, 401, 403, 422 ### DELETE /api/v1/vendors/{id} Delete vendor Soft delete a vendor record. The vendor is marked as deleted and hidden from list endpoints, but remains retrievable by ID so existing references on bills and other records stay intact. Parameters: - id (path, string, required) Responses: 200, 401, 403, 404, 409, 422 ### GET /api/v1/vendors/{id} Get vendor Retrieve a specific vendor by ID. Parameters: - id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/vendors/{id} Update vendor Update vendor contact information. Parameters: - id (path, string, required) Request body (VendorUpdateRequest): - name (string, optional) - email (string · email, optional) - address (string, optional) - phone (string, optional) - tax_id (string, optional) - website (string, optional) - notes (string, optional) - credit_limit_cents (integer, optional) - default_payment_term_id (string, optional) - status (CounterpartyStatus, optional) - category (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/vendors/{vendor_id}/contacts List vendor contacts Retrieve all contacts for a vendor. Parameters: - vendor_id (path, string, required) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - sort_by (query, string, optional): Column name to sort by (e.g. 'created_at', 'amount', 'name'). When changing sort, reset cursor to None. - descending (query, boolean, optional): Sort direction: true for descending (newest/largest first), false for ascending Responses: 200, 401, 403, 404, 422 ### POST /api/v1/vendors/{vendor_id}/contacts Create vendor contact Add a new contact to a vendor. Parameters: - vendor_id (path, string, required) Request body (EntityContactCreateRequest): - name (string, required) - email (string · email, optional) - phone (string, optional) - role (string, optional) - is_primary (boolean, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/vendors/{vendor_id}/contacts/{contact_id} Delete vendor contact Delete a vendor contact. Parameters: - vendor_id (path, string, required) - contact_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/vendors/{vendor_id}/contacts/{contact_id} Get vendor contact Retrieve a specific vendor contact. Parameters: - vendor_id (path, string, required) - contact_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/vendors/{vendor_id}/contacts/{contact_id} Update vendor contact Update an existing vendor contact. Parameters: - vendor_id (path, string, required) - contact_id (path, string, required) Request body (EntityContactUpdateRequest): - name (string, optional) - email (string · email, optional) - phone (string, optional) - role (string, optional) - is_primary (boolean, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422