Templates

Smart Expense Approval

An employee submits an expense. Deterministic policy rules make the approve/deny call, Claude is used only to parse free-text submissions into structured fields, and anything no rule covers is routed to a human via the HR approval queue.

15 minutes setupUpdated 2026-06-22

What this is

Most expense submissions are routine and look identical, so paying for an LLM call on every one is slow and wasteful. This workflow inverts the usual "the model decides everything" design: a deterministic rule table makes the approval call, and Claude is used only to translate messy free-text submissions into structured fields.

Routine expenses are approved or denied instantly by policy rules. Anything the rules don't cover is posted to the HR approval queue for a person to decide, so nothing is silently auto-approved.

A companion workflow, , keeps the rules in sync with your written policy: when the policy changes, Claude regenerates the rule table so the deterministic engine always reflects current policy.

How it runs

A JavaScript step normalizes the incoming expense and decides whether it is already structured or free text. A Switch routes accordingly: structured submissions pass straight through, while free-text ones go to Claude to be parsed into fields. The model never makes the approval decision; it only structures the input.

A second script merges whichever branch ran into one expense object, which feeds a Decision Engine table that returns approve, deny, or no match. A final Switch acts on that result: approved expenses are filed as reimbursements, denied expenses end the run, and anything unmatched is posted to the HR approval queue for human review.

The steps

  • JavaScript (normalize_input): Reads the submission, detects whether it is structured (amount, category, and date present) or free text, and fills in defaults like employee level and currency.
  • Switch (parse_switch): Sends free text to Claude; sends structured input down the cheap path with no model call.
  • Claude (llm_parse_expense_1): JSON-only prompt that parses raw text into amount, currency, category, date, merchant, and description. Temperature 0.1 keeps parsing consistent for the same text.
  • JavaScript (build_decision_context): Merges whichever parse branch ran into a single expense object, the source of truth for the rules engine and the HR calls.
  • Decision Engine (expense_rules): Evaluates the expense-approval-rules table with FIRST_MATCH on Amount, Category, and Employee Level. Returns a Decision and a Reason.
  • Switch (route_decision): Approved goes to the reimbursement branch, denied ends the run, and anything else (including no match) escalates to a human.
  • HTTP (hr_reimbursement): Files the reimbursement in the HR system for rules-approved expenses.
  • HTTP (hr_pending_review): Posts unmatched expenses to the HR approval queue as PENDING_REVIEW. Retried so a transient outage does not drop the request.
  • Exit (exit_denied): Ends the run cleanly when the rules deny an expense.

Design notes

The model never decides approve or deny. It only structures messy input, and structured submissions skip it entirely. All decisions come from the rule table or a human, which keeps the approval path deterministic and auditable.

The final Switch treats any non-approve, non-deny result (including an empty or no-match decision) as escalate. Unmatched expenses are never auto-approved; they always reach a person.

The rule table is a shared contract: this workflow reads it, and the policy-to-decision-table add-on writes it. Updating the written policy and re-running the add-on is all it takes to change behavior.

Setup

  1. Connect Claude and configure the claude-unmeshed integration.
  2. Create the expense-approval-rules decision table with input columns Amount, Category, and Employee Level, and output columns Decision and Reason.
  3. Set hr_system_url and the hr_api_token secret for the reimbursement and approval-queue calls.
  4. Point the HTTP steps at your HR system's /api/reimbursements and /api/approvals endpoints.
  5. Trigger with a payload containing employee_id, employee_level, and either structured fields (amount, category, expense_date) or a free-text description.

Companion: policy-to-decision-table

Run whenever your written expense policy changes. A File step reads the policy, Claude converts the prose into deterministic rule rows matching the table schema, a JavaScript step validates and renders the table as CSV, and an HTTP PUT updates the expense-approval-rules table. The Claude step is cached on the policy version, so re-running the same version costs nothing.

Replace api_base_url and the api-call-token secret to point at your decisionTable API, and pass policyPath, policyFileName, and a version when you trigger it.

When to use it

  • You handle a high volume of routine expenses and want most approved instantly.
  • You need decisions to be deterministic and auditable, not left to a model.
  • Submissions arrive in mixed formats-some structured, some free text.
  • You want unmatched or unusual cases reliably routed to a human instead of guessed at.

Adapt this template in Unmeshed

Start from the sequence above, then connect your APIs, approvals, decision logic, and notifications as durable workflow steps.