Free for 30 days on Scale.Start free
AP invoice routing and approval
Every step in the template

AP invoice routing and approval

An AP workspace where every invoice gets one row, the right approver gets pinged in Slack with full context, and Payment Ready rows are the single source of truth for what's cleared to pay.

Outcome

An AP workspace where every invoice gets one row, the right approver gets pinged in Slack with full context, and Payment Ready rows are the single source of truth for what's cleared to pay.

Time45 min setup, ongoing ~2 min per invoice reviewDifficultybeginnerForControllers, office managers, and ops leads handling 20-200 invoices/month with multiple approver tiers.
How this works

Open it, hand it to your agent, walk the steps.

Paste this to your agent (Claude / Cursor / Codex)
You are the agent running on the "AP invoice routing and approval" template workspace, connected via MCP at your-org/ap-invoice-routing-approval.

Your job: watch invoices-inbox/, extract invoice fields with Claude, route each invoice to the right approver based on amount thresholds, and create one Invoice queue row per invoice. Never flip Pending rows to Approved. Never move money.

User-loop protocol:
- You propose. The operator decides. Never flip Status past what the routing tier dictates. Never mark Payment Ready.
- Hourly (or "route invoices"): list .txt/.md files in INVOICES_INBOX_DIR. Skip names in processed_invoices.json. For each new file, run Claude extract on the contents.
- Extracted fields: vendor, invoice_number, amount (number), due_date (YYYY-MM-DD), department, gl_code, notes (one-sentence summary).
- Route based on amount: below AUTO_APPROVE_UNDER -> Status=Approved + auto-approved Slack post (no approver tagged). Between AUTO_APPROVE_UNDER and HIGH_VALUE_ABOVE -> Status=Pending + Approver=APPROVER_STANDARD + Slack approval request mentioning APPROVER_STANDARD. Above HIGH_VALUE_ABOVE -> Status=Pending + Approver=APPROVER_HIGH_VALUE + Slack approval request mentioning APPROVER_HIGH_VALUE.
- Add the filename to processed_invoices.json on success. If extraction fails with json.JSONDecodeError, mark processed permanently and log. If a transient error (Dock, Slack, network), do NOT mark processed; retry next run.
- End of every working session, write 1 paragraph to Status: invoices processed, routed standard, routed high-value, auto-approved, anything that failed extraction.

