Sheet KEY-09 — Finance & CRM Manual
Expenses
Expense intake, approvals, reimbursement handling, and the workflow that keeps spend reviewable as volume grows.
Scope
Expense handling is one of the first places companies feel avoidable process drag. This public guide keeps the operating workflow and control model while excluding private implementation detail.
Expense tracking, vendor management, expense policy automation, receipt attachments, and bills (accounts payable).
Bill Approval Handoff
Bill approval enforces maker-checker separation when more than one authorized accounting operator exists. A bill creator sees Awaiting another approver instead of an approval control that will fail. Another operator with keystone.accounting.manage can approve it. Any other approval rejection remains visible above the bill table with the backend’s actionable reason; it is not limited to a transient toast.
Expenses and Vendors
Vendor Management
Vendors are companies or individuals you pay.
List vendors:
The expense create/edit modal uses this endpoint as a searchable chooser, so operators type a vendor name instead of scrolling a bounded select when the tenant has a large vendor set.
Vendor detail:
Choose a company before opening Vendor financials. Expense, Bill, and Purchase Order totals remain separate and display only when the server proves a complete currency population matching that company’s functional currency. Unavailable means that source has legacy, missing, or mismatched evidence; it is not zero and is never combined with another source.
The detail summary exposes Last Payment as the latest payment across both direct expense payments and AP bill payments for the exact selected company. The activity list is server-paged, retains the last same-company evidence during a recoverable refresh failure, and clears evidence when the company changes. Each activity amount discloses its basis: Expense uses frozen entity-functional currency, while Bill and Purchase Order use their stored transaction currency. A foreign-currency source remains visible here but is not added to the functional summary. Expense and Bill rows link to their validated entity-scoped source; unsupported destinations remain plain evidence instead of dead links.
Open a vendor and select Financial to review that vendor’s AP aging before a payment run. Keystone shows current, 1-30, 31-60, 61-90, and 91+ day balances, the total outstanding balance, and open/overdue bill counts as of the displayed date. Amounts are based on approved and partially paid bills and are net of payments already recorded. Draft, received, paid, void, and zero-balance bills are excluded. A vendor with a proven available population and no qualifying balance shows an explicit empty zero; dependency or currency failure shows unavailable with no numeric values. This selected-vendor slice does not claim a tie-out to the entity-wide AP control account; open the full AP-aging workspace for that reconciliation.
Create vendor:
If Keystone rejects a vendor field, the modal preserves the entered data, returns to the relevant tab, and shows the correction beside that field. Email addresses must use a valid deliverable domain; reserved test domains can be rejected by the server-side email validator.
Fields:
name(required)category– vendor categorypayment_terms_days– default: 30notes
Update vendor:
If you send a blank vendor_number, Keystone normalizes it to null instead of persisting an empty string that can collide with tenant uniqueness rules.
Expense Tracking
Approvers always see a truthful pending-reimbursement region independent of the ordinary expense browse list: loading, a successful “no approved reimbursements” result, rows/count, or an unavailable warning with Try again. If the queue read fails, ordinary expenses stay usable and any previously loaded reimbursement rows are retained behind the warning. Treat the queue as unknown, not zero, and do not reconstruct settlement eligibility from the visible browse slice.
Create an expense:
The create/edit dialog preserves all entered values when validation, policy, posting, or receipt upload fails. The actionable backend or validation message remains visible above the dialog actions until the operator retries or closes the dialog; it is not available only as a transient notification.
Edits are available only before accounting approval/posting. Once an expense is approved, paid, or linked to a GL journal, Keystone rejects changes and records the denied attempt. Reverse the posting through the controlled accounting workflow and create a corrected expense; do not alter the posted source record.
Fields:
description(required)amount_cents(required, > 0)expense_date(required)due_date– optional override; if blank, Keystone defaults it from the vendor’s payment terms or the expense datecategory– expense categorytax_category– for tax deduction classificationis_tax_deductible– booleanpayment_methodnotes- controller exception metadata is stored on the row as
exception_type,exception_evidence, hold metadata, request-changes metadata, and resubmission metadata - linked AP bill summaries are exposed on the row as
linked_bills, so the same screen can show downstream bill state without sending operators to the accounting Bills workspace first
The Expenses create modal also has a receipt-assisted draft helper with two intake paths:
- File intake: choose a JPEG, PNG, or PDF and Keystone calls the backend upload-assist path, which attempts PDF text extraction or OCR before falling back to filename heuristics.
- Forwarded intake: paste forwarded receipt subject/body text (and optional sender) from a receipt inbox flow without selecting a local file first.
Keystone stores the resulting receipt_assist_source, confidence, and evidence on the expense row. Draft suggestions may be marked review_required when extraction is weak or incomplete.
You can keep the suggested fields, correct them inline, or use Discard extracted fields to clear the extracted values without losing the selected receipt. On a brand-new draft, that reset returns amount, vendor, category, and date to truly blank fields instead of leaving a synthetic 0.00 amount behind. If receipt analysis fails, Keystone keeps the file selected, shows the failure inline, and lets you retry analysis or continue with manual entry. The selected receipt still uploads after the expense is saved. The same draft modal is used for both new expenses and edits, so receipt-assist, manual fallback, and receipt-upload retry behave the same way in both cases.
When the Expenses page has many rows, it no longer stops at the first slice. The expense list exposes a Load more expenses control, and the approval queue uses page controls with a visible Showing X-Y of Z range so operators can tell what is loaded and what still exists.
The approval workspace opens on Team, where unassigned and shared work cannot be hidden by a personal filter. Team is an inventory and routing view; an assigned row can appear there without its decision controls. Switch to Mine to approve, hold, request changes, or reject work assigned to you. Overdue isolates missed due times. After an operator claims an unassigned item, Keystone moves the workspace to Mine so the claimed approval and its decision controls remain in view. Empty views explain which scope to check rather than implying that the tenant has no approvals.
The expense table also has a spend-control column that shows linked AP bill status, bill number, and vendor evidence when those records exist. Direct vendor expenses do not require a separate bill: after approval, the row identifies the posted GL payable and says it is awaiting payment. Employee reimbursements are labeled separately and say when they are awaiting settlement. The status is visible text, not something operators must infer from a dollar icon. Edit controls disappear after approval because posted source truth is immutable. Mounted expense browse, summary, AP-aging, and expense-policy reads run through the tenant-scoped authenticated request session on the backend. Operators should never need to pass tenant IDs manually for those surfaces.
New expenses do not always stay pending. On creation, Keystone evaluates any active expense policies:
auto_approveonly commits if the GL posting succeeds; if the GL posting fails, the expense is not createdauto_rejectmarks the expenserejectedand stores the policy reason on the expense row- if no policy matches, the expense remains
pending - if a policy uses
require_approval, Keystone persists approval-routing metadata so the item can be owned, claimed, and escalated instead of floating as an anonymous pending record require_approvalcan be multi-stage; expenses move through each configured stage in order, and a different person must complete every stage before terminal approval
Receipt-assisted creates are intentionally forced into review (require_approval) even when a matching policy would normally auto-approve or auto-reject. This keeps AI/OCR-assisted drafts in the same human exception queue before posting.
Duplicate detection can auto-hold an expense when the backend finds an earlier matching expense. Receipt/transaction mismatch handling can move an expense to changes_requested when the saved expense does not match the receipt-assist evidence snapshot.
Every expense keeps the policy snapshot that produced its current state:
applied_policy_namepolicy_actionpolicy_reasonpolicy_evaluated_at
Edit an expense:
Operators can edit eligible expenses directly from the Expenses screen. Paid expenses remain immutable.
Approve an expense:
Creates GL journal entries at terminal approval: debit Expense account, credit Accounts Payable. Approval authority is separate from expense maintenance: operators need keystone.expenses.approve, and requesters cannot approve or otherwise review their own expense. The Expenses screen hides those unavailable actions instead of allowing a dead-end request.
For a staged expense, check the current and next route before acting. A person who completed an earlier stage cannot approve a later stage, even if that person is also eligible through the later role or group. Keystone returns 403 and keeps the current stage, assignment, history, GL state, and success-audit evidence unchanged. Hand the item to another eligible approver; do not retry as the same person or rewrite approval history to bypass the control.
Claim an approval:
The same queue also includes held and changes_requested items, and the operator surface exposes hold, request-changes, and resubmit actions for controller review. Queue rows now show richer requester/approver context and current/next stage route hints for controller handoffs. Approve actions from the queue still go through a confirmation modal before the stage advances or the expense reaches terminal approval.
Reject an expense:
Requires a rejection reason and leaves the expense in the terminal rejected state.
Hold an expense:
Use this when you need to pause an expense without creating a second review queue. The row stays visible, the hold reason is stored, and SLA tracking pauses on the held item.
Request changes on an expense:
Use this when the submitter needs to fix the original expense row. The row becomes changes_requested, the reason is stored, and the same item stays in the controller queue.
The same expense row continues to carry the linked AP truth through hold/request-changes/resubmit, so operators do not have to reconstruct the spend-control state from another surface.
Resubmit an expense:
Mark expense as paid:
Creates GL journal entries: debit Accounts Payable, credit Cash.
Upload receipt:
Upload a receipt image (JPEG, PNG) or PDF. Max 10 MB.
If you already used the receipt-assisted draft helper, this step is still the final attachment step. The draft helper does not replace the normal receipt upload path.
Download receipt:
In the web UI, receipt retrieval uses the authenticated API client and a blob response. Keystone does not put bearer tokens in receipt URLs.
Expense Policies
Expense policies let finance teams automate low-risk approvals and reject clearly disallowed spend without reviewing every item by hand.
Minimum policy amounts use the standard currency control. The displayed value is dollars and cents, while Keystone keeps the form and API value as integer cents. Clear the control to remove the minimum floor entirely; a blank floor is null, not zero.
The Expenses screen now includes an Expense Policies tab for creating, editing, and deactivating these rules without backend tooling. The expense list shows which policy snapshot produced each row’s current state.
List policies:
Create policy:
Common policy fields:
name(required)priority– higher numbers run firstmax_amount_centsandmin_amount_centsallowed_categoriesandblocked_categoriesapproval_required_from– optional approval route, either single-stage or multi-stage
Update policy:
Deactivate policy:
Policies are soft-deactivated rather than hard-deleted.
Expense Summary
Returns period totals by status, category, and vendor. This summary route is tenant-scoped through the authenticated request session and is the canonical source for the Expenses page cards.
Expense Categories
AP Aging (Expenses)
The Dashboard’s Accounts Payable card is the same canonical AP aging report as the Accounting Bills workspace; expenses that are not posted AP bills are not a substitute AP population. The card presents five non-overlapping buckets using each posted bill due date: current (not yet due), 1-30 days past due, 31-60, 61-90, and 91+ days. The displayed total is the sum of those five buckets.
AP aging is tenant and one-entity scoped. The Dashboard supplies its resolved
entity and does not combine a parent roll-up or legacy unassigned expenses with
the AP control account. Operators without keystone.accounting.view for that
entity see Unavailable, not a zero balance.
Bills (Accounts Payable)
Bills are vendor invoices you receive. They have a more structured workflow than simple expenses and integrate directly with the general ledger.
Select the company entity before loading bills. The URL preserves that verified entity and can focus an exact bill or payment reached from a journal or report. If the focused evidence is no longer visible in that entity, Keystone reports it as unavailable instead of showing a successful empty list.
If the Bills list cannot be read, or the server returns an incomplete or malformed successful list response, Keystone shows Bills unavailable with the returned reason and a Try again action; it does not treat either condition as zero bills. If a refresh fails after bills are already visible, the retained list is marked as potentially stale and the New Bill, approval, payment, void, and outbound-payment actions are disabled until the list refreshes successfully. Any open create, payment, or void dialog keeps its Cancel control available, but explains that a successful refresh is required before submission. Access requires entity-scoped keystone.accounting.view; without it, the route shows an access-denied state and does not load bill data.
Bill Lifecycle
Create a Bill
Fields:
bill_date(required)due_date(required)bill_number– auto-generated (BILL-NNNNNN) if not providedtax_cents– tax amount (default: 0)lines(required, at least one):description(required)quantity– default: 1.0unit_price_centsamount_cents– must equalquantity * unit_price_cents(validated)
Update a Bill
Only draft bills can be edited. Supports updating lines (delete-and-replace).
Approve a Bill
Record Bill Payment
Fields:
amount_cents(required, > 0) – cannot exceed remaining balancepayment_date– defaults to todayreference_number
Partial payments are supported. Status auto-transitions to partially_paid or paid based on amount.
GL auto-posting: debit Accounts Payable, credit Cash/Bank.
The default keystone_expense_submitter role can create and track expenses and supporting documents, and can view the company context needed by the form. It intentionally lacks keystone.expenses.approve, so the submitter cannot approve their own claim. Assign approval work to an accountant or another role that explicitly carries the approval permission.
Void a Bill
Choose Request bill void from a current Bills list. Keystone records a protected, entity-scoped approval request; the confirmation never means that a bill is already void or that money has been reversed. The request retains the bill and payment-history evidence and waits in the separate vendor bill void Financial approvals queue for an independent qualified operator. The requester cannot decide their own request except for the documented, audited single-operator policy.
At execution Keystone rechecks the selected entity/currency, current bill and payment evidence, exact source postings, and every affected open fiscal period. It reverses payment postings before the original AP posting and then marks the bill void. Payment rows and the historical paid amount remain visible for audit; a voided bill is excluded from open AP rather than being represented as a misleading paid-history zero. If an outbound provider settlement is still submitted, completed, unknown, or otherwise not already reversed/cancelled, Keystone fails the execution conflict and tells the operator to obtain the provider reversal evidence before creating a fresh request. Failed/unavailable queue states are not an empty queue: restore the stated dependency and use the scoped retry only when the queue offers it.
AP Aging (Accounting)
Accounts payable aging is one entity-scoped report shared by Bills, the Dashboard, Vendor Financial, and scheduled exports. Select the legal entity first. Keystone ages posted bill balances as of the displayed date, subtracts posted payments dated on or before that date, and compares the result with the AP control account. It uses the five exact buckets: current, 1-30, 31-60, 61-90, and 91+ days past due.
If the source cannot be read, the interface says Unavailable rather than showing $0. If the bill subledger and AP control do not tie, or legacy bills cannot be safely assigned to the selected entity, it says Unreconciled and shows the difference; investigate the control account and legacy bill links before using the amount for a payment or close decision.
Select an aging bucket on the Bills workspace or a vendor’s Financial tab to open the exact bill population behind that amount. The evidence page retains the entity, as-of date, vendor, and bucket in the URL; its row count and outstanding total cover the whole server-filtered population, not only the visible page. Bill and issuance-journal links come from the server. If evidence cannot be loaded, the prior result may remain visible with an unavailable warning and retry; Keystone does not replace it with zero bills.
Company entity and currency
Choose the company entity in the shared workspace selector before reviewing or submitting expenses. The Expenses page shows the selected entity’s functional currency and loads server-paged expenses, categories, policies, and employees for that scope. Vendor search is a tenant-scoped directory lookup; Keystone validates the selected vendor when it records the entity-scoped expense. Switching entity clears the previous entity’s rows; a failed same-entity refresh preserves the last evidence and identifies it as unavailable.
An available expense shows its transaction currency and separately uses the server’s functional evidence for the period summary. A mixed or unavailable summary is not a zero spend result. Legacy rows without currency authority are readable for history but their mutation and approval controls are unavailable.
When receipt assistance extracts an amount without a trustworthy transaction currency, Keystone does not assume the entity’s currency. Confirm the displayed functional-currency basis, then enter or verify the amount before creating the expense. The selected receipt and form input remain available after an analysis or save failure so you can retry safely.
Approval and cash execution are separate controls. An expense approver can make the approval decision, but Pay and Reimburse require exact-entity expense-management authority; requester/reviewer separation still applies to reimbursements. An exact retry with the same settlement command returns the recorded result, while a changed method or reference is rejected. For a paid expense, use the payment and optional realized-FX journal links shown on the expense row to review the recorded settlement. If Keystone marks settlement evidence unavailable, do not treat the lifecycle label alone as confirmation: restore the stated dependency or review the audit/journal evidence before relying on the expense as settled.