> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudsquid.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Run an audit

> Point an agent at 12–24 months of your own AP history, read-only, and get evidence-backed findings — duplicates, unapplied credits, split invoices, bank-detail changes.

<Note>
  **Read-only, start to finish.** This recipe loads historical exports into a fresh project. Nothing here connects to a production system, and nothing here writes anywhere. The worst outcome is a clean bill of health.
</Note>

## The job

Nobody knows exactly what's leaking out of their AP process, because checking every transaction against every other one was never possible by hand. So it gets sampled, and sampling finds the errors that happen to be in the sample.

This recipe runs the checks across the full population — 12 to 24 months of it — and reports what it finds with the evidence attached. It's the first stage of [audit to prevention](/concepts/audit-to-prevention): prove the checks work on data nobody can dispute before anything runs on live events.

## What you'll build

```
historical exports ──▶ storage tables ──▶ Audit Findings (reconcile table)
                                                 │
                                            one task per check
                                                 │
                                          findings + evidence ──▶ review ──▶ xlsx report
```

One reconcile table, one SOP, and **each check runs as its own task** in that table — all of them sharing the same data access and the same procedure. This is the month-close pattern: the process is one thing, and its steps are tasks rather than separate tables.

## Ingredients

|                     |                                                                                                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Project**         | A fresh one. `AP Audit 2025-2026` — isolated data, isolated grants.                                                     |
| **Storage tables**  | `ap_ledger`, `vendor_master`, `payment_runs`. Optional: `supplier_statements`, `contracts`, plus invoice PDFs in Files. |
| **Reconcile table** | `Audit Findings` — one task per check                                                                                   |
| **Model tier**      | **Powerful**. Audit reasoning is exactly the case for it, and this is a batch job — latency doesn't matter here.        |
| **Integrations**    | None. That's the point of this stage.                                                                                   |
| **Auto Review**     | Off. Every finding gets human eyes in an audit.                                                                         |

**What to export.** Ask whoever runs your ERP for 12–24 months of:

```
ap_ledger:          invoice_id, vendor_id, invoice_number, invoice_date,
                    posting_date, amount_net, amount_gross, currency,
                    po_number, cost_center, entity, payment_status
vendor_master:      vendor_id, vendor_name, tax_id, bank_iban,
                    bank_last_changed, parent_vendor_id, entity
payment_runs:       run_id, run_date, invoice_id, amount_paid, entity
credit_notes:       credit_id, vendor_id, credit_number, amount,
                    issue_date, applied_to_invoice_id, status
contracts (opt.):   vendor_id, article_or_service, agreed_price, unit,
                    valid_from, valid_to
```

Column names don't have to match these — the agent reads your schema. They do have to be legible: `bank_last_changed` works, `dt_fld_09` doesn't.

## The starter SOP

