Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Expense Report

An employee spends company money — lunch with a client, a flight, a software license — and needs to get reimbursed. This procedure handles the entire flow: logging the expense, routing it to the right approver based on amount, waiting for approval, and confirming reimbursement.

Use it when: Someone on your team incurs a business expense and needs to submit it for reimbursement. Common categories include meals, travel, software, equipment, and events.

What makes it interesting: The approval chain changes depending on how much was spent. Small amounts go to a team lead; bigger amounts escalate to department heads or the CEO. If an approver goes silent, the procedure automatically escalates.


Walkthrough

Header

Every procedure starts with metadata — an ID, version, trigger type, and category. This tells Gateway how to register and find the procedure.

id: expense-report
version: 1
trigger: manual
category: Finance
 
# Expense Report
 
Submit, approve, and reimburse employee expenses. Handles receipt collection,
manager approval (with escalation for amounts over threshold), finance processing,
and reimbursement confirmation.

Input

The input section defines what information is needed to start the procedure. These fields are collected when someone triggers it.

## Input
 
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| submitter | string | yes | | Who's submitting (name or Slack user ID) |
| amount | number | yes | | Total amount in USD |
| category | string | yes | | meals / travel / software / equipment / events / other |
| description | string | yes | | What the expense is for |
| receipt_url | string | yes | | Link to receipt image/PDF (Google Drive, Notion, etc.) |
| date | date | no | today | Date expense was incurred |

State

State fields track data that persists across steps. They're stored in Notion so humans can see and edit them at any point.

## State
 
| Field | Storage | Description |
|-------|---------|-------------|
| expense_record | Notion DB row | The expense entry in Expenses database |
| approver | invocation | Manager assigned to approve |
| current_step | invocation | Where we are |

Steps

Steps are where the action happens. Each step is a named unit of work. The arrow at the end shows where execution goes next.

Step 1 — Log the expense. Create a database entry and notify the submitter.

### log_expense
Create a row in the **Expenses** database:
- Title: "{input.category}: {input.description}"
- Amount: {input.amount}
- Submitted By: {input.submitter}
- Date: {input.date}
- Receipt: {input.receipt_url}
- Status: "Pending Approval"
 
DM the submitter on Slack: "Expense logged: ${input.amount} for {input.description}. Routing for approval."
 
→ route_approval

Step 2 — Route to the right approver. The procedure picks the approver based on amount thresholds, then posts in Slack and waits.

### route_approval
Determine the approver:
- If amount ≤ $200 → team lead
- If amount > $200 and ≤ $1,000 → department head
- If amount > $1,000 → COO/CEO
 
Post in #finance-approvals:
> 💰 Expense approval needed
> **{input.submitter}** submitted ${input.amount} for "{input.description}"
> Category: {input.category} | Receipt: {input.receipt_url}
> React ✅ to approve, ❌ to reject, or reply with questions.
 
**Wait**: 48h for response
**On event** `approved`: → process_reimbursement
**On event** `rejected`: → notify_rejection
**On timeout**: Escalate — ping approver again, CC finance lead.
 
→ process_reimbursement

Step 3 — Process the reimbursement. Once approved, notify finance and wait for them to confirm payment.

### process_reimbursement
Update expense record status to "Approved".
Post in #finance: "Expense approved — ${input.amount} to {input.submitter}. Please process reimbursement."
 
**Wait**: 5d for finance to confirm
**On event** `reimbursed`: → confirm
 
→ confirm

Step 4 — Handle rejection. If the approver says no, update the record and tell the submitter.

### notify_rejection
Update expense record status to "Rejected".
DM submitter: "Your expense for {input.description} (${input.amount}) was not approved."
 
→ done ✓

Step 5 — Confirm. Mark the expense as reimbursed and close the loop.

### confirm
Update expense record status to "Reimbursed".
DM submitter: "Your expense of ${input.amount} for {input.description} has been reimbursed."
 
→ done ✓

Output

The output section defines what the procedure returns when it completes. Other procedures or reports can reference these fields.

## Output
 
| Field | Type | Description |
|-------|------|-------------|
| expense_id | string | Notion page ID of the expense record |
| status | string | approved / rejected / reimbursed |
| approved_by | string | Who approved |
| processed_at | date | When reimbursement was processed |

Key Patterns

This procedure demonstrates several patterns you can reuse:

  • Tiered approval routing — different approvers based on amount thresholds
  • Escalation on timeout — if the approver doesn't respond in 48 hours, escalate automatically
  • Multi-step async flow — two separate wait periods (approval, then reimbursement processing)
  • Status updates — the submitter gets a DM at every state change
View full procedure file →