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

# Reconciliation API

> Penny-precision read endpoints for certifying a migration before cutover: trial balance, AR aging, AP aging, inventory valuation, cash balances, resource counts, and external comparison.

The reconciliation cluster is the proof-of-correctness surface. Before flipping the cutover switch, you fire each endpoint against your Arcus entity and tie the numbers to the legacy ERP to the penny. After cutover, the same endpoints give you a permanent audit trail.

All endpoints are read-only except `POST .../compare`, which is also non-destructive (it returns deltas, no writes).

## Required scope

| Endpoint                  | Scope                                           |
| ------------------------- | ----------------------------------------------- |
| `GET trial-balance`       | `accounting:read` OR `migration:read`           |
| `GET ar-aging`            | `accounting:read` OR `migration:read`           |
| `GET ap-aging`            | `accounting:read` OR `migration:read`           |
| `GET inventory-valuation` | `accounting:read` OR `migration:read`           |
| `GET cash-balance`        | `accounting:read` OR `migration:read`           |
| `GET counts`              | `accounting:read` OR `migration:read`           |
| `POST compare`            | `migration:read` (narrower; migration-specific) |

The dual scope on the GETs means a regular operator with `accounting:read` can run reconciliation reports during normal operations; a migration-specific key with `migration:read` (and nothing else) can do the same during a cutover window.

## Trial balance

```bash theme={null}
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/trial-balance' \
  -H "Authorization: Bearer ark_live_..."
```

Optional query parameters:

* `as_of_date=YYYY-MM-DD` (default: today). Snapshot date for the trial balance.
* `external_source=versa` (default: all). Restrict to journal entries imported from a single source. Use this to verify that ONLY the migrated GL ties; subsequent native postings are excluded.

Response:

```json theme={null}
{
  "object": "trial_balance",
  "entity_id": "65dcd234-...",
  "as_of_date": "2026-07-12",
  "external_source": null,
  "rows": [
    { "account_id": "...", "account_number": "1000", "account_name": "Cash - Operating",
      "debit_total":  125000.00, "credit_total": 0.00, "balance": 125000.00 },
    { "account_id": "...", "account_number": "1200", "account_name": "Accounts Receivable",
      "debit_total":  127340.55, "credit_total": 0.00, "balance": 127340.55 },
    // ...
  ],
  "totals": { "debit_total": 1234567.89, "credit_total": 1234567.89, "balance": 0.00 }
}
```

A balanced trial balance has `totals.balance` equal to `0.00`. Anything else is a data error and unfreeze will block.

## AR aging

```bash theme={null}
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/ar-aging' \
  -H "Authorization: Bearer ark_live_..."
```

Response:

```json theme={null}
{
  "object": "ar_aging",
  "entity_id": "65dcd234-...",
  "as_of_date": "2026-07-12",
  "buckets": ["current","1_30","31_60","61_90","over_90"],
  "rows": [
    {
      "account_id": "...",
      "display_name": "Acme Corp",
      "external_id": "versa-customer-12345",
      "current": 1200.00, "b1_30": 0.00, "b31_60": 540.00,
      "b61_90": 0.00, "over_90": 0.00, "total": 1740.00
    },
    // ...
  ],
  "totals": {
    "current": 50000.00, "b1_30": 22000.00, "b31_60": 15000.00,
    "b61_90": 8000.00, "over_90": 32340.55, "total": 127340.55
  }
}
```

Sum of `totals.total` must equal the AR control-account balance in the trial balance. That tie is the primary AR proof.

## AP aging

Same shape as AR aging; vendor-keyed instead of customer-keyed.

```bash theme={null}
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/ap-aging' \
  -H "Authorization: Bearer ark_live_..."
```

## Inventory valuation

```bash theme={null}
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/inventory-valuation?as_of_date=2026-07-12&method=fifo' \
  -H "Authorization: Bearer ark_live_..."
```

Query parameters:

