Skip to main content

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.

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

EndpointScope
GET trial-balanceaccounting:read OR migration:read
GET ar-agingaccounting:read OR migration:read
GET ap-agingaccounting:read OR migration:read
GET inventory-valuationaccounting:read OR migration:read
GET cash-balanceaccounting:read OR migration:read
GET countsaccounting:read OR migration:read
POST comparemigration: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

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:
{
  "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

curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/ar-aging' \
  -H "Authorization: Bearer ark_live_..."
Response:
{
  "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.
curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/ap-aging' \
  -H "Authorization: Bearer ark_live_..."

Inventory valuation

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:
{
  "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

curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/cash-balance' \
  -H "Authorization: Bearer ark_live_..."
Response:
{
  "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

curl 'https://api.arcuserp.com/v1/entities/{ENTITY_ID}/reconciliation/counts?external_source=versa' \
  -H "Authorization: Bearer ark_live_..."
Response:
{
  "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.
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:
{
  "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:
{
  "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.