# 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 ## activation ### GET /api/v1/activation Get activation status Returns the business's post-onboarding activation milestones. Derived milestones (bank connected, first transactions, teammate added, first invoice) are recomputed from live data on each call. Responses: 200, 401, 403, 404, 422 ### POST /api/v1/activation/sample-preview Seed an isolated sample-data preview Seeds a small set of clearly-labeled, isolated sample transactions so a brand-new workspace isn't empty. No-ops if the business already has real transactions or a preview. Auto-purged when the first real bank sync lands. Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/activation/{milestone} Record a client-reported activation milestone Records an explicit, client-only activation milestone (e.g. first_report, coa_reviewed, first_value_moment). Idempotent: recording an already-completed milestone is a no-op. Derived milestones cannot be set here. Parameters: - milestone (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## advisory-firm-entity-groups ### GET /api/v1/advisory-firms/{firm_id}/entity-groups List entity groups List all entity groups for a firm with their member businesses. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 422 ### POST /api/v1/advisory-firms/{firm_id}/entity-groups Create entity group Create a named group of related client businesses. Requires owner or admin role. Initial members may be provided via business_ids. Parameters: - firm_id (path, string, required) Request body (CreateEntityGroupRequest): - name (string, required) - description (string, optional) - group_type (string, optional) - primary_business_id (string, optional) - business_ids (array · string, optional) Responses: 201, 400, 401, 403, 409, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/entity-groups/{group_id} Delete entity group Delete an entity group and its memberships. Member businesses themselves are not affected. Parameters: - firm_id (path, string, required) - group_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/entity-groups/{group_id} Get entity group Get an entity group with its member businesses. Parameters: - firm_id (path, string, required) - group_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/advisory-firms/{firm_id}/entity-groups/{group_id} Update entity group Update a group's name, description, type, or primary entity. Parameters: - firm_id (path, string, required) - group_id (path, string, required) Request body (UpdateEntityGroupRequest): - name (string, optional) - description (string, optional) - group_type (string, optional) - primary_business_id (string, optional) Responses: 200, 400, 401, 403, 404, 409, 422 ### POST /api/v1/advisory-firms/{firm_id}/entity-groups/{group_id}/businesses Add business to entity group Add an active firm client business to an entity group. Parameters: - firm_id (path, string, required) - group_id (path, string, required) Request body (EntityGroupMemberRequest): - business_id (string, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### PUT /api/v1/advisory-firms/{firm_id}/entity-groups/{group_id}/businesses Set entity group businesses Replace the group's membership with the given business set. Parameters: - firm_id (path, string, required) - group_id (path, string, required) Request body (SetEntityGroupMembersRequest): - business_ids (array · string, required) Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/entity-groups/{group_id}/businesses/{business_id} Remove business from entity group Remove a business from an entity group. Parameters: - firm_id (path, string, required) - group_id (path, string, required) - business_id (path, string, required) Responses: 200, 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. Requires a JSON body with 'confirm_password' — the caller's own account password. Parameters: - firm_id (path, string, required) Request body (AdvisoryFirmDeleteRequest): - confirm_password (string · password, 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}/menu Set a firm staff member's sidebar menu Restrict which app sections a firm staff member can reach, either firm-wide or for one client business. Firm staff reach clients *through the firm* and have no membership row there, so the per-membership menu could not be stored for them at all — this is where their menu lives instead. Unlike the per-membership menu, this one is enforced server-side: a request for a section outside the menu is rejected with 403, not merely hidden from the sidebar. Parameters: - firm_id (path, string, required) - user_id (path, string, required) Request body (FirmStaffMenuRequest): - business_id (string, optional) - visible_sections (array · string, optional) 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-chat-public ### GET /api/v1/share/{token} Public conversation viewer (unauthenticated) Resolve a share token to a read-only conversation snapshot. Organization-only links answer 401 so the viewer can sign in and retry the member endpoint. Parameters: - token (path, string, required) Responses: 200, 401, 404, 422, 429 ### GET /api/v1/share/{token}/member Shared conversation viewer (authenticated) Resolve a share token for a signed-in viewer. Organization-only links require access to the business the conversation belongs to. Parameters: - token (path, string, required) Responses: 200, 401, 403, 404, 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 default path assembles the file natively from the stored DeckSpec (python-pptx / WeasyPrint) — searchable text, real tables, server-rendered charts. Clients may still upload rendered slide PNGs as multipart parts to force the legacy screenshot pipeline. 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}/refresh Refresh a deck with live data Re-pull live CFO-portal data into every data-bound slide of an existing deck. Titles, ordering, hidden flags, and commentary are preserved (pass regenerate_commentary=true to refresh the AI commentary too). The reporting period stays fixed — a refresh means 'same window, current numbers'. Parameters: - deck_id (path, string, required) 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 Add a slide to a deck Insert a new slide. Data-bound types are populated from live CFO portal data (with AI commentary); 'custom' slides use the supplied markdown. Optional 0-based position, defaults to the end. Parameters: - deck_id (path, string, required) Request body (AddSlideRequest): - slide_type (string, required) - title (string, optional) - markdown (string, optional) - position (integer, optional) Responses: 201, 400, 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) - markdown (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/ai-reporting/decks/{deck_id}/slides/{slide_id}/duplicate Duplicate a slide Copy a slide (new id, same content, title suffixed with '(copy)') directly after the original. No body required. Parameters: - deck_id (path, string, required) - slide_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/ai-reporting/decks/{deck_id}/slides/{slide_id}/refresh Refresh a single slide with live data Re-pull live data for one data-bound slide. 400s for slide types that carry no live data (cover, asks, custom). Pass regenerate_commentary=true to refresh its AI commentary too. Parameters: - deck_id (path, string, required) - slide_id (path, string, required) Responses: 201, 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. 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, 400, 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/completed List completed AP approvals List recently approved or rejected bills that have left the pending queue (their workflow run reached a terminal state). Parameters: - limit (query, integer, optional): Maximum number of completed rows to return. Responses: 200, 401, 403, 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: - id (query, array · string, optional): Filter bills by specific bill UUIDs (batch fetch — avoids N+1 single-bill requests) - 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) - bill_number (query, string, optional): Filter bills by invoice/bill number (case-insensitive partial match) - due_date_from (query, string, optional): Filter bills with due date on or after this date (YYYY-MM-DD) - due_date_to (query, string, optional): Filter bills with due date on or before this date (YYYY-MM-DD) - 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) - bill_number (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/duplicate-check Check for possible duplicate bills Advisory, read-only check used during bill entry. Returns existing bills that may duplicate the one being entered (same bill number, or same vendor and amount within ~90 days). Never blocks creation — it is purely a heads-up so the user can confirm before saving. Parameters: - bill_number (query, string, optional): In-progress bill/invoice number to match (optional). - vendor_id (query, string, optional): In-progress vendor UUID to match (optional). - amount (query, integer, optional): In-progress amount in cents to match (optional). - received_on (query, string · date, optional): Invoice date (YYYY-MM-DD); bounds the vendor+amount lookback window. Responses: 200, 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, plus the overdue balance across bills past their due date. 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 and/or direct expense lines 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, optional) → BillBatchPaymentAllocation - expense_lines (array · BillBatchExpenseLine, optional) → BillBatchExpenseLine - 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 ### GET /api/v1/bills/upcoming List upcoming (expected) bills Forecast rows for bills the business expects — recurring contractor payments, retainers. These are estimates: they carry no journal entries and are excluded from AP totals, aged payables and the balance sheet. Parameters: - status (query, string, optional): Filter by status: expected, converted, dismissed. - vendor_id (query, string, optional): Filter by vendor. - date_from (query, string · date, optional): Only occurrences expected on or after this date. - date_to (query, string · date, optional): Only occurrences expected on or before this date. - limit (query, integer, optional): Maximum rows to return. Responses: 200, 401, 403, 422 ### POST /api/v1/bills/upcoming Create an upcoming bill (optionally recurring) Create one expected bill, or a recurring series. A recurring request materialises every projected occurrence up front (capped by `recurrence.occurrences`), so each one can be edited, deleted or matched independently. Returns every occurrence created. Request body (UpcomingBillCreateRequest): - vendor_id (string, optional) - vendor_name (string, optional) - description (string, optional) - estimated_amount (integer, required) - expected_date (string · date, required) - notes (string, optional) - recurrence (UpcomingBillRecurrence, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/bills/upcoming/{upcoming_bill_id} Delete an upcoming bill Deletes this occurrence. Pass `series=true` to also drop every later still-expected occurrence in the same series; occurrences already converted into real bills are never touched. Parameters: - upcoming_bill_id (path, string, required) - series (query, boolean, optional): Also delete later still-expected occurrences. Responses: 200, 401, 403, 404, 422 ### GET /api/v1/bills/upcoming/{upcoming_bill_id} Get one upcoming bill Parameters: - upcoming_bill_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/bills/upcoming/{upcoming_bill_id} Edit an upcoming bill Edits this occurrence only, not the whole series — so a one-month change to a recurring estimate does not redefine the schedule. Parameters: - upcoming_bill_id (path, string, required) Request body (UpcomingBillUpdateRequest): - vendor_id (string, optional) - vendor_name (string, optional) - description (string, optional) - estimated_amount (integer, optional) - expected_date (string · date, optional) - notes (string, optional) - status (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/bills/upcoming/{upcoming_bill_id}/convert Push an upcoming bill into a real bill Creates a **draft** bill from the estimate and marks the forecast row converted. The bill posts nothing to the ledger until it is approved and marked received, exactly like a hand-entered bill. Parameters: - upcoming_bill_id (path, string, required) Request body (UpcomingBillConvertRequest): - amount (integer, optional) - due_date (string · date, optional) - bill_number (string, optional) - s3_key (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/upcoming/{upcoming_bill_id}/link Match an upcoming bill to a bill that arrived Links the forecast row to an existing bill so the obligation stops being counted twice — once as a projection and once as a payable. Parameters: - upcoming_bill_id (path, string, required) Request body (UpcomingBillLinkRequest): - bill_id (string, required) Responses: 201, 400, 401, 403, 404, 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 ### PATCH /api/v1/bills/{bill_id}/payment-initiation Mark or clear bill payment-initiated Track money in flight: mark that payment was initiated outside DayZero (e.g. a bank with no API) before the bank transaction exists to reconcile against. Parameters: - bill_id (path, string, required) Request body (BillPaymentInitiationRequest): - initiated (boolean, required) - initiated_on (string · date-time, optional) Responses: 200, 400, 401, 403, 404, 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 ### POST /api/v1/bills/{bill_id}/resolve-approval Force-resolve a stuck pending-approval bill Manually drive a bill that is wedged in 'pending_approval' to a terminal state when the normal Approve/Reject card is unavailable (the approval run never started, crashed before suspending, or suspended on a gate with no approvers). Cancels any in-flight approval run for the bill, then either approves it to 'received' (posting the journal entry) or rejects it to 'canceled'. Parameters: - bill_id (path, string, required) Request body (ResolvePendingApprovalRequest): - action (string, required) - ledger_id (string, optional) - reason (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/{bill_id}/submit-for-ap-approval Submit bill for AP approval Manually route a forecasted bill into the firm's AP approval workflow. Use when a bill was approved before the workflow was provisioned or when automatic routing did not run. The bill moves from 'forecasted' to 'pending_approval' and posts to 'received' only once an approver signs off. 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: 201, 400, 401, 403, 404, 422 ### POST /api/v1/bills/{bill_id}/submit-for-approval Send bill for approval Submit a cleaned-up draft bill into the AP approval workflow. The bill moves to 'pending_approval' and is NOT posted to the general ledger until an approver signs off, at which point it posts to 'received'. Requires the business to have the approval workflow enabled. 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: 201, 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 - include_total_count (query, boolean, optional): Whether to include total count (expensive - avoid if possible) - cursor (query, string, optional): Cursor for pagination - limit (query, integer, optional): Pagination limit - direction (query, string, optional): Pagination direction: 'next' or 'prev' - 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/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) - payment_details (InvoicePaymentDetails, 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 ### GET /api/v1/businesses/plaid-transaction-settings Get Plaid pending-transaction settings Whether this business ingests & displays Plaid pending (unsettled) transactions as a read-only awareness layer. Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/plaid-transaction-settings Update Plaid pending-transaction settings Enable or disable ingesting & displaying Plaid pending transactions. Pending rows are financially inert (no journal entries) until they settle. Local accounting engine only. Request body (PlaidTransactionSettingsUpdateRequest): - show_pending_transactions (boolean, required) Responses: 200, 400, 401, 403, 404, 422 ### 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 and/or sidebar section visibility in this business. Parameters: - user_id (path, string, required) Request body (BusinessUserRoleUpdateRequest): - role (Role, required) → Role - visible_sections (array · string, optional) 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) Request body (BusinessDeleteRequest): - confirm_password (string · password, required) - business_id (string, optional) - confirm_name (string, required) - preserve_teal_instance (boolean, optional) - preserve_dz_users (boolean, optional) Responses: 202, 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) - start_date (string · date, optional) - accounting_basis (string, optional) - mailbox (string, optional) - inventory_mode (string, optional) - ap_approval_enabled (boolean, optional) - auto_rename_counterparties (boolean, 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 - period_granularity (query, string, optional): Optional period grouping: monthly, quarterly, or yearly. When set, requires start_date and adds one as-of balance per period end between start_date and as_of_date. - start_date (query, string, optional): Start date (YYYY-MM-DD); required when period_granularity is set 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) Request body (AnalyzeBalanceSheetRequest): - as_of_date (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) Request body (AnalyzeCashFlowRequest): - start_date (string, required) - end_date (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) Request body (AnalyzeIncomeStatementRequest): - start_date (string, required) - end_date (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 ### POST /api/v1/businesses/{business_id}/mailbox Provision inbound mailbox Mint an inbound mailbox slug for a business that has none, so vendor bills can be forwarded to its AP inbox. Idempotent: a business that already has a mailbox gets it back unchanged (never overwritten). Parameters: - business_id (path, string, required) Responses: 200, 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 ### GET /api/v1/businesses/{business_id}/trial-balance Get trial balance Trial balance listing every ledger account with debit and credit balances as of a point-in-time date. All amounts in cents. 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 accounts with zero debit and credit balances 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) - billing_rule (string, optional) - unit_label (string, optional) - unit_price (number | string, 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 ### GET /api/v1/catalog-items/{bundle_id}/components List a bundle's components Explode a bundle catalog item into its component items + quantities. Parameters: - bundle_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/catalog-items/{bundle_id}/components Set a bundle's components Replace a bundle's component set wholesale. Parameters: - bundle_id (path, string, required) Request body (BundleComponentsSetRequest): - components (array · BundleComponentInput, optional) → BundleComponentInput Responses: 200, 400, 401, 403, 404, 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) - billing_rule (string, optional) - unit_label (string, optional) - unit_price (number | string, 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 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/covenants/{covenant_id} Delete a debt covenant Remove a covenant rule from a debt instrument. Parameters: - business_id (path, string, required) - covenant_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id} Soft-delete a debt instrument Remove a debt instrument from the schedule (soft delete). Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id} Update a debt instrument Update editable fields on an existing debt instrument. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Request body (DebtInstrumentUpdateRequest): - name (string, optional) - current_balance_cents (integer, optional) - notes (string, optional) - amount_repaid_cents (integer, optional) - revenue_share_bps (integer, optional) - total_repayment_cap_cents (integer, optional) - linked_ledger_id (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id}/covenants List covenants for a debt instrument List financial covenants attached to a debt instrument. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id}/covenants Add a covenant to a debt instrument Attach a financial covenant to a loan. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Request body (DebtCovenantCreateRequest): - metric_name (string, required) - operator (string, required) - threshold_value (string, required) - measurement_frequency (string, optional) - description (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id}/payment-schedule Get a loan's customizable paydown table Return the persisted, editable paydown schedule for a loan. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id}/payment-schedule/generate Generate or rebuild a loan's paydown table (Re)generate a loan's paydown table from its terms. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Request body (DebtScheduleGenerateRequest): - overwrite_custom (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/instruments/{instrument_id}/payment-schedule/rows Add a row to a loan's paydown table Add a user-defined payment row to a loan's paydown table. Parameters: - business_id (path, string, required) - instrument_id (path, string, required) Request body (DebtScheduleRowCreateRequest): - due_date (string · date, required) - principal_cents (integer, optional) - interest_cents (integer, optional) - note (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, optional) - cash_ledger_id (string, optional) - payment_date (string · date, optional) - memo (string, optional) - transaction_id (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 ### DELETE /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/payment-schedule/rows/{row_id} Delete a loan paydown-table row Remove a row from a loan's paydown table. Parameters: - business_id (path, string, required) - row_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/businesses/{business_id}/cfo-portal/debt-schedule/payment-schedule/rows/{row_id} Edit a loan paydown-table row Edit a single paydown-table row (date, principal, interest, status). Parameters: - business_id (path, string, required) - row_id (path, string, required) Request body (DebtScheduleRowUpdateRequest): - due_date (string · date, optional) - principal_cents (integer, optional) - interest_cents (integer, optional) - status (string, optional) - note (string, optional) Responses: 200, 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 ## change-orders ### GET /api/v1/change-orders List change orders List change orders, optionally filtered by contract. Parameters: - contract_id (query, string, optional): Filter by contract ID - status (query, string, optional): Filter by status - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/change-orders Create change order Create a versioned amendment against a live contract. Request body (ChangeOrderCreate): - contract_id (string, required) - description (string, optional) - value_delta_cents (integer, required) - hours_delta (number | string, optional) - delta_lines (object, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/change-orders/{change_order_id} Delete change order Delete a draft change order. Parameters: - change_order_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/change-orders/{change_order_id} Get change order Get a change order by ID. Parameters: - change_order_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/change-orders/{change_order_id} Update change order Update a draft change order. Revert a sent or signed change order to draft first (this clears its signatures). Parameters: - change_order_id (path, string, required) Request body (ChangeOrderUpdate): - description (string, optional) - value_delta_cents (integer, optional) - hours_delta (number | string, optional) - delta_lines (object, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/change-orders/{change_order_id}/countersign Countersign change order Countersign and apply the value delta to the contract. Parameters: - change_order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/change-orders/{change_order_id}/revert-to-draft Revert change order to draft Send a sent or signed (not countersigned) change order back to draft so it can be edited. Clears the sent/signed timestamps and any signed file. Parameters: - change_order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/change-orders/{change_order_id}/send Send change order Send a change order (draft -> sent). Parameters: - change_order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/change-orders/{change_order_id}/sign Mark change order signed Mark a change order signed (sent -> signed). Parameters: - change_order_id (path, string, required) Responses: 201, 400, 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) - apply_eliminations (query, boolean, optional): Remove intercompany due-to / due-from positions recorded by the cleanup engine from assets and liabilities - entity_group_id (query, string, optional): Optional entity group to scope the roll-up to; defaults to all active firm client businesses 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) - entity_group_id (query, string, optional): Optional entity group to scope the roll-up to; defaults to all active firm client businesses 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 ## contracts ### GET /api/v1/contracts List contracts List contracts for the business with optional filters. Parameters: - status (query, string, optional): Filter by contract status - customer_id (query, string, optional): Filter by customer ID - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/contracts Create a contract Create a contract directly from the Contracts UI. Provide either an existing order to promote (`order_id`) or a full order payload (`order`) to draft and countersign in one step. Both paths run the order through countersign, so the contract keeps a frozen order as its provenance. Request body (ContractCreate): - order_id (string, optional) - order (OrderCreate, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/cash-flow-forecast Weekly cash-inflow forecast Forward-looking weekly cash-inflow forecast projected from each live contract's order-line billing rules, bucketed into calendar weeks from the current week. Parameters: - weeks (query, integer, optional): Number of weeks to project. Responses: 200, 401, 403, 422 ### GET /api/v1/contracts/drawdowns List drawdowns (cross-contract queue) The portfolio-wide hours-approval queue. Parameters: - status (query, string, optional): Filter by drawdown status - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/contracts/drawdowns/{drawdown_id}/approve Approve a drawdown (hours gate) Approve a pending drawdown. The approver may correct the hours (the original is retained). Approval draws down hours and recognizes revenue by the hours-% method. Parameters: - drawdown_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/drawdowns/{drawdown_id}/return Return a drawdown to the submitter Return a pending drawdown entry. Parameters: - drawdown_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/inbound/hours Ingest an inbound hours submission (Slack/email reply) Turn a parsed Slack or email hours reply into a pending drawdown entry awaiting approval (the hours gate). The caller supplies the already-parsed fields; the target contract must belong to the business. Request body (HoursSubmissionRequest): - contract_id (string, required) - hours (number | string, required) - entry_date (string, required) - submitted_by (string, optional) - submitted_via (string, optional) - source_message_id (string, optional) - description (string, optional) - order_line_id (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/invoices Invoice review queue List contract-drafted invoices awaiting approval (invoice gate). Parameters: - contract_id (query, string, optional): Filter by contract ID - status (query, string, optional): Invoice status filter - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/contracts/invoices/reminders Send payment reminders in bulk Send payment-reminder emails for up to 100 contract invoices in one call. Applies the single-reminder rules per invoice (open/partially paid only, customer email required, 24h cooldown) and returns a per-invoice sent/skipped result instead of failing the whole batch. Request body (InvoiceReminderBatchRequest): - invoice_ids (array · string, required) Responses: 201, 401, 403, 422 ### PATCH /api/v1/contracts/invoices/{invoice_id} Edit a draft contract invoice Edit a drafted contract invoice's description, due date, and line items before approval. Line items are replaced wholesale and the invoice total is recomputed from them. Only drafts are editable — approved invoices are already billed. Parameters: - invoice_id (path, string, required) Request body (ContractInvoiceUpdateRequest): - description (string, optional) - due_date (string, optional) - line_items (array · ContractInvoiceLineUpdate, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/invoices/{invoice_id}/payments Apply a payment to a contract invoice Record a payment (amount, method, received date) against a contract invoice, transitioning it to partially/fully paid. Parameters: - invoice_id (path, string, required) Request body (ContractInvoicePaymentRequest): - amount_cents (integer, required) - method (string, optional) - received_date (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/invoices/{invoice_id}/reminder Send a payment reminder for a contract invoice Email the invoice's customer a payment reminder (amount due, due date, pay link when available). Only open or partially paid contract invoices qualify, and at most one reminder is sent per invoice per 24 hours. Parameters: - invoice_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/invoices/{invoice_id}/send Approve & send a contract invoice (invoice gate) Finalize a drafted contract invoice (draft -> open). This is when the amount becomes billed and any prepaid block books to Deferred Revenue. Parameters: - invoice_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/orders/intake-pdf Intake an order-form PDF into a draft order AI-extract an uploaded order-form PDF (referenced by its S3 key) into a DRAFT order for human review. Reuses the invoice document-extraction path. Upload the file first via the uploads endpoint, then pass the S3 key. Request body (OrderPdfIntakeRequest): - s3_key (string, required) - customer_id (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/reports/{report_type} Download an Order-to-Cash report (CSV/XLSX) Generate and download an O2C report as a CSV or XLSX file. Parameters: - report_type (path, string, required) - format (query, string, optional): Output format: 'csv' or 'xlsx'. Responses: 200, 400, 401, 403, 422 ### GET /api/v1/contracts/settings Get Order-to-Cash settings Return the business's O2C configuration defaults, or system defaults if none have been set. Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/contracts/settings Update Order-to-Cash settings Persist the business's O2C configuration defaults (invoice cadence, default net days, tax treatment, hours/invoice approver roles) and return the stored settings. Request body (O2CSettings): - invoice_cadence (InvoiceCadenceEnum, optional) → InvoiceCadenceEnum - default_net_days (integer, optional) - default_tax_treatment (string, optional) - hours_approver_role (string, optional) - invoice_approver_role (string, optional) - slack_team_id (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/summary Portfolio summary (dashboard KPIs) Business-wide contract KPIs aggregated in SQL across ALL contracts (not a capped page): total value, billed, recognized, deferred, and counts by status. Responses: 200, 401, 403, 422 ### GET /api/v1/contracts/summary/by-type Contracted value grouped by billing rule Total contracted line value bucketed by billing rule (prepaid_block, flat, per_unit, recurring, usage_metered) for the value-by-type chart. Responses: 200, 401, 403, 422 ### DELETE /api/v1/contracts/{contract_id} Delete contract Delete a contract on which no money has moved: nothing billed or recognized, no non-draft invoices linked, no approved drawdowns. Linked draft invoices are deleted with it; the originating order is left intact. Parameters: - contract_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/{contract_id} Get contract Get a contract with billed/earned/hours burndown figures. Parameters: - contract_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/contracts/{contract_id} Edit contract Edit a contract's descriptive/schedule fields (number, first invoice date, estimated end date). Running tallies (billed/recognized/value) are maintained by the billing engine and cannot be edited. Parameters: - contract_id (path, string, required) Request body (ContractUpdate): - number (string, optional) - status (string, optional) - first_invoice_date (string · date, optional) - end_date (string · date, optional) - line_items (array · ContractLineItemUpdate, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/{contract_id}/burndown Per-line hours burndown Hours burndown broken down per order line: attributed drawdowns count against their line; unattributed drawdowns draw the prepaid block pool and are reported on the block subtotal. Per-period billed lines also expose billed hours and derived recognized revenue. Parameters: - contract_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/check-completion Evaluate auto-completion Mark the contract completed when the term has elapsed or the prepaid block is fully consumed and billed. Returns the current contract state. Parameters: - contract_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/credit-memo Issue a contract-level credit memo Issue a credit memo for the contract's customer (a contract discount / concession), linking the contract id into the memo description. Parameters: - contract_id (path, string, required) Request body (ContractCreditMemoRequest): - amount_cents (integer, required) - reason (string, optional) - description (string, optional) - issue_date (string · date, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/draft-invoices Auto-draft invoices (invoice gate) Draft the execution invoice from the contract's billing rules. Prepaid blocks route to Deferred Revenue. Invoices are created as drafts for human review before sending. Parameters: - contract_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/{contract_id}/drawdowns List a contract's drawdowns List drawdown entries for a contract. Parameters: - contract_id (path, string, required) - status (query, string, optional): Filter by drawdown status - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/drawdowns Record a drawdown (hours consumed) Records hours consumed as a pending entry awaiting approval. Parameters: - contract_id (path, string, required) Request body (DrawdownCreate): - order_line_id (string, optional) - catalog_item_id (string, optional) - hours (number | string, required) - entry_date (string, required) - description (string, optional) - submitted_by (string, optional) - submitted_via (string, optional) - source_message_id (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/generate-period-invoices Draft the next period's invoices Draft the next billing period's invoice from recurring (each_period) and per-unit (approved hours x price) order lines. Idempotent per (contract, period): a period already drafted is not re-drafted. Parameters: - contract_id (path, string, required) Request body (GeneratePeriodInvoicesRequest): - period_date (string, required) Responses: 201, 400, 401, 403, 404, 422 ### PATCH /api/v1/contracts/{contract_id}/hours Set the contract's total purchased hours Update the contract's total contracted hours (the burndown denominator). Correction path for contracts whose hour total wasn't captured at countersign (e.g. per-unit blocks). The new total cannot be lower than the hours already drawn down. Parameters: - contract_id (path, string, required) Request body (ContractHoursUpdate): - total_hours (number | string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/invoices Manually draft an additional invoice Draft an additional (e.g. future-dated) invoice on the contract, outside the execution/period automation. Lines default to the contract's recurring (each-period) order lines when omitted. The draft is reviewed and sent like any other contract invoice. Parameters: - contract_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/pause Pause contract Pause an active contract. Parameters: - contract_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/reorder Reorder from a contract (follow-on block) Create a new draft order for the same customer/project, pre-filled from the contract's prepaid-block line(s) — a fresh block of the same qty, price, and billing rule. Lets a depleting contract spawn a follow-on order. Parameters: - contract_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/contracts/{contract_id}/resume Resume contract Resume a paused contract. Parameters: - contract_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/contracts/{contract_id}/suggested-invoices Suggested invoices to link Rank the business's unlinked (non-void, non-draft) invoices as likely matches for this contract, using deterministic heuristics: same customer, issue date inside the contract term, exact amount matches (order line / per-period / remaining value), and description-token overlap. Returns the top matches with human-readable reasons for each score. Parameters: - contract_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## counterparty-aliases ### GET /api/v1/counterparty-aliases List counterparty aliases List all counterparty aliases configured for the current business. Responses: 200, 422 ### POST /api/v1/counterparty-aliases Create or update a counterparty alias Create a counterparty alias, upserting when one already exists for Request body (CounterpartyAliasCreate): - registry_id (string, optional) - counterparty_name (string, optional) - display_name (string, required) Responses: 201, 422 ### DELETE /api/v1/counterparty-aliases/{alias_id} Delete a counterparty alias Delete a counterparty alias. Display falls back to the canonical Parameters: - alias_id (path, string, required) Responses: 200, 422 ### PATCH /api/v1/counterparty-aliases/{alias_id} Update a counterparty alias Change the display name on an existing counterparty alias. Parameters: - alias_id (path, string, required) Request body (CounterpartyAliasUpdate): - display_name (string, required) Responses: 200, 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 ### POST /api/v1/credit/card-settlements Ingest cleared card settlements Push settled card transactions. Settlements only, never authorisations. Idempotent on the settlement ID, so re-pushing a batch reports duplicates rather than double-counting. A negative amount is a refund. Request body (CardSettlementIngestRequest): - credit_line_id (string, optional) - external_line_id (string, optional) - settlements (array · CardSettlementIngestItem, required) → CardSettlementIngestItem Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/credit/cards Issue a card against a credit line Mints a card with spend controls enforced at the network — ceiling, interval, per-transaction cap, and a merchant-category allowlist. Card credentials never transit DayZero. Request body (CardIssueRequest): - credit_line_id (string, required) - display_name (string, optional) - card_type (string, optional) - spend_limit_cents (integer, optional) - spend_interval (string, optional) - transaction_limit_cents (integer, optional) - cardholder_provider_user_id (string, optional) - allowed_mcc_codes (array · string, optional) - blocked_mcc_codes (array · string, optional) - auto_lock_date (string · date, optional) - idempotency_key (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/credit/cards/sync Reconcile cards against the issuer The issuer emits no card-state webhook, so drift is only found by asking. A card suspended at the issuer that we still believe is active is the case this catches. Parameters: - credit_line_id (query, string, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/credit/cards/{card_id} Terminate a card Permanent. Settlements already cleared stay on the ledger and their cycles still bill: terminating stops future spend, it does not cancel debt. Parameters: - card_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/credit/cards/{card_id}/embed-token Mint a token to display card credentials The issuer renders the number inside its own embedded component. The PAN and CVV never pass through DayZero. Parameters: - card_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### PATCH /api/v1/credit/cards/{card_id}/limits Update card spend controls Change a card's ceiling, interval, or category rules. Parameters: - card_id (path, string, required) Request body (CardLimitUpdateRequest): - spend_limit_cents (integer, optional) - spend_interval (string, optional) - transaction_limit_cents (integer, optional) - allowed_mcc_codes (array · string, optional) - blocked_mcc_codes (array · string, optional) - auto_lock_date (string · date, optional) - display_name (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/credit/cards/{card_id}/suspension Lift a card suspension Unsuspend a card. Parameters: - card_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/credit/cards/{card_id}/suspension Suspend a card Declines further authorisations while keeping the card's number and history — the right lever for a delinquent line, since termination cannot be undone. Parameters: - card_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/credit/cycles/assess-late Flag and charge past-due cycles Flags past-due cycles and charges them under each program's configured late rule — rate basis, grace period, day count, compounding, and cap. A program with no late rule still gets the status flag but is not charged, since the rate basis would have to be invented. Idempotent per cycle per day, so re-running the sweep never double-charges. Pass `dry_run` to preview. Request body (LateAssessmentRequest): - credit_line_id (string, optional) - as_of (string · date, optional) - dry_run (boolean, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/credit/cycles/close Close cycles and create their draws Closes every cycle whose period has ended: fixes the principal, computes the disbursement fee and the full-term interest once, and sets the due date. Idempotent — a cycle already closed is skipped. Pass `dry_run` to see what would close without writing. Request body (CycleCloseRequest): - credit_line_id (string, optional) - as_of (string · date, optional) - issue_statements (boolean, optional) - dry_run (boolean, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/credit/events Poll the credit event feed Every credit event in order, from a cursor. Pass the returned `next_cursor` as `after` on the following call. This is the polling half of event delivery — the same events are pushed to any registered webhook endpoint, so a partner may use either or both, and replay through this feed after an outage. Parameters: - after (query, integer, optional): Return events after this sequence number. - event_types (query, string, optional): Comma-separated event types to include. - credit_line_id (query, string, optional) - limit (query, integer, optional) Responses: 200, 401, 403, 422 ### GET /api/v1/credit/lines List credit lines Every line on this business, with current balances. Parameters: - program_id (query, string, optional): Filter to one program. - status (query, string, optional): Filter by line status. - limit (query, integer, optional) - offset (query, integer, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/credit/lines Open a credit line Open a line on approval. Terms not supplied inherit the program's defaults. Request body (CreditLineCreateRequest): - program_id (string, optional) - external_line_id (string, optional) - external_merchant_id (string, optional) - credit_limit_cents (integer, required) - term_days (integer, optional) - rate_bps (integer, optional) - disbursement_fee_bps (integer, optional) - cycle_anchor_day (integer, optional) - start_date (string · date, optional) - linked_account_name (string, optional) - linked_account_mask (string, optional) - plaid_account_id (string, optional) - autopay_enabled (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id} Credit line dashboard Limit, current balance, available credit, utilisation in basis points, start date, term, and linked account. Available credit and utilisation are returned rather than derived so the dashboard card and the statement band cannot disagree. Parameters: - credit_line_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/credit/lines/{credit_line_id} Update a credit line Change the repayment account, AutoPay, or line status. Parameters: - credit_line_id (path, string, required) Request body (CreditLineUpdateRequest): - linked_account_name (string, optional) - linked_account_mask (string, optional) - plaid_account_id (string, optional) - autopay_enabled (boolean, optional) - status (CreditLineStatusEnum, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/cards List cards on a credit line Cards issued against this line. Parameters: - credit_line_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/cards/monitor Monitor card spend and controls Per-card cleared spend, remaining headroom, and any drift between our record and the issuer's. Parameters: - credit_line_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/ledger Credit line ledger register The append-only register every balance is derived from, in replay order. Parameters: - credit_line_id (path, string, required) - limit (query, integer, optional) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/ledger/verify Verify ledger integrity Recomputes the hash chain and re-derives the balance from the entries. An append-only register has no legitimate way to fail this, so a false result means a row was altered or removed. Parameters: - credit_line_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/payoff-quote Quote the payoff amount Principal plus accrued interest as at a date. Request this rather than reusing the displayed balance: principal and accrued interest settle separately, so the balance alone is not the payoff amount. Parameters: - credit_line_id (path, string, required) - as_of (query, string · date, optional): Quote date. Defaults to today. Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/statements List statements One row per closed cycle: period, issued date, due date, status, amount due, and a download link. The due date is fixed at close and must not be derived from the issued date. Parameters: - credit_line_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/statements/{cycle_id} Get a rendered statement The statement document for one cycle: roll-forward, the settlements behind it, and the amount due. The partner provides the download affordance and nothing else. Parameters: - credit_line_id (path, string, required) - cycle_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/credit/lines/{credit_line_id}/subledger Transactions tab: roll-forward, activity, cycle history The balance roll-forward for one cycle, the open cycle's activity beneath it, and one row per closed cycle. Filters run server-side: a paginated envelope silently breaks client-side filtering. Parameters: - credit_line_id (path, string, required) - cycle_id (query, string, optional): Render the band for this cycle. Defaults to the latest. - status (query, string, optional): Comma-separated cycle states, e.g. `paid,late`. - start (query, string · date, optional): Include cycles ending on or after. - end (query, string · date, optional): Include cycles starting on or before. Responses: 200, 401, 403, 404, 422 ### POST /api/v1/credit/payments Record a payment Records a debit the partner executed and returns corrected balances. Allocation is not selectable: principal before accrued interest, earliest due date first. A payment can clear the full principal while interest remains outstanding — that cycle is not Paid, and the returned status is authoritative. Request body (PaymentRecordRequest): - credit_line_id (string, optional) - external_line_id (string, optional) - external_payment_id (string, required) - amount_cents (integer, required) - paid_on (string · date, required) - method (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/credit/payments/{payment_id}/reversal Reverse a failed payment Unwinds a returned debit exactly as it was applied and re-accrues from the failure date. The merchant is not charged interest for the period the payment was pending. Parameters: - payment_id (path, string, required) Request body (PaymentReversalRequest): - reversed_on (string · date, required) - reason (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/credit/programs List lending programs Every program this business operates, with its policy status. Parameters: - limit (query, integer, optional) - offset (query, integer, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/credit/programs Create a lending program Stand up a partner lending program. The program carries the parameter envelope, the policy positions, statement branding, and the card-issuing configuration, so onboarding a second partner is a row rather than a build. Request body (CreditProgramCreateRequest): - slug (string, required) - display_name (string, required) - status (ProgramStatusEnum, optional) → ProgramStatusEnum - limit_min_cents (integer, optional) - limit_max_cents (integer, optional) - allowed_term_days (array · integer, optional) - default_rate_bps (integer, optional) - default_disbursement_fee_bps (integer, optional) - max_float_days (integer, optional) - statement_balance_mode (string, optional) - allocation_scope (string, optional) - cycle_boundary_basis (string, optional) - cycle_close_rule (string, optional) - cycle_anchor_day (integer, optional) - excess_credit_disposition (string, optional) - late_terms (object, optional) - early_payoff_terms (object, optional) - underwriting_criteria (array · object, optional) - legal_lending_entity (string, optional) - statement_branding (object, optional) - event_delivery_mode (EventDeliveryMode, optional) → EventDeliveryMode - issuing_enabled (boolean, optional) - ramp_spend_program_id (string, optional) - allowed_mcc_codes (array · string, optional) - blocked_mcc_codes (array · string, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/credit/programs/{program_id} Get a lending program One program, including which policy rules are still undecided. Parameters: - program_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/credit/programs/{program_id} Update a lending program Supplying `late_terms`, `early_payoff_terms`, or `underwriting_criteria` is what settles the corresponding open decision: the guarded code paths stop raising once a rule body exists. Parameters: - program_id (path, string, required) Request body (CreditProgramUpdateRequest): - display_name (string, optional) - status (ProgramStatusEnum, optional) - default_rate_bps (integer, optional) - default_disbursement_fee_bps (integer, optional) - statement_balance_mode (string, optional) - allocation_scope (string, optional) - cycle_close_rule (string, optional) - cycle_anchor_day (integer, optional) - excess_credit_disposition (string, optional) - late_terms (object, optional) - early_payoff_terms (object, optional) - underwriting_criteria (array · object, optional) - legal_lending_entity (string, optional) - statement_branding (object, optional) - event_delivery_mode (EventDeliveryMode, optional) - issuing_enabled (boolean, optional) - ramp_spend_program_id (string, optional) - allowed_mcc_codes (array · string, optional) - blocked_mcc_codes (array · string, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/credit/underwriting/assessments List underwriting assessments Assessments for this business. Parameters: - credit_line_id (query, string, optional) Responses: 200, 401, 403, 422 ### POST /api/v1/credit/underwriting/assessments Request an underwriting assessment Returns a pending assessment. Bank backfill, categorisation, and trailing-ratio history can take up to three days, so callers must not assume a synchronous answer — poll the get endpoint or subscribe to the completion event. We return evidence; the lending decision stays the partner's. Request body (UnderwritingAssessmentRequest): - credit_line_id (string, optional) - external_application_id (string, optional) - program_id (string, optional) - criteria_override (array · object, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/credit/underwriting/assessments/{assessment_id} Get an underwriting assessment Each criterion carries its value, the threshold it was measured against, a pass or fail, and the source the figure came from. `decision` is always null by design. Parameters: - assessment_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/credit/underwriting/assessments/{assessment_id}/completion Complete an underwriting assessment Scores measured figures against the program's criteria set and closes the assessment. Returns per-criterion value, threshold, pass or fail, and source, plus roll-up counts and a weighted score. A criterion we could not measure comes back `passed: null` rather than false — those call for opposite actions. No recommendation is returned at any point. Parameters: - assessment_id (path, string, required) Request body (UnderwritingCompletionRequest): - measured_values (object, required) - financials (object, optional) - evidence (object, optional) - segment (string, optional) Responses: 201, 400, 401, 403, 404, 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) - restocking_fee_cents (integer, optional) - 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}/unapply Un-apply credit memo from invoice Remove (un-link) a credit memo from an invoice, restoring balances. Parameters: - credit_memo_id (path, string, required) Request body (CreditMemoUnapply): - invoice_id (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) - status (query, string, optional): Filter by status: active, inactive, or blocked - 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 six tiles: categorization, AP drafts, anomalies, close score, burn/runway, and AI spend. 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 (Void duplicates, 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 ## dashboards ### GET /api/v1/dashboards List dashboards Cursor-paginated list of dashboards visible to the caller: their own, ones assigned to them, and business-visible ones. 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/dashboards Create a dashboard Create an empty dashboard (widgets are added separately). Request body (CreateDashboardRequest): - title (string, required) - description (string, optional) - visibility (string, optional) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/dashboards/assignable-users List assignable users Users of this business a dashboard can be assigned to. Responses: 200, 401, 403, 422 ### POST /api/v1/dashboards/models/build Build an approved model plan Evaluate a draft model against live ledger data and persist it as a dashboard whose widgets are its sheets. The draft must have come from `/models/import` or the `plan_model` chat tool, and expires an hour after it was created. Request body (BuildModelRequest): - draft_id (string, required) - visibility (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/dashboards/models/drafts/{draft_id} Read a proposed model in full The complete plan behind a draft: **every** row of every sheet, the formula or resolved ledgers behind each one, and a sample column of real values. Parameters: - draft_id (path, string, required) - full (query, boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### PATCH /api/v1/dashboards/models/drafts/{draft_id} Edit a proposed model before building it Change the period axis or an assumption value on a draft and get the re-planned model back — re-resolved against the chart of accounts and re-evaluated, so the effect of the edit is visible before anything is built. Parameters: - draft_id (path, string, required) Request body (ReviseDraftRequest): - title (string, optional) - assumptions (object, optional) - grain (string, optional) - start (string · date, optional) - end (string · date, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/models/import Import a reference workbook as a model plan Parse an uploaded .xlsx into a draft model and return it for review. **Nothing is persisted** — the importer cannot know which ledgers feed the workbook's data rows, and guessing would produce a model that renders a clean grid of wrong numbers. The response carries a `confidence` score, the sheets skipped as raw data exports, and the formulas that could not be translated, so the caller can resolve them before calling `/models/build`. Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/dashboards/{dashboard_id} Delete a dashboard Soft-delete a dashboard. Parameters: - dashboard_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/dashboards/{dashboard_id} Get a dashboard Fetch the full DashboardSpec for rendering. Parameters: - dashboard_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/dashboards/{dashboard_id} Update a dashboard Patch title, description, or visibility. Parameters: - dashboard_id (path, string, required) Request body (UpdateDashboardRequest): - title (string, optional) - description (string, optional) - visibility (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/dashboards/{dashboard_id}/assignments Set dashboard assignments Replace the set of users assigned to this dashboard, optionally changing its visibility at the same time. Parameters: - dashboard_id (path, string, required) Request body (AssignUsersRequest): - user_ids (array · string, optional) - visibility (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/export Export a dashboard to Excel, CSV, or PDF Queue an asynchronous export of this dashboard and return a task id. Poll the standard report task-status endpoint (`GET /api/v1/task/{task_id}`) for progress and the download link — this reuses the report pipeline rather than adding a second one. Parameters: - dashboard_id (path, string, required) Request body (ExportDashboardRequest): - output_format (string, optional) - widget_ids (array · string, optional) - include_empty (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/model/refresh Re-derive a model from current ledger data Recompute every sheet. Unlike a snapshot dashboard's refresh, this re-runs the **model**, so a journal entry booked since the last render moves every dependent line — not only the rows whose source tool happened to be re-executed. Parameters: - dashboard_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/dashboards/{dashboard_id}/model/workbook Download a model as a live-formula Excel workbook Stream the model as an .xlsx whose computed cells hold **real Excel formulas** — subtotals, roll-forwards and assumption references stay live, so an analyst can change a rate in the Assumptions sheet and watch every dependent line move. Parameters: - dashboard_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/dashboards/{dashboard_id}/schedule Delete the refresh schedule Remove the schedule. The dashboard and its widgets are untouched. Parameters: - dashboard_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/dashboards/{dashboard_id}/schedule Get the refresh schedule Return this dashboard's refresh schedule, including its last/next run times and the error from the most recent failed run. Responds with `schedule: null` when no schedule is configured — absence of a schedule is a normal state, not a 404. Parameters: - dashboard_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/dashboards/{dashboard_id}/schedule Update the refresh schedule Patch the cadence or enabled state. Any cadence change — including re-enabling a disabled schedule — re-derives `next_run_at` from now, so a schedule that was paused for a long time does not fire immediately on resume. Parameters: - dashboard_id (path, string, required) Request body (DashboardRefreshScheduleUpdate): - frequency (string, optional) - day_of_week (integer, optional) - day_of_month (integer, optional) - hour_utc (integer, optional) - timezone (string, optional) - is_enabled (boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/schedule Create a refresh schedule Schedule a recurring background refresh of this dashboard's widget sources. A dashboard may have at most one schedule — returns 400 if one already exists (PATCH it instead). Parameters: - dashboard_id (path, string, required) Request body (DashboardRefreshScheduleRequest): - frequency (string, optional) - day_of_week (integer, optional) - day_of_month (integer, optional) - hour_utc (integer, optional) - timezone (string, optional) - is_enabled (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/share Create a public share link Mint a token-protected public URL that renders this dashboard read-only for anyone who holds the link — no DayZero account needed. The raw token is returned in this response **exactly once**; only its SHA-256 digest is stored, so it cannot be recovered later (mint a new link instead). Parameters: - dashboard_id (path, string, required) Request body (CreateDashboardShareRequest): - expires_in_hours (integer, optional) - slug (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/dashboards/{dashboard_id}/shares List share links Every share link for this dashboard — active, expired and revoked — newest first, with view counts. Raw tokens are never re-emitted, so rows identify a link by id and slug only. Parameters: - dashboard_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### DELETE /api/v1/dashboards/{dashboard_id}/shares/{share_id} Revoke a share link Revoke one link. The public viewer 404s immediately — the cached payload is dropped as part of the revoke. Idempotent for a link that was already revoked. Parameters: - dashboard_id (path, string, required) - share_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/dashboards/{dashboard_id}/versions List dashboard versions Newest-first version history for this dashboard. Rows are skinny — the stored spec is omitted; fetch a single version to preview it. Parameters: - dashboard_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/versions Save a dashboard version Capture the dashboard's current title, description, visibility and widgets as a new numbered version. This is an explicit user action — editing a dashboard or refreshing its widgets does NOT create a version. At most 25 versions are retained per dashboard; saving past that prunes the oldest. Parameters: - dashboard_id (path, string, required) Request body (SaveVersionRequest): - change_summary (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### GET /api/v1/dashboards/{dashboard_id}/versions/{version_id} Get a dashboard version Fetch one stored version including its full spec. Parameters: - dashboard_id (path, string, required) - version_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/versions/{version_id}/restore Restore a dashboard version Roll the dashboard back to a stored version. The version's spec is re-validated against the current schema first (a 400 means the version predates an incompatible schema change), and the state being overwritten is saved as a new version first — so the restore itself is undoable via `undo_version_number`. Parameters: - dashboard_id (path, string, required) - version_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/widgets Add a widget Add a widget from a chat card ('Add to dashboard') or manually. The snapshot payload is stored verbatim; a source tool, when provided, must be a READ tool. Parameters: - dashboard_id (path, string, required) Request body (AddWidgetRequest): - title (string, required) - widget_type (string, required) - layout (WidgetLayout, optional) - source (WidgetSource, optional) - snapshot (WidgetSnapshot, optional) - body (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/widgets/reorder Reorder widgets Set a new widget order via a list of widget_ids. Parameters: - dashboard_id (path, string, required) Request body (ReorderWidgetsRequest): - widget_ids (array · string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/dashboards/{dashboard_id}/widgets/{widget_id} Remove a widget Remove one widget from the dashboard. Parameters: - dashboard_id (path, string, required) - widget_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/dashboards/{dashboard_id}/widgets/{widget_id} Update a widget Patch a widget's title, layout, or (text widgets) body. Parameters: - dashboard_id (path, string, required) - widget_id (path, string, required) Request body (UpdateWidgetRequest): - title (string, optional) - layout (WidgetLayout, optional) - body (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/dashboards/{dashboard_id}/widgets/{widget_id}/alert Create an alert on a metric widget Create a threshold alert rule bound to a metric widget. The rule is evaluated against the widget's **stored snapshot** — it never re-executes the widget's source tool, so the alert is exactly as fresh as the dashboard itself. Returns 400 if the widget is not a metric widget or if `value_path` does not resolve to a number in the current snapshot. Parameters: - dashboard_id (path, string, required) - widget_id (path, string, required) Request body (CreateWidgetAlertRequest): - name (string, optional) - threshold_value (integer, required) - threshold_direction (string, required) - value_path (string, optional) - channels (array · string, optional) - check_frequency (string, optional) - cooldown_hours (integer, optional) - description (string, optional) - slack_channel (string, optional) Responses: 201, 400, 401, 403, 404, 422 ## dashboards-public ### GET /api/v1/public/dashboards/{token} Public dashboard viewer (unauthenticated) Resolve a share token to a read-only, redacted dashboard. Returns 404 for unknown, expired, revoked, or deleted targets — the four are indistinguishable on purpose. Rate-limited per IP and cached for 60 seconds; the payload reflects each widget's stored snapshot, so it is exactly as fresh as the dashboard itself. Parameters: - token (path, string, required) Responses: 200, 404, 422, 429 ## deals ### GET /api/v1/o2c/deals List deals List pipeline deals for the business with stage / expansion / text-search filters and cursor pagination. Always returns the total matching count alongside the page. Parameters: - stage (query, string, optional): Filter by exact deal stage. - is_expansion (query, boolean, optional): Filter by new-customer (false) vs. expansion (true). - search (query, string, optional): Case-insensitive search across name / prospect / contact. - owner (query, string, optional): Filter by sales rep / deal owner (case-insensitive exact match; see GET /o2c/deals/owners). Pass `__unassigned__` for deals with no owner yet. - cursor (query, string, optional): Pagination cursor - direction (query, string, optional): Which way to walk from `cursor`. Send `prev` with the page's `prev_cursor` to page backwards. - limit (query, integer, optional): Page size - sort_by (query, string, optional): Column to sort by. - descending (query, boolean, optional): Sort direction. Responses: 200, 401, 403, 422 ### POST /api/v1/o2c/deals Create a deal Create a pipeline deal. Provide either `prospect_name` (before a real customer record exists) or `customer_id` (an expansion deal on an existing customer). `stage` defaults to `prospecting`. Request body (DealCreate): - prospect_name (string, optional) - customer_id (string, optional) - name (string, optional) - contact_name (string, optional) - contact_email (string, optional) - source (string, optional) - stage (string, optional) - is_expansion (boolean, optional) - expected_value_cents (integer, optional) - expected_hours (number | string, optional) - expected_term_months (integer, optional) - expected_start_date (string · date, optional) - expected_close_date (string · date, optional) - notes (string, optional) - owner_name (string, optional) - deal_type (string, optional) - revenue_type (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/o2c/deals/bulk Bulk-create deals Create up to 250 pipeline deals in one request — the import path behind the Pipeline page's Import button, for moving an existing pipeline spreadsheet in. Request body (DealBulkCreateRequest): - deals (array · DealBulkRow, required) → DealBulkRow Responses: 201, 400, 401, 403, 422 ### GET /api/v1/o2c/deals/owners List sales reps with deals Every distinct sales rep / deal owner on the business's deals, with their deal counts, plus how many deals have no owner yet. Feeds the pipeline's owner filter chips, which need the full roster rather than whichever owners happen to be on the current page. Responses: 200, 401, 403, 422 ### GET /api/v1/o2c/deals/pipeline Pipeline view: forecast metrics + deals Stage-weighted pipeline forecast metrics (computed over all open deals) plus a filtered, paginated deals page with `effective_stage` computed server-side (deal.stage, or the linked order's live status once converted). Parameters: - stage (query, string, optional): Filter the deals page by stage. - is_expansion (query, boolean, optional): Filter the deals page by new-customer vs. expansion. - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size - sort_by (query, string, optional): Column to sort by. - descending (query, boolean, optional): Sort direction. Responses: 200, 401, 403, 422 ### GET /api/v1/o2c/deals/{deal_id} Get deal Get a single deal with its customer/project relationships. Parameters: - deal_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/o2c/deals/{deal_id} Edit deal Edit a deal's stage, notes, owner / deal type / revenue type, and expected-commercials estimates. Blocked once the deal has converted to an order (`order_id` set) -- manage it from Orders instead. Stage changes are validated against the model's allowed transitions. Parameters: - deal_id (path, string, required) Request body (DealUpdate): - stage (string, optional) - notes (string, optional) - owner_name (string, optional) - deal_type (string, optional) - revenue_type (string, optional) - expected_value_cents (integer, optional) - expected_hours (number | string, optional) - expected_term_months (integer, optional) - expected_start_date (string · date, optional) - expected_close_date (string · date, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/o2c/deals/{deal_id}/create-order Convert a deal into a customer/project/draft order One-way handoff into Order-to-Cash (`CreateOrderFromDealService`): creates a customer if the deal has none yet, a project beneath it (unique per customer, case-insensitive), and a draft order pre-filled from the deal's estimates -- then freezes the deal by stamping `customer_id` / `project_id` / `order_id`. Atomic: any failure rolls back every write. Fails if the deal has already converted. Parameters: - deal_id (path, string, required) Request body (DealCreateOrderRequest): - project_name (string, required) - order_description (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/o2c/deals/{deal_id}/mark-lost Mark a deal lost Close an open deal as lost with a required reason. Parameters: - deal_id (path, string, required) Request body (DealMarkLostRequest): - reason (string, required) - note (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/o2c/deals/{deal_id}/reopen Reopen a lost deal Restore a closed_lost deal to whatever stage it was in right before it was marked lost. Pre-handoff only -- a deal that has already converted to an order (`order_id` set) can never be reopened here. Parameters: - deal_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## deferred-revenue ### GET /api/v1/deferred-revenue List deferred invoices Deferred invoices with their schedules and remaining balances. Parameters: - status (query, DeferralStatusEnum, optional): Filter by deferral status - customer_id (query, string, optional): Filter by customer - limit (query, integer, optional): Page size - offset (query, integer, optional): Offset Responses: 200, 401, 403, 422 ### GET /api/v1/deferred-revenue/invoices/{invoice_id} Get an invoice's deferral An invoice's deferral, if any, and how much of it can be deferred. Parameters: - invoice_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/deferred-revenue/invoices/{invoice_id} Defer an invoice Route an invoice's revenue to Deferred Revenue and schedule it. Parameters: - invoice_id (path, string, required) Request body (DeferInvoiceRequest): - deferred_amount_cents (integer, optional) - unit (DeferralUnitEnum, optional) → DeferralUnitEnum - method (DeferralMethodEnum, optional) → DeferralMethodEnum - period_count (integer, required) - total_units (number | string, optional) - start_date (string · date, optional) - revenue_ledger_name (string, optional) - periods (array · DeferralPeriodInput, optional) - notes (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### GET /api/v1/deferred-revenue/matrix Deferred revenue matrix Deferred invoices (rows) against recognition months (columns). Parameters: - status (query, DeferralStatusEnum, optional): Filter by deferral status Responses: 200, 401, 403, 422 ### POST /api/v1/deferred-revenue/recognize Run recognition Post every deferred revenue entry that has come due. Request body (RunRecognitionRequest): - as_of (string · date, optional) Responses: 201, 401, 403, 422 ### GET /api/v1/deferred-revenue/recognized Recognized revenue Recognized revenue by period, method and source, with upcoming totals for drill-down. Parameters: - start_date (query, string · date, optional): Only periods ending on/after this date - end_date (query, string · date, optional): Only periods ending on/before this date - limit (query, integer, optional): Page size, in groups - offset (query, integer, optional): Offset, in groups Responses: 200, 401, 403, 422 ### GET /api/v1/deferred-revenue/{deferral_id} Get a deferral One deferred revenue schedule with all of its periods. Parameters: - deferral_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/deferred-revenue/{deferral_id} Update deferral terms Re-cut the not-yet-posted part of a recognition schedule. Parameters: - deferral_id (path, string, required) Request body (UpdateDeferralTermsRequest): - unit (DeferralUnitEnum, optional) - method (DeferralMethodEnum, optional) - period_count (integer, optional) - total_units (number | string, optional) - start_date (string · date, optional) - revenue_ledger_name (string, optional) - notes (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/deferred-revenue/{deferral_id}/cancel Undo a deferral Release the remaining deferred balance back to revenue. Parameters: - deferral_id (path, string, required) Request body (CancelDeferralRequest): - as_of (string · date, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/deferred-revenue/{deferral_id}/periods/{period_id} Fill in a period's numerator Enter the units burned down in a period so it can post. Parameters: - deferral_id (path, string, required) - period_id (path, string, required) Request body (SetPeriodUnitsRequest): - units (number | string, required) - recognition_date (string · date, optional) - post_now (boolean, optional) Responses: 201, 400, 401, 403, 404, 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 ## documents-public ### GET /api/v1/public/documents/{token} Public shared-document viewer (unauthenticated) Resolve a share token to the stored HTML page. Returns 404 for unknown, expired, or revoked tokens — the three are indistinguishable on purpose. Rate-limited per IP. Parameters: - token (path, string, required) Responses: 200, 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-matrix Depreciation schedule matrix Depreciation pivoted to months across the top and assets down the side. Parameters: - start_date (query, string · date, optional): Earliest period to include (YYYY-MM-DD) - end_date (query, string · date, optional): Latest period to include (YYYY-MM-DD) - include_posted (query, boolean, optional): Include already-posted periods - include_scheduled (query, boolean, optional): Include queued (not yet posted) periods 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/from-transactions Create asset from transactions Capitalize ledger transactions into a fixed asset. Request body (CreateAssetFromTransactionsRequest): - ledger_id (string, required) - transaction_ids (array · string, required) - name (string, required) - useful_life_months (integer, optional) - depreciation_method (DepreciationMethod, optional) → DepreciationMethod - in_service_date (string · date, optional) - salvage_value (integer, optional) - auto_place_in_service (boolean, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/fixed-assets/generate-schedules Queue depreciation schedules Queue draft depreciation entries for active assets that have none. Responses: 201, 422 ### POST /api/v1/fixed-assets/post-scheduled-depreciation Post due scheduled depreciation Post every queued depreciation entry whose date has arrived. Request body (PostScheduledDepreciationRequest): - as_of (string · date, optional) Responses: 201, 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 ### GET /api/v1/fixed-assets/scheduled-journal-entries List scheduled journal entries Depreciation journal entries queued to post on their future dates. Parameters: - through_date (query, string · date, optional): Only entries queued on or before this date - limit (query, integer, optional): Maximum entries to return Responses: 200, 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. Parameters: - asset_id (path, string, required) - force (query, boolean, optional): Also delete posted depreciation entries and every journal entry posted for this asset (capitalization, depreciation, disposal). Use when the asset was set up by mistake. 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 ## gmail ### GET /api/v1/gmail/connect-url Get Gmail OAuth connect URL Generate a URL to redirect the user to Google for authorization. Personal connection: links the signed-in user's own mailbox. Responses: 200, 400, 401, 422 ### DELETE /api/v1/gmail/disconnect Disconnect Gmail Revoke the Google grant (best effort) and delete the stored connection. DayZero never stored any email content. Responses: 200, 400, 401, 422 ### GET /api/v1/gmail/oauth/callback Gmail OAuth callback Handle the Google OAuth redirect. Called by Google, not the frontend. Parameters: - code (query, string, optional): Authorization code from Google - state (query, string, optional): OAuth state nonce - error (query, string, optional): Set by Google when the user denies consent Responses: 200, 422 ### GET /api/v1/gmail/status Gmail connection status Returns the signed-in user's Gmail connection state and health. Responses: 200, 401, 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 ## intercompany-cleanup ### GET /api/v1/advisory-firms/{firm_id}/access-requests List aggregated parent-side access requests Case B candidates grouped by the expected counterparty account: '12 transactions, $148,200 — second leg expected in Parent Brex — request access'. Parameters: - firm_id (path, string, required) - session_id (query, string, optional): Scope to one session Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/account-map Get the firm Account Master Map Every map entry for the firm, auto-seeded from all Plaid/Teal accounts across the firm's businesses that have no entry yet, with ingestion coverage windows computed from transaction data. Only ``entity_role`` requires human input. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/account-map Add an external account stub to the map Registers an account the firm does not ingest (e.g. the parent company's card) so orphan classification can point at it. Parameters: - firm_id (path, string, required) Request body (AccountMapEntryCreate): - display_name (string, required) - entity_role (string, optional) - business_id (string, optional) - last4 (string, optional) - account_type (string, optional) - notes (string, optional) - known_description_strings (array · string, optional) Responses: 201, 401, 403, 404, 422 ### DELETE /api/v1/advisory-firms/{firm_id}/account-map/{entry_id} Delete an external account map entry Only external stubs can be deleted; linked entries reseed from the connected account. Parameters: - firm_id (path, string, required) - entry_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PATCH /api/v1/advisory-firms/{firm_id}/account-map/{entry_id} Update an account map entry Parameters: - firm_id (path, string, required) - entry_id (path, string, required) Request body (AccountMapEntryUpdate): - entity_role (string, optional) - display_name (string, optional) - last4 (string, optional) - account_type (string, optional) - notes (string, optional) - known_description_strings (array · string, optional) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/candidates/bulk-resolve Confirm or reject match candidates Confirming applies the booking side effects: same-business pairs retarget to the transfers ledger, cross-entity pairs book due-to/due-from plus an elimination entry, parent legs book due-from-parent on our side only. Every confirmation also feeds rule learning and the account map's known description strings. Parameters: - firm_id (path, string, required) Request body (BulkResolveRequest): - candidate_ids (array · string, required) - action (string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/candidates/{candidate_id}/context Drill-in context for one match candidate Full journal lines per leg plus the history of confirmed pairs between the same two accounts — everything needed to judge the candidate without leaving the review queue. Parameters: - firm_id (path, string, required) - candidate_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/cleanup-badge Open intercompany review-queue count Lightweight count of proposed candidates awaiting review — drives the Cleanup sidebar badge. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 422 ### GET /api/v1/advisory-firms/{firm_id}/cleanup-sessions List cleanup sessions for a firm Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/cleanup-sessions Start a historical cleanup session Creates a cleanup session over the given entities and date window, then kicks off the matching workflow. Poll the session for status and counters. Parameters: - firm_id (path, string, required) Request body (CleanupSessionCreate): - date_start (string · date, required) - date_end (string · date, required) - business_ids (array · string, optional) - date_tolerance_days (integer, optional) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/cleanup-sessions/{session_id} Get a cleanup session with progress counters Parameters: - firm_id (path, string, required) - session_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/cleanup-sessions/{session_id}/candidates List match candidates for a session Impact-sorted (amount desc), cursor-paginated. Filter by ``case`` (internal_transfer | parent_intercompany | data_gap | hidden_detail) and ``status`` (proposed | confirmed | rejected | applied). Parameters: - firm_id (path, string, required) - session_id (path, string, required) - case (query, string, optional): Filter by case type - status (query, string, optional): Filter by status - cursor (query, string, optional) - limit (query, integer, optional) - direction (query, string, optional) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/cleanup-sessions/{session_id}/run Re-run matching for a session (iterative clearing) Confirmed pairs drop out of the pool, so each re-run surfaces the next layer of matches. Parameters: - firm_id (path, string, required) - session_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/cleanup-sessions/{session_id}/workpaper Download the cleanup session workpaper (PDF) Audit-ready summary of one cleanup session: scope, account map snapshot, every resolved candidate, and what remains open. Parameters: - firm_id (path, string, required) - session_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/intercompany-balances Standing due-to / due-from balances per entity Derived from posted intercompany ledger entries; powers the firm dashboard widget. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/resolutions List resolution batches (history) Every bulk-resolve call is a batch: who resolved what, when, and how much was booked. Batches are the unit of undo. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/resolutions/{batch_id}/undo Undo a resolution batch Unwinds every booking from the batch: retargeted journal lines return to their prior ledgers, categorization methods are restored, elimination entries are removed, and the candidates reopen as proposed. Learning writes are not reversed. Parameters: - firm_id (path, string, required) - batch_id (path, string, required) Responses: 201, 401, 403, 404, 422 ### GET /api/v1/advisory-firms/{firm_id}/settlement-proposals Pairwise net intercompany positions ready to settle Derived from open elimination entries: who owes whom, net, per entity pair. Largest positions first. Parameters: - firm_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/advisory-firms/{firm_id}/settlements Record an intercompany settlement (netting entry) Books a netting journal entry in each entity (debit due-to / credit due-from) after the repayment transfer has been matched, and marks the pair's open eliminations as posted. Refuses to net more than both gross balances support. Parameters: - firm_id (path, string, required) Request body (RecordSettlementRequest): - payer_business_id (string, required) - payee_business_id (string, required) - amount_cents (integer, required) - settlement_date (string · date, required) - memo (string, optional) Responses: 201, 401, 403, 404, 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:landed_cost_allocations ### GET /api/v1/inventory/landed-cost-allocations/orders List allocatable purchase orders Return received/completed POs that can absorb landed cost, with per-order unit and variant counts so the UI can suggest a split. Parameters: - limit (query, integer, optional): Max POs to return. Responses: 200, 401, 403, 422 ### GET /api/v1/inventory/landed-cost-allocations/{bill_id} List a bill's landed cost allocations Return every landed-cost allocation recorded for the bill plus a reconciliation summary (allocated vs. unallocated cents). Parameters: - bill_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/inventory/landed-cost-allocations/{bill_id} Replace a bill's landed cost allocations Set the full list of landed-cost allocations for a bill. Existing allocations are reversed first, then the requested targets are applied to the cost snapshots — idempotent. The allocated total may not exceed the bill amount. Parameters: - bill_id (path, string, required) Request body (LandedCostAllocationCreateRequest): - targets (array · LandedCostAllocationTarget, optional) → LandedCostAllocationTarget Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/inventory/landed-cost-allocations/{bill_id}/{allocation_id} Remove a landed cost allocation Delete one allocation and reverse the exact per-unit cost it added back out of the affected cost snapshots. Parameters: - bill_id (path, string, required) - allocation_id (path, string, required) Responses: 200, 401, 403, 404, 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 ### GET /api/v1/inventory/locations/summaries Get inventory summaries for all locations Get inventory summaries for all locations of the business in a single request. Parameters: - include_archived (query, boolean, optional): Include archived locations Responses: 200, 401, 403, 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 Archive a variant (default), or permanently delete it with `permanent=true`. Permanent deletion is rejected when the variant has linked inventory, order, shipment, transfer, or count records. Parameters: - id (path, string · uuid, required) - permanent (query, boolean, optional): Permanently delete instead of archiving. Only allowed when the variant has no linked records. - product_id (query, string, optional) Responses: 200, 400, 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) - product_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, array · string, optional): Filter by specific invoice UUIDs (repeatable). Batch-fetch a known set of invoices in one request instead of one GET /invoices/{id} per id. - 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, line item description, or customer name (case-insensitive partial match) - number (query, string, optional): Exact invoice number match (case-insensitive). Use this instead of `search` when resolving one known invoice number. - due_date_from (query, string · date, optional): Only invoices due on or after this date (YYYY-MM-DD) - due_date_to (query, string · date, optional): Only invoices due on or before this date (YYYY-MM-DD) - 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) - invoice_number (string, optional) - description (string, optional) - fulfillment_location_id (string, optional) - project_id (string, optional) - recurring (InvoiceRecurringConfig, optional) - historical (boolean, 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) - allow_duplicates (boolean, optional) - historical (boolean, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/invoices/bulk-payments Bulk mark invoices paid Mark a selection of invoices paid in one call, recording the payment date you supply and optionally matching one or more bank transactions to each invoice. Request body (BulkMarkInvoicesPaidRequest): - invoices (array · BulkInvoicePaymentRow, required) → BulkInvoicePaymentRow - paid_on (string · date-time, 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 balance and total overdue balance. Responses: 200, 401, 403, 422 ### GET /api/v1/invoices/scheduled Upcoming scheduled invoices Forward-looking list of invoices that will auto-draft on their scheduled day — future occurrences of active recurring invoice templates and upcoming contract billing periods — within the horizon. Parameters: - days (query, integer, optional): Horizon in days to project scheduled invoices forward (default 90, max 730). Only bounds templates/contracts without an explicit end date — a recurring template with an end date always projects its full remaining run through that date, regardless of this value. 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, void, or uncollectible invoice. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 400, 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) - invoice_date (string, optional) - invoice_number (string, optional) - description (string, optional) - fulfillment_location_id (string, optional) - project_id (string, optional) - contract_id (string, optional) - revenue_treatment (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 ### GET /api/v1/invoices/{invoice_id}/pdf/internal Download DayZero-generated invoice PDF Stream a PDF rendered by DayZero (not the Stripe-hosted one), including the business's custom footer text and banking details block. Works for any invoice, including drafts and invoices without Stripe integration. Parameters: - invoice_id (path, string · uuid, required) Responses: 200, 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}/unpaid Mark Invoice Unpaid Revert a manually-marked-paid invoice back to open/partially paid. 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 entry number (e.g. JE-0015, 15), description, source, line memo, ledger/account name, or line amount (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 · JournalLineEntryCreate, 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: 202, 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) - cash_flow_section (CashFlowSectionEnum, optional) - cfs_class (CashFlowClassEnum, optional) - depreciation_tag (DepreciationTagEnum, 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 ### GET /api/v1/ledgers/deletion-status/{workflow_id} Get ledger deletion status Poll for ledger cascade deletion workflow status. Parameters: - workflow_id (path, string, required) Responses: 200, 401, 403, 404, 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) - confirmation (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) - 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) - cash_flow_section (CashFlowSectionEnum, optional) - cfs_class (CashFlowClassEnum, optional) - depreciation_tag (DepreciationTagEnum, optional) - editable (boolean, optional) - parent_id (string, optional) - financial_account_type (string, optional) Responses: 200, 400, 401, 403, 404, 422 ### DELETE /api/v1/ledgers/{ledger_id}/cascade Cascade delete ledger Start a Temporal workflow that deletes a ledger and all dependent records. Poll GET /ledgers/deletion-status/{workflow_id} for progress. Parameters: - ledger_id (path, string, required) Responses: 200, 202, 400, 401, 403, 404, 422 ### GET /api/v1/ledgers/{ledger_id}/cascade-preview Preview ledger cascade delete Preview dependent records that would be deleted when permanently removing a ledger. Parameters: - ledger_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ## loans ### POST /api/v1/loans/accrue-interest Run interest accrual across all active loans Accrue interest on every active loan through a date. Request body (LoanRunAccrualsRequest): - through_date (string · date, required) Responses: 201, 400, 401, 403, 422 ### GET /api/v1/loans/subledger Loan subledger summary grouped by counterparty Business-wide loan subledger grouped by counterparty. Parameters: - direction (query, string, optional): Filter by direction: `lend` or `borrow`. Responses: 200, 401, 403, 422 ### POST /api/v1/loans/{instrument_id}/accrue-interest Accrue interest on one loan Accrue interest on one loan through a date. Parameters: - instrument_id (path, string, required) Request body (LoanAccrueInterestRequest): - through_date (string · date, required) - memo (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### PATCH /api/v1/loans/{instrument_id}/counterparty Re-tag a loan's counterparty type Re-tag the counterparty classification of a loan. Parameters: - instrument_id (path, string, required) Request body (LoanCounterpartyUpdateRequest): - counterparty_type (LoanCounterpartyTypeEnum, required) → LoanCounterpartyTypeEnum Responses: 200, 401, 403, 404, 422 ### POST /api/v1/loans/{instrument_id}/disbursements Record an additional loan disbursement Record an additional draw/advance on an existing loan. Parameters: - instrument_id (path, string, required) Request body (LoanDisbursementRequest): - amount_cents (integer, required) - cash_ledger_id (string, required) - disbursement_date (string · date, optional) - memo (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### GET /api/v1/loans/{instrument_id}/subledger Get a loan's subledger register A single loan's full subledger register, oldest entry first. Parameters: - instrument_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### POST /api/v1/loans/{instrument_id}/write-off Write off a loan Write off all (default) or part of a loan's balance. Parameters: - instrument_id (path, string, required) Request body (LoanWriteOffRequest): - write_off_date (string · date, optional) - principal_cents (integer, optional) - memo (string, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ## managerial-key-mappings ### GET /api/v1/businesses/{business_id}/managerial-key-mappings Get the managerial key mapping table One row per ledger in the chart of accounts (all account types) with a ``keys`` object holding the mapped category under every active key type, ``null`` where unmapped. Parameters: - business_id (path, string, required) - key_type (query, string, optional): Return only this key type's column. - only_mapped (query, boolean, optional): Drop ledgers that have no value under any returned key. Responses: 200, 400, 401, 403, 422 ### PUT /api/v1/businesses/{business_id}/managerial-key-mappings Bulk upsert managerial key mappings Apply ``[{ledger_id, key_type, value}]`` cells. A ``null`` value clears the cell; cells not referenced are left untouched. Responds with the refreshed table. Parameters: - business_id (path, string, required) Request body (ManagerialKeyMappingUpsertRequest): - items (array · ManagerialKeyMappingItem, optional) → ManagerialKeyMappingItem Responses: 200, 400, 401, 403, 422 ### GET /api/v1/businesses/{business_id}/managerial-key-mappings/export.csv Export the managerial key mapping table as CSV Flat CSV with one row per ledger and one column per active key type. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 422 ### GET /api/v1/businesses/{business_id}/managerial-key-mappings/key-types List managerial key types Return every key type (column) of the Managerial Key Mapping table. The two built-in defaults are always present and flagged ``is_default: true`` even before they have been customised. Parameters: - business_id (path, string, required) Responses: 200, 401, 403, 422 ### POST /api/v1/businesses/{business_id}/managerial-key-mappings/key-types Create a managerial key type Add a new key type (a column of the mapping table). The key is a lowercase slug and must be unique within the business. Parameters: - business_id (path, string, required) Request body (ManagerialKeyTypeCreateRequest): - key (string, required) - label (string, required) - description (string, optional) - sort_order (integer, optional) Responses: 201, 400, 401, 403, 409, 422 ### DELETE /api/v1/businesses/{business_id}/managerial-key-mappings/key-types/{key} Delete a managerial key type Remove a key type. Refused while the key still has ledger mappings unless ``force=true``, which deletes those mappings too. Built-in defaults are deactivated rather than removed. Parameters: - business_id (path, string, required) - key (path, string, required) - force (query, boolean, optional): Also delete this key type's ledger mappings. Responses: 200, 400, 401, 403, 404, 422 ### PUT /api/v1/businesses/{business_id}/managerial-key-mappings/key-types/{key} Update a managerial key type Relabel, reorder, or (de)activate a key type. Updating a built-in default materialises its definition row. Parameters: - business_id (path, string, required) - key (path, string, required) Request body (ManagerialKeyTypeUpdateRequest): - label (string, optional) - description (string, optional) - sort_order (integer, optional) - is_active (boolean, 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 ## o2c-webhooks ### GET /api/v1/webhooks/o2c List outbound webhook endpoints List the business's registered webhook endpoints (secret masked). Parameters: - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/webhooks/o2c Register an outbound webhook endpoint Register a URL to receive O2C events. The response includes the plaintext `secret` exactly once — store it to verify the `X-DayZero-Signature` (HMAC-SHA256) header on delivered events. Request body (WebhookEndpointCreate): - url (string, required) - event_types (array · string, required) - secret (string, optional) - is_active (boolean, optional) Responses: 201, 400, 401, 403, 422 ### DELETE /api/v1/webhooks/o2c/{endpoint_id} Delete a webhook endpoint Delete a webhook endpoint (and its delivery history). Parameters: - endpoint_id (path, string, required) Responses: 204, 401, 403, 404, 422 ### GET /api/v1/webhooks/o2c/{endpoint_id} Get a webhook endpoint Get a single webhook endpoint by ID. Parameters: - endpoint_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### GET /api/v1/webhooks/o2c/{endpoint_id}/deliveries List an endpoint's delivery history Delivery records for the endpoint, including per-delivery `status` (pending/succeeded/retrying/failed), `attempts`, the last `response_code`, and `next_retry_at` when a retry is queued. Parameters: - endpoint_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/webhooks/o2c/{endpoint_id}/test-fire Fire a test event Emit a synthetic event of `event_type` to the business's active subscribed endpoints so a receiver can be validated end-to-end. Parameters: - endpoint_id (path, string, required) - event_type (query, string, required): Event type to fire, e.g. 'invoice.sent' Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/webhooks/slack/o2c-hours Slack webhook: inbound O2C hours submission Receives the `/hours` slash command and `app_mention` events from Slack and turns them into pending drawdown entries awaiting approval. Requests are verified against the Slack signing secret; the Slack workspace must be linked to a business via the O2C settings `slack_team_id`. Responses: 200, 403 ## 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 ## oauth-grants ### GET /api/v1/oauth/grants List connected apps List active MCP OAuth grants for the current user Responses: 200, 401, 422 ### DELETE /api/v1/oauth/grants/{grant_id} Revoke connected app Revoke access for a connected OAuth client. Parameters: - grant_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/oauth/grants/{grant_id}/revoke Revoke connected app Revoke access for a connected OAuth client (POST alias). Parameters: - grant_id (path, string, required) Responses: 201, 400, 401, 403, 404, 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 1-month (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) - referral_source (string, optional) - referred_by_email (string, optional) - success_path (string, optional) - cancel_path (string, optional) Responses: 201, 400, 409, 422, 429, 503 ### 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 ## orders ### GET /api/v1/orders List orders List order forms for the business with optional filters. Parameters: - status (query, string, optional): Filter by order status - customer_id (query, string, optional): Filter by customer ID - cursor (query, string, optional): Pagination cursor - limit (query, integer, optional): Page size Responses: 200, 401, 403, 422 ### POST /api/v1/orders Create order Create a new draft order with per-line billing rules. Request body (OrderCreate): - customer_id (string, required) - project_id (string, optional) - order_description (string, optional) - order_start_date (string, optional) - first_invoice_date (string, optional) - term_months (integer, optional) - discount_cents (integer, optional) - tax_treatment (string, optional) - currency (string, optional) - lines (array · OrderLineInput, optional) → OrderLineInput Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/from-estimate/{estimate_id} Create order from estimate Convert a draft or accepted estimate into a new draft order. The customer, currency, description, and line items carry over (every line billed as a one-time `flat` fee); an optional `project_id` in the body attaches the order to a project. The estimate is marked converted and linked to the order — converting it a second time (or converting it to an invoice afterwards) is rejected. Parameters: - estimate_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/orders/{order_id} Delete order Delete an order. Allowed for draft, sent, and signed orders (nothing financial has happened yet); a countersigned order can only be deleted after its contract is deleted. Parameters: - order_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/orders/{order_id} Get order Get a single order with its lines. Parameters: - order_id (path, string, required) Responses: 200, 401, 403, 404, 422 ### PUT /api/v1/orders/{order_id} Update order Update a draft order (optionally replacing all lines; the order value is recomputed from them). Revert a sent/signed order to draft first to edit it. Parameters: - order_id (path, string, required) Request body (OrderUpdate): - customer_id (string, optional) - project_id (string, optional) - order_description (string, optional) - order_start_date (string, optional) - first_invoice_date (string, optional) - term_months (integer, optional) - discount_cents (integer, optional) - tax_treatment (string, optional) - currency (string, optional) - lines (array · OrderLineInput, optional) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/attach-contract-file Attach the executed contract file Attach an uploaded executed-contract PDF (the `s3_key` from `POST /upload`, or a URL) to a sent, signed or countersigned order. Completes the presign → upload → attach flow so countersign can freeze the document reference into the contract. Attaching to a **sent** order also marks it signed — the executed copy is the customer's signature — so the stage can be finished. Parameters: - order_id (path, string, required) Request body (OrderAttachContractFileRequest): - signed_contract_file_url (string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/countersign Countersign order Countersign a signed order. Freezes the order (the audit baseline) and instantiates a live contract (signed -> countersigned). Parameters: - order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/mark-sent Mark order sent (no email) Move a draft order to sent with no customer-facing side effect. Internal approval path for orders signed out-of-band or backfilled — this path will never notify the customer. (Note: v1 has no outbound order email yet, so today this behaves identically to the send endpoint.) Parameters: - order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/remind-signer Remind the customer to sign Email the order's customer contact a signature reminder referencing the order number. Only valid while a signature is outstanding (status sent or signed); at most one reminder per order per 24 hours. The response's `signer_reminder_sent_at` reflects the send. Parameters: - order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/revert-to-draft Revert order to draft Send a sent or signed (not countersigned) order back to draft so it can be edited. Clears the sent/signed timestamps and any signed contract file. Parameters: - order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/send Send order for signature Send a draft order for signature (draft -> sent). Parameters: - order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/sign Mark order signed Mark a sent order as signed by the customer (sent -> signed). Parameters: - order_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ## orders-esign ### GET /api/v1/orders/{order_id}/esign Get an order's e-signature status Current envelope status for the order — refreshed live from the provider when credentials are configured, otherwise the stored snapshot. 400 when the order has no envelope. Parameters: - order_id (path, string, required) Responses: 200, 400, 401, 403, 404, 422 ### POST /api/v1/orders/{order_id}/esign/send Send an order for e-signature Create an e-signature envelope for a draft or sent order and email it to the customer contact for signing. The request supplies the document (an https URL or an uploaded `s3_key`) since order PDFs are rendered client-side. A draft order is moved to `sent`. Returns 400 when e-signature is not configured, the order is in the wrong status, the customer has no email, or an envelope is already out. Parameters: - order_id (path, string, required) Request body (ESignSendRequest): - document_url (string, required) - subject (string, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/webhooks/esign E-signature provider webhook Inbound envelope-event receiver for the e-signature provider (DocuSign Connect). Deliveries are authenticated by HMAC-SHA256 signature over the raw body; unsigned or mis-signed deliveries get 403. Envelope `completed` marks the order signed; `declined` and `voided` are recorded. Redeliveries are idempotent. Responses: 200, 400, 403 ## 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) - code (query, string, optional): Exact project code match (case-insensitive). - name (query, string, optional): Exact project name match (case-insensitive). - search (query, string, optional): Partial match against project code or name. 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 ### POST /api/v1/projects/bulk Bulk create projects Create up to 100 projects in one request with per-row customer resolution. Rows may pass customer_name instead of customer_id — an exact case-insensitive match auto-resolves; ambiguous or unmatched names fail only that row (CUSTOMER_AMBIGUOUS / CUSTOMER_NOT_FOUND with candidate customers) unless create_missing_customers is true, in which case unmatched customers are created inline. Request body (ProjectBulkCreateRequest): - rows (array · ProjectBulkCreateRow, required) → ProjectBulkCreateRow - create_missing_customers (boolean, optional) Responses: 201, 400, 401, 403, 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 ### DELETE /api/v1/projects/{project_id}/permanent Delete project permanently Permanently delete a project row. Rejected when the project is still tagged on invoices, journal entry lines, time entries, contracts, orders or deals unless `force` is set. Parameters: - project_id (path, string, required) - force (query, boolean, optional): Delete even when records are tagged to the project, clearing their project tag. 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, optional) - payment_term_id (string, optional) - expected_paid_on (string · date, 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, optional) - payment_term_id (string, optional) - expected_paid_on (string · date, 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) - contract_id (string, 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 · uuid, 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 · uuid, 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 · uuid, 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) - contract_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 · uuid, 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 · uuid, 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 · uuid, 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 · uuid, 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 · uuid, 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 · uuid, 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, 404, 422 ### GET /api/v1/reports/download Download report (proxy) Download a report/export file. Validates ownership and audits the download, then 307-redirects to a short-TTL presigned S3 URL so the transfer happens directly against S3; falls back to a same-origin proxy stream if presigning is unavailable. Parameters: - s3_key (query, string, required) - filename (query, string, optional) - inline (query, boolean, optional) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/reports/preview-info Report preview availability Report whether a server-rendered PNG preview is available for a report/export file, and how many pages it has. Validates ownership exactly like /download. Renders and caches every page (capped at 20) on the first call for a key; later calls are served from cache. Parameters: - s3_key (query, string, required) Responses: 200, 400, 401, 403, 404, 422 ### GET /api/v1/reports/preview-page Report preview page image Return one rendered page of a report/export preview as a raw PNG image. Validates ownership exactly like /download. Renders and caches on a cache miss. Parameters: - s3_key (query, string, required) - page (query, integer, required): 1-based page number. Responses: 200, 400, 401, 403, 404, 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 ### DELETE /api/v1/reports/{report_id} Delete report Delete a report and remove its associated S3 file. Parameters: - report_id (path, string, required) Responses: 200, 401, 403, 404, 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 ## revenue-schedules ### GET /api/v1/revenue-schedules List revenue schedules List deferred-revenue tranches across all sources. Parameters: - source (query, RevenueSourceEnum, optional): Filter by source - limit (query, integer, optional): Page size - offset (query, integer, optional): Offset Responses: 200, 401, 403, 422 ### POST /api/v1/revenue-schedules/preorders Book a pre-order Collect pre-order cash to deferred revenue (releases on fulfillment). Request body (PreorderBookRequest): - order_reference (string, required) - amount_cents (integer, required) - order_date (string · date, optional) - receive_via (string, optional) - revenue_ledger_name (string, optional) - currency (string, optional) - reference_id (string, optional) Responses: 201, 400, 401, 403, 422 ### POST /api/v1/revenue-schedules/preorders/fulfill Recognize a pre-order on fulfillment Release a parked pre-order tranche into revenue (point-in-time). Request body (PreorderFulfillRequest): - order_reference (string, required) - fulfillment_date (string · date, optional) Responses: 201, 400, 401, 403, 404, 422 ### POST /api/v1/revenue-schedules/recognize Run recognition Post all due tranches the unified engine owns for this business. Responses: 201, 401, 403, 422 ### GET /api/v1/revenue-schedules/waterfall Deferred-revenue waterfall Remaining deferred revenue scheduled to release, by month. Parameters: - source (query, RevenueSourceEnum, optional): Filter by source Responses: 200, 401, 403, 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-attach-many Bulk attach tags to many entities Attach one or more tags to many entities of the same type in a single request. Idempotent; existing attachments are skipped. Replaces the per-entity request fan-out used for bulk tagging a table selection. Request body (BulkEntityTagAttachManyRequest): - tag_ids (array · string, required) - model_type (string, required) - model_ids (array · 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-expenses-to-invoice Add expenses to invoice Append billable vendor expenses to a draft invoice. Request body (AddExpensesToInvoiceRequest): - invoice_id (string, required) - document_link_ids (array · string, required) 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 ### GET /api/v1/time-entries/billable-expenses List billable expenses Vendor bill costs available to on-bill to a customer invoice. 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 ## todos ### GET /api/v1/todos List user todos List todos for the authenticated user with cursor pagination. Parameters: - completed (query, boolean, optional): Filter by completion 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, 422 ### POST /api/v1/todos Create a todo Create a personal or business-scoped todo for the current user. Request body (UserTodoCreateRequest): - title (string, required) - description (string, optional) - business_id (string, optional) - due_date (string · date-time, optional) - reminder_at (string · date-time, optional) - priority (TodoPrioritySchema, optional) → TodoPrioritySchema - entity_type (string, optional) - entity_id (string, optional) Responses: 201, 400, 401, 404, 422 ### DELETE /api/v1/todos/{todo_id} Delete a todo Soft-delete a todo. Parameters: - todo_id (path, string, required) Responses: 200, 401, 404, 422 ### GET /api/v1/todos/{todo_id} Get a todo Retrieve a single todo by ID. Parameters: - todo_id (path, string, required) Responses: 200, 401, 404, 422 ### PATCH /api/v1/todos/{todo_id} Update a todo Partially update a todo's fields. Parameters: - todo_id (path, string, required) Request body (UserTodoUpdateRequest): - title (string, optional) - description (string, optional) - due_date (string · date-time, optional) - reminder_at (string · date-time, optional) - priority (TodoPrioritySchema, optional) - entity_type (string, optional) - entity_id (string, optional) Responses: 200, 400, 401, 404, 422 ### POST /api/v1/todos/{todo_id}/complete Complete a todo Mark a todo as completed. Parameters: - todo_id (path, string, required) Responses: 201, 401, 404, 422 ### POST /api/v1/todos/{todo_id}/uncomplete Uncomplete a todo Mark a completed todo as pending again. Parameters: - todo_id (path, string, required) Responses: 201, 401, 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. - reconciled (query, boolean, optional): Filter by reconciliation state. `true` returns only reconciled transactions, `false` only unreconciled. Omit for all. - posted_status (query, string, optional): Filter by bank posting status: 'posted' (cleared activity that affects the books) or 'pending' (unsettled authorizations, read-only awareness layer). Omit to return both. - 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) - remember (boolean, optional) 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) - reconciled (boolean, 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- or Xero-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 or Xero transaction on the DayZero local accounting engine. Plaid rows get their auto journal entry re-created immediately; Xero rows get theirs rebuilt from the source data on the next Xero sync. Parameters: - transaction_id (path, string, required) Responses: 201, 400, 401, 403, 404, 422 ### PATCH /api/v1/transactions/{transaction_id}/split Split a transaction across categories Rebuild a transaction's journal entry so its single category leg becomes multiple category legs (e.g. 70% COGS / 30% Sales Tax). Allocations may be percentages, fixed amounts, or a remainder line; the resulting legs always balance against the bank leg. Parameters: - transaction_id (path, string, required) Request body (TransactionSplitRequest): - split (array · SplitAllocation, required) → SplitAllocation Responses: 200, 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) - business_id (query, string, optional) - firm_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 - x-firm-id (header, string, optional) 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. Requires a JSON body with 'confirm_password' — the calling user's own account password. Parameters: - user_id (path, string · uuid, required) Request body (UserDeleteRequest): - confirm_password (string · password, 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-bill-requests ### GET /api/v1/vendor-bill-requests List vendor bill-request links All links for the business, newest first. Raw tokens are never returned. Responses: 200, 401, 403, 422 ### POST /api/v1/vendor-bill-requests Create a vendor bill-request link Mint a public URL a vendor or contractor can use to submit a bill without an account. The raw URL is returned once and cannot be recovered later; only its hash is stored. Request body (VendorBillRequestLinkCreateRequest): - vendor_id (string, optional) - label (string, optional) - expires_in_days (integer, optional) Responses: 201, 400, 401, 403, 404, 422 ### DELETE /api/v1/vendor-bill-requests/{link_id} Revoke a vendor bill-request link Stop accepting submissions through the link. The public form returns 404 after. Parameters: - link_id (path, string, required) Responses: 200, 401, 403, 404, 422 ## vendor-bill-requests-public ### GET /api/v1/public/bill-requests/{token} Vendor bill-request form metadata (unauthenticated) Resolve a bill-request token to what the submit form needs: business name, pre-bound vendor (if any), label, and required fields. Returns 404 for unknown, expired, or revoked tokens — the three are indistinguishable on purpose. Rate-limited per IP. Parameters: - token (path, string, required) Responses: 200, 404, 422, 429 ### POST /api/v1/public/bill-requests/{token} Submit a bill through a bill-request link (unauthenticated) Multipart form: submitter_name, submitter_email, amount (dollars), optional invoice_number / due_date / description, and the invoice file (PDF, PNG, or JPG). Creates a draft bill for the manager to confirm or deny. 409 when the invoice looks like a duplicate. Rate-limited per IP and per link. Parameters: - token (path, string, required) Responses: 201, 400, 404, 409, 422, 429 ## 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) - allocation_lines (array · JournalEntryLineItem, 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) - allocation_lines (array · JournalEntryLineItem, 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}/journal-entry Generate vendor credit journal entry Post the issue journal entry for a vendor credit that has none linked (Dr offset account, Cr Vendor Credit Balance). Parameters: - vendor_credit_id (path, string, required) Request body (VendorCreditJournalEntryGenerateRequest): - ledger_id (string, optional) - journal_entry_date (string · date, optional) Responses: 201, 400, 401, 403, 404, 409, 422 ### POST /api/v1/vendor-credits/{vendor_credit_id}/journal-entry/link Attach journal entry to vendor credit Link an existing journal entry to a vendor credit. Parameters: - vendor_credit_id (path, string, required) Request body (JournalEntryLinkRequest): - journal_entry_id (string, required) Responses: 201, 400, 401, 403, 404, 409, 422 ### POST /api/v1/vendor-credits/{vendor_credit_id}/unapply Un-apply vendor credit from bill Remove (un-link) a vendor credit from a bill, restoring balances. Parameters: - vendor_credit_id (path, string, required) Request body (VendorCreditUnapply): - bill_id (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) - status (query, string, optional): Filter by status: active, inactive, or blocked - 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