* `as_of_date=YYYY-MM-DD` (default: today)
* `method` (default: entity's costing method) - `fifo`, `avg`, or `lot_cost`
* `location_id` (optional) - filter to one warehouse

Response:

```json theme={null}
{
  "object": "inventory_valuation",
  "as_of_date": "2026-07-12",
  "method": "fifo",
  "by_location": [
    { "location_id": "...", "name": "Main Warehouse", "total_value": 187500.42 }
  ],
  "by_product": [
    { "product_id": "...", "sku": "WIDGET-100", "on_hand": 273, "unit_cost": 1.0092, "total_value": 275.51 }
  ],
  "total_value": 187500.42
}
```

`total_value` must equal the Inventory control-account balance in the trial balance.

## Cash balance

```bash theme={null}
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/cash-balance' \
  -H "Authorization: Bearer ark_live_..."
```

Response:

```json theme={null}
{
  "object": "cash_balance",
  "as_of_date": "2026-07-12",
  "accounts": [
    {
      "account_id": "...",
      "account_number": "1000",
      "name": "Cash - Operating",
      "gl_balance": 125000.00,
      "bank_balance": 125000.00,
      "uncleared_count": 0,
      "uncleared_amount": 0.00
    }
  ],
  "total_gl": 125000.00,
  "total_bank": 125000.00
}
```

`gl_balance` equals the trial-balance value for that account. `bank_balance` is the running bank-reconciliation balance. They tie when all transactions are cleared; uncleared rows are flagged for review.

## Resource counts

```bash theme={null}
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/counts?external_source=versa' \
  -H "Authorization: Bearer ark_live_..."
```

Response:

```json theme={null}
{
  "object": "resource_counts",
  "as_of": "2026-07-12T08:34:41.506Z",
  "external_source": "versa",
  "counts": {
    "accounts": 140071,
    "account_contacts": 142533,
    "account_addresses": 138991,
    "products": 1099,
    "orders": 265200,
    "order_items": 887423,
    "order_payments": 132784,
    "vendor_bills": 8791,
    "vendor_bill_items": 31273,
    "ap_payments": 7416,
    "journal_entries": 716569,
    "journal_entry_lines": 2641793
  }
}
```

`external_source` filters to records imported from that source via the bulk endpoint. Omit it to see total counts across all sources.

## Compare external records (the penny-match endpoint)

This is the workhorse. Submit external records (from the legacy ERP) and the API returns the per-record verdict against your Arcus data.

```bash theme={null}
curl -X POST 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/compare' \
  -H "Authorization: Bearer ark_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_source": "versa",
    "external_resource": "ar_aging_by_customer",
    "tolerance_cents": 1,
    "external_records": [
      { "external_id": "versa-customer-12345", "current": 1200.00, "b1_30": 0.00, "b31_60": 540.00, "b61_90": 0.00, "over_90": 0.00, "total": 1740.00 },
      { "external_id": "versa-customer-67890", "current": 0.00,    "b1_30": 320.00, "b31_60": 0.00, "b61_90": 0.00, "over_90": 100.00, "total": 420.00 }
    ]
  }'
```

Supported `external_resource` values:

* `ar_aging_by_customer` - per-customer AR aging buckets
* `ap_aging_by_vendor` - per-vendor AP aging buckets
* `trial_balance_by_account` - per-GL-account debit/credit/balance
* `resource_counts` - object-level counts

`tolerance_cents` is the per-record delta you accept (default: 1 cent). Set to 0 for exact ties.

Response:

```json theme={null}
{
  "object": "reconciliation_compare_result",
  "external_source": "versa",
  "external_resource": "ar_aging_by_customer",
  "tolerance_cents": 1,
  "total_records": 2,
  "matched": 1,
  "mismatched": 1,
  "missing_in_arcus": 0,
  "extra_in_arcus": 0,
  "mismatches": [
    {
      "external_id": "versa-customer-67890",
      "arcus_id": "...",
      "field_deltas": {
        "b1_30":  { "external": 320.00, "arcus": 318.50, "delta_cents": 150 }
      }
    }
  ],
  "missing_in_arcus_records": [],
  "extra_in_arcus_records": []
}
```

`matched` records balance to the penny. `mismatched` records have at least one field outside `tolerance_cents`. `missing_in_arcus` records exist in the external file but not in Arcus. `extra_in_arcus` records exist in Arcus but not in the external file.

Hard cap: 5000 records per call. Split larger comparisons into batches.

## Suggested cutover-day workflow

1. **Pre-cutover (week of):** run trial balance + counts daily. Confirm you can reproduce the same totals from the legacy ERP. Spot-check 5 to 10 named accounts via `compare`.
2. **Cutover Friday EOD (T-0):** freeze the entity. Run final reconciliation suite. Snapshot legacy source data alongside the Arcus database snapshot.
3. **Cutover Saturday morning:** run a top-up bulk-import sync for any data that changed between Thursday's full load and Friday EOD freeze. Re-run reconciliation.
4. **Cutover Saturday evening:** swap traffic, then run `POST .../cutover/verify` which fires the full reconciliation suite. This is the gate that unblocks unfreeze.
5. **Cutover Sunday:** smoke-test critical user flows. Re-run individual reconciliation reports as needed.
6. **Cutover Monday go-live:** keep the reconciliation key live for a week so the operator can investigate any reported discrepancies.

## Error envelope

Like the rest of the public API, every 4xx response follows the canonical envelope:

```json theme={null}
{
  "error": "missing_required_field",
  "code": "missing_required_field",
  "type": "validation_error",
  "param": "external_resource",
  "hint": "Provide one of: 'ar_aging_by_customer', 'ap_aging_by_vendor', 'trial_balance_by_account', 'resource_counts'."
}
```

See [Errors](/concepts/errors).

## Related

* [Cutover orchestration](/guides/cutover-orchestration) - the freeze / snapshot / swap / verify / unfreeze sequence that consumes these reads.
* [Migrating Data into Arcus](/guides/migration-from-versa) - the bulk-load step that populates the data these endpoints read.
* [Idempotency](/concepts/idempotency) - request-level idempotency semantics.