```md theme={null}
# SOP — AP leakage audit (read-only lookback)

Version 1.0 · Owner: Controlling · Last approved by: <name>, <date>

## Purpose
Investigate a historical AP population for recoverable money and control
failures. Report findings with evidence. This is a READ-ONLY audit: never
propose a posting, never modify a record, never contact a vendor.

## Scope
The period loaded into the project's storage tables. State the exact date
range and row count in every finding report so results are reproducible.

## Output format — every finding, without exception
| Field               | Content                                          |
|---------------------|--------------------------------------------------|
| check               | Which check below produced this                  |
| finding             | One sentence: what is wrong                      |
| row_references      | Every source row id involved — never summarize   |
| amount              | Money at stake, with currency                    |
| confidence          | high / medium / low, with the reason             |
| recommended_action  | What a human should do next                      |

A finding without row references is not a finding. If you cannot point at
the rows, report it as an observation and mark confidence low.

## Check 1 — Exact duplicates
Same vendor + same invoice number → duplicate. Same vendor + same amount
+ same invoice date, different invoice number → duplicate.
Evidence required: both invoice_ids, both payment statuses, amount.
Report paid duplicates and unpaid duplicates separately — only the paid
ones are recoverable.

## Check 2 — Near duplicates
Same vendor + same amount, invoice dates within 10 days, different
invoice number. Also flag same amount + same PO across different invoice
numbers.
Evidence required: both rows, the date gap, the PO if present.
Confidence is never high here. Recurring identical charges (rent,
subscriptions, retainers) are normal — check whether the pair repeats
monthly before flagging, and say so.

## Check 3 — Cross-entity duplicates
The same invoice paid by two entities, or by a parent and a subsidiary.
Match on vendor tax_id (NOT vendor_id — the same vendor has different ids
per entity) + invoice number, or tax_id + amount + date window.
Evidence required: both entity codes, both vendor_ids, the shared tax_id.
This check finds money the single-entity checks structurally cannot.

## Check 4 — Unapplied credit notes
Credit notes with status open, or with no applied_to_invoice_id, where the
vendor has since been paid.
Evidence required: credit_id, amount, issue date, age in days, and the
payments made to that vendor after the issue date.
Sort by age. A two-year-old open credit is a different conversation from
a two-week-old one.

## Check 5 — Bank detail changes near a payment run
Any vendor whose bank_iban changed within 30 days before a payment to
that vendor.
Evidence required: vendor_id, change date, payment run date and id, the
amount paid, the days between.
This is a fraud-pattern check, not an error check. Report every hit
regardless of amount, and never conclude fraud — report the pattern and
let a human investigate.

## Check 6 — Split invoices under an approval threshold
Two or more invoices from one vendor, within 7 days, that individually
sit below an approval threshold and together sit above it.
Ask for the thresholds if they are not in the data. Do not assume them.
Evidence required: all invoice_ids, individual amounts, the total, the
threshold it crosses.
Legitimate causes exist (partial deliveries, milestone billing). Report
the pattern, mark confidence medium, and name the plausible innocent
explanation you considered.

## Check 7 — Price vs contract
Only run when `contracts` is loaded. Compare invoiced unit price against
the agreed price valid on the invoice date.
Tolerance: the smaller of 2% or 10.00 per unit.
Evidence required: invoice row, contract row, both prices, the variance
per unit and in total.
Skip lines with no matching contract — say how many you skipped rather
than guessing at a price.

## Rules that apply to every check
- Never guess an amount. If the data does not support a number, say so.
- Never merge two findings to make a bigger one.
- A check that finds nothing is a result. Report it explicitly with the
  row count examined — silence is not evidence of absence.
- If a check cannot run because data is missing, say which column was
  missing rather than skipping quietly.
- Report counts and totals per check, plus the population size, so the
  whole audit reconciles.
```

## Walkthrough

<Steps>
  <Step title="Create the project and load the exports">
    New project. Create each export as a **Storage** table and upload the CSV. If you have invoice PDFs, put them in **Files** — checks 1, 2, and 7 get sharper when the agent can open the document behind a row.
  </Step>

  <Step title="Create the Audit Findings table">
    A **Reconcile** table with **Operating Procedures**: `Run the audit checks defined in the SOP. Read-only.`

    **Output Fields**: `check`, `finding`, `row_references`, `amount`, `currency`, `confidence`, `recommended_action`, `population_examined`. Grant **Data Access** to every storage table you loaded. Model tier **Powerful**.
  </Step>

  <Step title="Add the SOP and sanity-check it against your data">
    Paste the starter SOP into `SOP.md`, then ask in chat:

    > Read SOP.md, then look at the storage tables. Which checks can run against this data as-is, which need a column I haven't loaded, and what's the row count and date range per table?

    It tells you what's runnable before you run anything. Load what's missing, or drop the check.
  </Step>

  <Step title="Run one check first">
    Create a task for Check 1 alone. Duplicates are the easiest to verify by hand, so it's the cheapest way to confirm the agent is reading your schema correctly. Check a few findings against the ERP yourself.
  </Step>

  <Step title="Run the rest">
    One task per check. They share the SOP and the data access, so nothing is configured twice.
  </Step>