Don't touch:
- Invoice queue.Status (Pending / Approved / Rejected / Payment Ready is the operator's flow).
- Invoice queue.Approved At / Approved By (process_approvals.py + the human approver set those).
- processed_invoices.json transient errors (only permanent failures get marked).

First MCP tool calls:
1. list_surfaces(workspace_slug="ap-invoice-routing-approval")
2. list_rows(workspace_slug="ap-invoice-routing-approval", surface_slug="invoice-queue")
3. get_doc(workspace_slug="ap-invoice-routing-approval", surface_slug="status")
The template · 5 steps

Top to bottom. Each step has tasks, pointers, gotchas.

Set routing thresholds + approver Slack IDs

5 min

The whole pipeline pivots on three numbers and two Slack user IDs. AUTO_APPROVE_UNDER is the threshold below which invoices skip approval entirely (default 500). HIGH_VALUE_ABOVE is the threshold above which invoices route to the high-value approver (default 5000). APPROVER_STANDARD and APPROVER_HIGH_VALUE are Slack user IDs (e.g. U12345678).

Tasks
  • Decide AUTO_APPROVE_UNDER. Below this number, invoices are auto-approved without a Slack ping. Default 500. Higher if you trust historical vendors more.
  • Decide HIGH_VALUE_ABOVE. Above this, invoices route to the high-value approver. Default 5000.
  • Get APPROVER_STANDARD Slack user ID: in Slack, click the approver's profile, More, Copy Member ID. Format like U12345678.
  • Get APPROVER_HIGH_VALUE Slack user ID the same way. Often the CFO or CEO at smaller companies.
Gotchas
  • Slack user IDs (Uxxxxx) not display names. Display names break when someone changes theirs.
  • If APPROVER_STANDARD is blank, the Slack message still posts but without a mention, so nobody gets notified. Always fill both.

Wire .env + the two Python scripts

20 min

Two scripts: route_invoices.py runs on cron (hourly or whatever fits), process_approvals.py runs daily. Both read .env, both are standalone CueAPI handlers. Install Anthropic, requests, dotenv, drop scripts from Setup guide.

Tasks
  • Open Setup guide (doc) and copy route_invoices.py + process_approvals.py into a local folder
  • Run pip install anthropic requests python-dotenv
  • Create .env with DOCK_API_KEY, DOCK_WORKSPACE_SLUG, ANTHROPIC_API_KEY, SLACK_WEBHOOK_URL, INVOICES_INBOX_DIR=./invoices-inbox, AUTO_APPROVE_UNDER=500, HIGH_VALUE_ABOVE=5000, APPROVER_STANDARD, APPROVER_HIGH_VALUE, CURRENCY_SYMBOL=$, CLAUDE_MODEL=claude-sonnet-4-6
  • Generate a Dock API key at trydock.ai/settings/api
  • Create a Slack incoming webhook for your finance or ops channel, paste the URL into SLACK_WEBHOOK_URL
  • mkdir invoices-inbox; drop a test invoice as a .txt file (plain text vendor/amount/due date)
  • Run python route_invoices.py once manually. Confirm an Invoice queue row appears + a Slack approval request lands in the channel.
Gotchas
  • PDF invoices need pdfplumber. The base template handles .txt and .md only. See the Extending section in Setup guide for the PDF swap.
  • Empty file in inbox: the script skips silently and marks processed. Check the log if you expected a row and didn't get one.
Agent prompt for this step
Run a first route of the inbox. List new files in INVOICES_INBOX_DIR. For each, run Claude extract. Route based on amount thresholds. Create an Invoice queue row at the right Status. Post the matching Slack message. Append filename to processed_invoices.json on success. Post a Status entry summarizing: processed, routed standard, routed high-value, auto-approved, failed extraction.

Test the full approval loop

15 min

Before scheduling, confirm the human-loop works end to end. Drop three test invoices at different amounts (e.g. 100, 1500, 8000). Watch Slack, watch Invoice queue, flip a Pending row to Approved, run process_approvals.py, confirm Payment Ready and the second Slack ping.

Tasks
  • Drop three test invoices in invoices-inbox/ at amounts that hit each tier: ~100 (auto-approve), ~1500 (standard), ~8000 (high-value)
  • Run python route_invoices.py. Watch Slack: one auto-approved confirmation, two approval requests with the right mentions.
  • In Dock Invoice queue, flip one Pending row's Status to Approved
  • Run python process_approvals.py. Watch: Invoice queue Status flips to Payment Ready + Approved At gets stamped + Slack gets a 'Ready for Payment' message.
  • Confirm the auto-approved row is already at Status=Approved with no further action needed.
Gotchas
  • If the Status flip doesn't trigger the Payment Ready transition, check that process_approvals.py is reading the same DOCK_WORKSPACE_SLUG.
  • If Slack mentions don't notify the approver, the bot user might not be in the channel. Invite the bot to the channel.

Schedule hourly route + daily approval sweep

10 min

Two cron tasks: route_invoices.py hourly (or every 15 min if you process invoices faster), process_approvals.py daily at 9 AM. CueAPI is the right pick for cloud-scheduled runs. Cron is fine for an always-on machine.

Tasks
  • Option A, cron: crontab -e, add `0 * * * * cd /path && source .env && python3 route_invoices.py >> route.log 2>&1` and `0 9 * * * cd /path && source .env && python3 process_approvals.py >> process.log 2>&1`
  • Option B, CueAPI: pip install cueapi cueapi-worker, then cueapi create --schedule '0 * * * *' --name 'ap-invoice-router' --handler ./route_invoices.py and cueapi create --schedule '0 9 * * *' --name 'ap-invoice-processor' --handler ./process_approvals.py
  • Confirm next hour: Status has a fresh session entry. Drop a real invoice + watch the routing fire automatically.
Gotchas
  • Hourly route is the typical pace. Faster than that and approvers get hit with a Slack ping per invoice within minutes; slower than that and invoices sit in the inbox folder visibly stale.
  • Daily process_approvals.py is enough for most teams. Bump to twice-daily if approvers tend to clear queues mid-afternoon.

Set the audit + month-end review cadence

15 min/month ongoing

Beyond the routing itself, the value of this template is the audit log. Every routing decision lives in Approval log doc + every invoice row stays in Invoice queue. Month-end, walk the queue: how many invoices auto-approved, how many escalated, anything that should change about the thresholds.

Tasks
  • Block 15 min at month-end on the calendar
  • Open Invoice queue (table). Filter to Submitted At in last 30 days. Count rows at each Status.
  • Are too many invoices going to the high-value approver? Bump HIGH_VALUE_ABOVE.
  • Are auto-approvals catching invoices that should have a human eye? Lower AUTO_APPROVE_UNDER.
  • Walk Approval log (doc) for any rejected invoices. Confirm the vendor follow-up happened.
Gotchas
  • Changing AUTO_APPROVE_UNDER mid-month re-routes future invoices but doesn't re-evaluate past rows. That's correct behavior.
  • Auto-approved invoices still belong in the queue for audit. Don't filter them out of the monthly review.
FAQ

Common questions on this template.

Does the agent move money or initiate payments?
No. The agent extracts, routes, and tracks. Payment Ready is the workflow's terminal state. Actual payment happens in your AP system (QuickBooks, Xero, NetSuite, or a bank portal) by a human. The Extending section of Setup guide outlines wiring Payment Ready to a QuickBooks bill creation, but money movement is operator-initiated.
Can I add PDF invoices to the inbox folder?
Yes, with one extension: pip install pdfplumber, then in route_invoices.py extract text with pdfplumber.open(path) before passing to extract_invoice(). The base template handles .txt and .md only because not every operator wants the pdfplumber dep.
What if Claude misreads an amount or vendor?
Two protections. First, the approver sees the extracted fields in the Slack message before approving; misreads get caught here. Second, every routing decision logs to Approval log doc, so audit trail is intact. If you see persistent extraction misses on a vendor, edit the prompt in extract_invoice() to include a vendor-specific hint.
Can I route by department too, not just amount?
Yes, via the routing_rules.json extension in Setup guide. The base template routes on amount only. You can add per-department routing (IT invoices to IT director, marketing invoices to CMO) by extending the routing function. The pattern is the same: read department from the extracted fields, look up the right approver, route.
What happens to invoices that fail Claude extraction?
They get logged and marked permanently processed (json.JSONDecodeError). The agent moves on; the file stays in the inbox folder for manual review. Transient errors (Dock down, Slack 500) don't mark processed and will retry next run. This is intentional: a bad scan shouldn't loop forever, but a Dock outage shouldn't lose invoices.

Open this template as a workspace.

We mint a fresh copy in your org with the steps as table rows, the pointers as a separate table, and the brief as a doc. Bring your agents, start checking off boxes.