Invoice Match Custom
What it does
An extractor reads the invoice document and pulls vendor, line items, quantities, and totals. A matcher checks those against the purchase order and goods-receipt records in the database: does the price match the PO, was the quantity received, has this invoice number been paid before. The flow branches on the result: a clean match is summarized and paused for human approval; a mismatch produces an exception report that names each disagreement. No payment is executed by the workflow under any path.
The cast · 4 agents
Three-way match the invoice against the purchase order and goods-receipt records: price per line against the PO, quantity against what was received, and the invoice number against payment history for duplicates. Return an object with a 'clean' boolean and, per line, what matched or disagreed. Read-only.
Read the invoice document. Extract the vendor, invoice number, date, line items with quantities and unit prices, and the total. Flag anything illegible rather than guessing.
Summarize the clean match in three lines — vendor, amount, what it matched against — and post it to the AP channel for sign-off. Do not pay anything.
Write the exception: name each disagreement precisely (duplicate invoice number, price above PO, quantity short of receipt), what the records say, and what to check first. Post it to the AP channel.
Flow
Interface
Depends on · 3 MCP servers
Multi-account MCP for Gmail, Calendar, Drive, Docs, and Sheets — 61 tools, tokenio.github.adelaidasofia/google-workspace-mcpdriveAVerified
io.github.supabase/postgres-mcpnot in the registry yetdb
io.github.slack/slack-mcpnot in the registry yetchat# Generated by @socketcat/compiler for target: langgraph
# blueprint: com.socketcat/invoice-match v1.0.0 schema: socketcat.dev/blueprint/v0
# This code is yours. Edit it freely. The socketcat_runtime helper is optional and can be vendored.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import socketcat_runtime as _rt
class State(TypedDict, total=False):
invoiceRef: object
disposition: object
invoice: object
match: object
def node_AgentInvoke_0(state):
return _rt.run_agent("extractor", state, {"invoiceRef":"invoiceRef"}, "invoice")
def node_AgentInvoke_1(state):
return _rt.run_agent("matcher", state, {"invoice":"invoice"}, "match")
def node_Branch_2(state):
return {}
def node_Join_3(state):
return {}
def node_AgentInvoke_4(state):
return _rt.run_agent("approvalClerk", state, {"match":"match","invoice":"invoice"}, "disposition")
def node_Ask_5(state):
return _rt.run_ask("Approve this invoice for payment, or send it back with notes.", state, "disposition")
def node_AgentInvoke_6(state):
return _rt.run_agent("exceptionWriter", state, {"match":"match","invoice":"invoice"}, "disposition")
def route_Branch_2(state):
if _rt.cond("match.clean == true", state): return "AgentInvoke_4"
return "AgentInvoke_6"
def build():
b = StateGraph(State)
b.add_node("AgentInvoke_0", node_AgentInvoke_0)
b.add_node("AgentInvoke_1", node_AgentInvoke_1)
b.add_node("Branch_2", node_Branch_2)
b.add_node("Join_3", node_Join_3)
b.add_node("AgentInvoke_4", node_AgentInvoke_4)
b.add_node("Ask_5", node_Ask_5)
b.add_node("AgentInvoke_6", node_AgentInvoke_6)
b.add_edge(START, "AgentInvoke_0")
b.add_edge("AgentInvoke_0", "AgentInvoke_1")
b.add_edge("AgentInvoke_1", "Branch_2")
b.add_conditional_edges("Branch_2", route_Branch_2, {"AgentInvoke_4": "AgentInvoke_4", "AgentInvoke_6": "AgentInvoke_6"})
b.add_edge("Join_3", END)
b.add_edge("AgentInvoke_4", "Ask_5")
b.add_edge("Ask_5", "Join_3")
b.add_edge("AgentInvoke_6", "Join_3")
return b.compile()
INPUTS = ["invoiceRef"]
OUTPUTS = ["disposition"]
if __name__ == "__main__":
_rt.main(build, INPUTS, OUTPUTS)
▸blueprint.json (the portable format)
{
"id": "com.socketcat/invoice-match",
"flow": {
"type": "sequence",
"blocks": [
{
"in": {
"invoiceRef": "invoiceRef"
},
"out": "invoice",
"use": "extractor",
"type": "agent"
},
{
"in": {
"invoice": "invoice"
},
"out": "match",
"use": "matcher",
"type": "agent"
},
{
"else": [
{
"in": {
"match": "match",
"invoice": "invoice"
},
"out": "disposition",
"use": "exceptionWriter",
"type": "agent"
}
],
"type": "branch",
"cases": [
{
"when": "match.clean == true",
"blocks": [
{
"in": {
"match": "match",
"invoice": "invoice"
},
"out": "disposition",
"use": "approvalClerk",
"type": "agent"
},
{
"out": "disposition",
"type": "ask",
"prompt": "Approve this invoice for payment, or send it back with notes."
}
]
}
]
}
]
},
"tags": [
"finance",
"accounts-payable",
"three-way-match",
"human-in-the-loop"
],
"title": "Invoice Match",
"agents": {
"matcher": {
"model": {
"hint": "reasoning"
},
"title": "Matcher",
"tools": [
"db.query_dsl"
],
"output": {
"type": "object",
"required": [
"clean"
],
"properties": {
"clean": {
"type": "boolean"
},
"lines": {
"type": "array"
}
}
},
"instructions": "Three-way match the invoice against the purchase order and goods-receipt records: price per line against the PO, quantity against what was received, and the invoice number against payment history for duplicates. Return an object with a 'clean' boolean and, per line, what matched or disagreed. Read-only."
},
"extractor": {
"model": {
"hint": "fast"
},
"title": "Extractor",
"tools": [
"drive.read_doc"
],
"instructions": "Read the invoice document. Extract the vendor, invoice number, date, line items with quantities and unit prices, and the total. Flag anything illegible rather than guessing."
},
"approvalClerk": {
"model": {
"hint": "fast"
},
"title": "Approval Clerk",
"tools": [
"chat.post_message"
],
"instructions": "Summarize the clean match in three lines — vendor, amount, what it matched against — and post it to the AP channel for sign-off. Do not pay anything."
},
"exceptionWriter": {
"model": {
"hint": "reasoning"
},
"title": "Exception Writer",
"tools": [
"chat.post_message"
],
"instructions": "Write the exception: name each disagreement precisely (duplicate invoice number, price above PO, quantity short of receipt), what the records say, and what to check first. Post it to the AP channel."
}
},
"estate": {
"match": {
"type": "object",
"description": "The matcher's verdict, including a 'clean' flag and per-line results."
},
"invoice": {
"type": "object",
"description": "The extracted vendor, line items, and totals."
}
},
"$schema": "socketcat.dev/blueprint/v0",
"authors": [
{
"url": "https://socketcat.com",
"name": "SocketCat"
}
],
"license": "MIT",
"servers": [
{
"ref": "io.github.adelaidasofia/google-workspace-mcp",
"alias": "drive"
},
{
"ref": "io.github.supabase/postgres-mcp",
"alias": "db"
},
{
"ref": "io.github.slack/slack-mcp",
"alias": "chat"
}
],
"summary": "Extracts an invoice, three-way matches it against PO and receipt records, and routes to approval or a written exception.",
"targets": [
"langgraph",
"*"
],
"version": "1.0.0",
"interface": {
"inputs": {
"invoiceRef": {
"type": "string",
"description": "The invoice to process, as a drive document id or path."
}
},
"outputs": {
"disposition": {
"type": "string",
"description": "Approved-and-queued, or the exception report, whichever path ran."
}
}
},
"extensions": {
"dev.langgraph": {
"checkpointer": "memory"
}
},
"description": "An extractor reads the invoice document and pulls vendor, line items, quantities, and totals. A matcher checks those against the purchase order and goods-receipt records in the database: does the price match the PO, was the quantity received, has this invoice number been paid before. The flow branches on the result: a clean match is summarized and paused for human approval; a mismatch produces an exception report that names each disagreement. No payment is executed by the workflow under any path."
}More in Finance & legal
Browse all →Contract Redline
Extracts a contract's clauses, checks each against your playbook, and loops a redliner against a reviewer until it's clean — then a lawyer approves.