</Steps>

### What good looks like

**A real finding, with its evidence:**

> **Check 3 — Cross-entity duplicates.** Invoice `MV-4471` from Marloe Verpackung (tax ID DE••1882) was paid twice: once by entity DE01 as vendor 21044 on 2025-11-14 (payment run PR-2211, 8,940.00 EUR) and once by entity AT02 as vendor 30871 on 2025-12-02 (PR-2249, 8,940.00 EUR). Same invoice number, same amount, same tax ID, different vendor records — which is why the per-entity duplicate check did not catch it. Rows: ledger 118422, 121907; payment runs PR-2211, PR-2249. Amount at stake 8,940.00 EUR. Confidence: high. Recommended action: raise a recovery claim against Marloe Verpackung for the AT02 payment.

Every number points at a row. A controller can verify this in the ERP in two minutes.

**A check that correctly finds nothing:**

> **Check 5 — Bank detail changes near a payment run.** Examined 1,247 vendor records against 38 payment runs in the period. Nine vendors had a bank\_iban change during the window; none fell within 30 days before a payment to that vendor — closest was 63 days (vendor 20115, change 2025-08-03, next payment 2025-10-05). No findings. Population examined: 1,247 vendors, 38 runs, 14,902 ledger rows.

This is a result, not a blank. It says what was checked, how much of it, and what the near-miss was — which is what makes "nothing found" believable.

**A finding the agent correctly refuses to call:**

> **Check 6 — Split invoices.** Three invoices from Kestrel Instrumente within 5 days (4,800.00, 4,950.00, 4,200.00 EUR; total 13,950.00). No approval thresholds are present in the loaded data, so I cannot say whether this total crosses one — the pattern is reported, the conclusion is not. The innocent explanation I considered: all three reference PO 4500219 with different delivery notes, which is consistent with a phased delivery. Confidence: low. Recommended action: confirm your approval threshold, then check whether phased delivery against one PO is expected for this vendor.

The agent did the arithmetic and then declined to allege something the data doesn't support.

## The findings report

When the checks are done, ask for the deliverable:

> Produce an xlsx of the audit: a summary sheet with each check, rows examined, findings, and total amount at stake; then one sheet per check with the full findings and their row references.

It writes the file into the project's **Files**. That's the artifact you take to the meeting — and because every row reference is in it, it survives being questioned.

## Iterating

Your first run will find things that aren't findings. That's normal, and it's fixed in the SOP, not by hand:

> Check 2 is flagging our monthly software subscriptions as near duplicates. Add a rule: identical amounts recurring at a monthly cadence from the same vendor are expected, not near duplicates — exclude them and report the count excluded.

Note the last clause. Excluding something silently is how an audit loses credibility; excluding it and saying how many were excluded is how it keeps it.

## What to do with the findings

The audit is stage one. The checks that found money are the checks worth running continuously — before payment rather than after it:

<CardGroup cols={2}>
  <Card title="From audit to prevention" icon="shield-check" href="/concepts/audit-to-prevention">
    The three-stage path: audit, recover, prevent. Read this next.
  </Card>

  <Card title="Vendor invoice audit" icon="receipt" href="/cookbooks/invoice-audit">
    The continuous sibling of this lookback — the same checks, running on invoices as they arrive.
  </Card>

  <Card title="Integrations" icon="plug" href="/concepts/integrations">
    What turns a batch run into a live trigger.
  </Card>

  <Card title="Governance" icon="shield-halved" href="/concepts/governance">
    Grants, roles, and the audit trail behind every finding.
  </Card>
</CardGroup>

<Note>
  All vendor names, tax IDs, invoice numbers, amounts, and row ids in this cookbook are invented.
</Note>
