Pay invoices safely
You are about to pay a supplier invoice, or to change the bank account on a vendor record. Before any money moves, run the checks below and give the person who approves the payment what they return.
Use this skill when:
- a supplier (or someone writing as the supplier) sends new bank details;
- the IBAN on an invoice differs from the account on the vendor record;
- you prepare a payment run and want each invoice checked first;
- you are asked "is this invoice OK to pay?".
The two free checks need no account and no token.
Connect
MCP server (Streamable HTTP, JSON-RPC 2.0):
{
"mcpServers": {
"jithox": { "url": "https://jithox.com/api/mcp" }
}
}
Plain HTTP works as well; every example below is a curl call. Set your own user agent as shown, so these calls can be told apart from other traffic.
Step 1 - a new or changed bank account: check_payment_change
Call it when a supplier gives a new account, and when the IBAN on the invoice
is not the one on file (then the invoice IBAN is the new one). Leave
ibanOnFile out for a first payment to a new supplier; the answer then says
nothing could be compared.
curl -s https://jithox.com/api/mcp \
-A 'pay-invoices-safely/1.0' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"check_payment_change","arguments":{"newIban":"DK27 5301 0244 5638 21","ibanOnFile":"DK50 0040 0440 1162 43","supplierCountry":"DK"}}}'
The tool result (result.content[0].text), run against production on
2026-09-24 and shortened where it says …:
{
"kind": "payment_change_check",
"data": {
"verdict": "verify_first",
"summary": "The new number is a well-formed account and nothing about it stands out. It is still a change of where money goes: confirm it by phone before updating the record.",
"newAccount": { "status": "valid", "iban": "DK27 5301 0244 5638 21", "country": "Denmark", … },
"accountOnFile": { "status": "valid", "iban": "DK50 0040 0440 1162 43", "country": "Denmark", … },
"flags": [
{ "code": "bank_changed", "severity": "note", "what": "Same country, different bank (identifier 0040 → 5301). Ordinary on its own — companies do change banks — and worth mentioning on the call." }
],
"requiredSteps": [
"Call the supplier on a phone number from your OWN records or a previous, paid invoice — never one from the change request or its e-mail signature.",
"Have them read the new account number back to you; do not read it to them.",
"Have a second person approve the change to the vendor record before any payment runs.",
"Keep the request, the name of the person you spoke to, and the time of the call with the vendor record."
],
"doesNotProve": "Who owns the new account, that it exists or is open, or that the request came from the supplier. No free registry answers those. Only the call-back does.",
…
}
}
Act on verdict:
verdict | What you do |
|---|---|
no_change | The same account, written differently. Go on to step 2. If the request insisted the details were new, that mismatch is worth a call. |
verify_first | Do not pay and do not change the vendor record. Hand the requiredSteps to a person. |
stop | Red flags. Do not pay, hold other payments to this supplier, hand flags and requiredSteps to a person. |
invalid_new_account | The number cannot be an IBAN. Do not pay. Ask the supplier for the number through a channel you already know. |
| No answer (timeout, HTTP error or tool error) | Not a pass. Do not pay or change the vendor record; hand it to a person. |
The call-back is the check. A person calls the supplier on a phone number that does NOT come from the e-mail, the change request or the invoice. You cannot make that call for them, and a well-formed IBAN is not a safe one.
The same call for the stop, no_change and invalid_new_account cases is
in references/examples.md.
Step 2 - the invoice itself: POST /api/invoice/review (free)
Send the invoice as structured data. It checks the structure, recomputes the totals, checks the IBAN on it and applies the Peppol BIS Billing 3.0 rules. No account, no token, nothing charged. Like review_invoice it reads structured data, not a PDF or a scan: turn those into fields first.
curl -s https://jithox.com/api/invoice/review \
-A 'pay-invoices-safely/1.0' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{"invoice":{"invoiceNumber":"2026-0917","issueDate":"2026-09-17","dueDate":"2026-10-17","currency":"DKK","buyerReference":"PO-4471","supplier":{"name":"Nordlys Tryk ApS","countryCode":"DK","vatId":"DK31500060","endpointId":"5790000000012","endpointScheme":"0088"},"customer":{"name":"Havn Logistik A/S","countryCode":"DK","vatId":"DK31500044","endpointId":"5790000000029","endpointScheme":"0088"},"lines":[{"description":"Printing, September","quantity":1,"unitPrice":8000,"vatPercent":25}],"totalWithoutVat":8000,"totalVat":2000,"totalWithVat":10000,"payment":{"iban":"DK27 5301 0244 5638 21"}}}'
Run against production on 2026-09-24, shortened:
{
"review": {
"checks": [
{ "id": "structure", "status": "pass", … },
{ "id": "arithmetic", "status": "pass", "detail": "Line totals and VAT match the amounts on the invoice.", … },
{ "id": "payment_details", "status": "pass", "detail": "DK27 5301 0244 5638 21 is a structurally valid Denmark IBAN. This does not say the account exists or who it belongs to.", … },
{ "id": "supplier_vat", "status": "skipped", "detail": "The VIES register was not queried: this run had no authorised workspace for it.", … },
{ "id": "customer_vat", "status": "skipped", … },
{ "id": "peppol", "status": "pass", … }
],
"findings": [],
"readyToSend": true,
"hasUnknowns": true,
…
},
"verified": false,
"billing": { "charged": false, "reason": "Signed out: only the free local checks ran, and nothing was charged.", … }
}
How to read it:
- Without an account the two VAT checks are
skipped: the VIES register was not asked. That is whyhasUnknownsis true. Say so to the person; never report the VAT numbers as checked. - Every entry in
findingshas aseverityand afix. Ablockermeans the invoice is wrong (for example, a total that does not add up): do not pay it, ask the supplier for a corrected invoice. An example is in references/examples.md. payment_detailsonly says the IBAN is well formed. Whether it is the supplier's account is step 1, not this check.
Step 3 - VAT numbers from the EU register: review_invoice (paid)
Run this only after step 2. It repeats the structured invoice checks and adds supplier and customer VAT-number checks against the EU VIES register. Its live input schema reads the payment and declared invoice totals. It needs a token:
- A person (not the agent) creates a Jithox connection at https://jithox.com/mcp/account#connection.
- The agent exchanges the connection's id and secret for a token at /api/oauth/token:
curl -s https://jithox.com/api/oauth/token \
-A 'pay-invoices-safely/1.0' \
-H 'Accept: application/json' \
-d grant_type=client_credentials \
-d "client_id=$JITHOX_CLIENT_ID" \
-d "client_secret=$JITHOX_CLIENT_SECRET"
Put the access token from the answer in JITHOX_TOKEN. An expired or refused
token gets the same payment_required answer as no token. Fetch one new
token and try once more.
- Call the tool with the token (the invoice fields go straight into
arguments):
curl -s https://jithox.com/api/mcp \
-A 'pay-invoices-safely/1.0' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $JITHOX_TOKEN" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"review_invoice","arguments":{"invoiceNumber":"2026-0917","issueDate":"2026-09-17","dueDate":"2026-10-17","currency":"DKK","buyerReference":"PO-4471","supplier":{"name":"Nordlys Tryk ApS","countryCode":"DK","vatId":"DK31500060","endpointId":"5790000000012","endpointScheme":"0088"},"customer":{"name":"Havn Logistik A/S","countryCode":"DK","vatId":"DK31500044","endpointId":"5790000000029","endpointScheme":"0088"},"lines":[{"description":"Printing, September","quantity":1,"unitPrice":8000,"vatPercent":25}],"totalWithoutVat":8000,"totalVat":2000,"totalWithVat":10000,"payment":{"iban":"DK27 5301 0244 5638 21"}}}}'
The price is not in this file, because prices change: read the pricing
field of review_invoice in https://jithox.com/mcp.json. Without a token the
tool answers payment_required, runs nothing and names humanUrl and
tokenUrl.
With a token, run against production on 2026-09-24, shortened:
{
"kind": "invoice_review",
"data": {
"checks": [
…
{ "id": "supplier_vat", "status": "unknown", "source": "eu_vies", "detail": "The VIES register did not answer." },
{ "id": "customer_vat", "status": "fail", "source": "eu_vies", "detail": "VIES reports this number as not registered." },
…
],
"findings": [
{ "code": "supplier_vat_unverified", "severity": "warning", "what": "We could not reach a verdict on VAT number DK31500060. The VIES register did not answer.", "fix": "Review again later. Treat this as unchecked — it is not evidence that the number is good or bad." },
{ "code": "customer_vat_not_registered", "severity": "blocker", "what": "VIES does not know VAT number DK31500044.", "fix": "Check the number with the customer — a typo, a closed registration or a non-EU number would all look like this." }
],
"readyToSend": false,
"hasUnknowns": true,
…
}
}
unknown is not a pass: the register did not answer, so the number is
unchecked. A fail on the supplier's VAT number is a reason to hold the
payment and ask. The VAT numbers in these examples are made up, so the
register does not know them.
Step 4 - decide, and say what was not checked
Jithox never pays and never signs a payment, and none of these calls asks for a bank login or a signing key. You prepare; a person approves the payment.
Hold the payment and hand it to a person when any of these is true:
- step 1 gave a
verdictother thanno_change; - step 2 has not returned an answer with no finding of severity
blocker; - step 3 has a finding with severity
blocker; - a VAT check you needed is
skipped,unknownorfail. - a check gave no answer: a timeout, an HTTP error, a JSON-RPC error or a tool error is not a pass. Do not pay; hand it to a person.
Even when everything passes, a clean answer is not a guarantee. Put the
doesNotProve text of step 1 and the detail of every check that is not
pass into your note, word for word. A short note:
Invoice 2026-0917 from Nordlys Tryk ApS, DKK 10000.00 to DK27 5301 0244 5638 21.
Bank account: verify_first (bank changed 0040 -> 5301). NOT paid.
Call the supplier on the number in our own records; have them read the account back.
Not proven: who owns the new account, that it exists or is open, or that the request came from the supplier.
Invoice: structure, totals, IBAN format and Peppol rules pass.
VAT: not checked (skipped, no account).
Needs: call-back + second approver before payment.