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.

What this guide covers

This guide walks through two closely related tasks:
  1. Manual journal entries — posting adjusting, accrual, or correcting entries with balanced lines
  2. Period close — locking a period after all entries are in so no accidental postings slip through
Most GL entries are posted automatically by Arcus when business events occur (order fulfillment, payments, receipts). Manual JEs are for adjusting entries, accruals, depreciation, and corrections that do not originate from a transaction.

Prerequisites

  • API key with scope accounting:write (create JEs)
  • API key with scope accounting:approve (approve JEs above threshold)
  • API key with scope accounting:close_period (close periods)
  • Valid account_id values for postable GL accounts (use GET /v1/gl-accounts?postable_only=true)
  • An open accounting period that covers your entry_date

The two non-negotiable rules

Before writing any JE code, understand the two rules Arcus enforces at every write path:

1. Debits must equal credits

Every journal entry must balance: the sum of all debit amounts must equal the sum of all credit amounts. An off-by-one-cent imbalance is a validation failure.
Rule: SUM(debit) == SUM(credit)
If the amounts do not balance, the API returns 422 with code: gl_imbalance and includes the exact difference:
{
  "error": "gl_imbalance",
  "code": "gl_imbalance",
  "type": "validation_error",
  "hint": "GL posting imbalance [MANUAL]: debits=500.00 credits=300.00 diff=200.0000"
}

2. Post to leaf accounts only

You cannot post to header (rollup) accounts like “1000 Cash and Bank Accounts” or “1400 Inventory.” You must post to a leaf account like “1011 Operating Checking” or “1410 Finished Goods Inventory.” Use GET /v1/gl-accounts?postable_only=true to get the list of valid accounts for your entity.
{
  "error": "account_is_header",
  "code": "account_is_header",
  "type": "validation_error",
  "hint": "GL account 1000 is a header account and cannot be posted to directly.",
  "param": "lines[0].account_id"
}

Posting a manual journal entry

Example: prepaid insurance amortization

A 1,200annualinsurancepolicypaidupfront.Eachmonth,1,200 annual insurance policy paid upfront. Each month, 100 moves from Prepaid Expenses to Insurance Expense.
curl -s -X POST "https://api.arcuserp.com/v1/journal-entries" \
  -H "Authorization: Bearer $ARCUS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: je-insurance-amort-2026-05" \
  -d '{
    "entry_date": "2026-05-31",
    "description": "Prepaid insurance amortization - May 2026",
    "memo": "Acme General Liability Policy #GL-2026-001",
    "lines": [
      {
        "account_id": "gl_insurance_expense_uuid",
        "debit": 100.00,
        "description": "Insurance expense May"
      },
      {
        "account_id": "gl_prepaid_expenses_uuid",
        "credit": 100.00,
        "description": "Release prepaid"
      }
    ]
  }'

Response shape

{
  "data": {
    "id": "je_uuid_abc",
    "je_number": "JE-00128",
    "entry_date": "2026-05-31",
    "description": "Prepaid insurance amortization - May 2026",
    "status": "posted",
    "total_debits": 100.00,
    "total_credits": 100.00,
    "source_type": "MANUAL",
    "lines": [
      {
        "id": "jel_uuid_1",
        "account_id": "gl_insurance_expense_uuid",
        "account_number": "6100",
        "account_name": "Insurance Expense",
        "debit": 100.00,
        "credit": 0.00,
        "description": "Insurance expense May"
      },
      {
        "id": "jel_uuid_2",
        "account_id": "gl_prepaid_expenses_uuid",
        "account_number": "1310",
        "account_name": "Prepaid Expenses",
        "debit": 0.00,
        "credit": 100.00,
        "description": "Release prepaid"
      }
    ]
  }
}

Approval workflow

If your entity has approval thresholds configured, journal entries above the low threshold land in pending_approval status. GL does not post until the entry is approved. Check the status:
curl "https://api.arcuserp.com/v1/journal-entries/$JE_ID" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
# Check: data.status
Approve (requires accounting:approve scope; approver must be a different user than the creator):
curl -s -X POST "https://api.arcuserp.com/v1/journal-entries/$JE_ID/approve" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
The status transitions to posted and the GL lines are active.

Reversing a posted entry

GAAP requires an audit trail: you cannot modify or delete a posted journal entry. To undo one, create a reversing entry:
curl -s -X POST "https://api.arcuserp.com/v1/journal-entries/$JE_ID/reverse" \
  -H "Authorization: Bearer $ARCUS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reversal_date": "2026-06-01",
    "description": "Reverse JE-00128 - incorrect account"
  }'
The reversal is a new JE with all debits and credits swapped. The original entry is unchanged and both remain visible in the account ledger. The response includes is_reversing: true and reversal_of_id pointing to the original.

Idempotency

Use a stable Idempotency-Key tied to your source record. For recurring entries, use a key that includes the period:
je-monthly-rent-2026-05
je-depreciation-equipment-2026-05
je-insurance-amort-2026-05
Replaying the same key within 24 hours returns the original response without creating a duplicate entry.

Closing an accounting period

When all entries for a period are in, close the period to prevent accidental future postings.

Step 1: Run the pre-close checklist

curl "https://api.arcuserp.com/v1/periods/$PERIOD_ID/close-checklist" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
The checklist reports blocking issues that should be resolved before close:
{
  "data": {
    "period_id": "per_uuid",
    "period_name": "May 2026",
    "blocking_issues": [],
    "warnings": [
      {
        "type": "open_vendor_bills",
        "count": 2,
        "message": "2 vendor bills are open and unpaid."
      }
    ],
    "clearing_account_drains": [
      {
        "account": "stripe_clearing",
        "balance": 0.00,
        "status": "ok"
      }
    ],
    "is_closeable": true
  }
}
is_closeable: true means no blocking issues. Warnings are advisory. Common blocking issues:
IssueWhat to do
Unposted journal entries in the periodPost or delete the draft entries
Pending-approval journal entriesApprove or reject them
Clearing account blocking_issueInvestigate unexplained drift in a marketplace clearing account

Step 2: Request close (two-person flow)

For separation of duties, use the two-step close:
# Step 2a: Request (any accounting:close_period user)
curl -s -X POST "https://api.arcuserp.com/v1/periods/$PERIOD_ID/request-close" \
  -H "Authorization: Bearer $ARCUS_API_KEY"

# Step 2b: Approve (a different user with accounting:close_period + accounting:approve)
curl -s -X POST "https://api.arcuserp.com/v1/periods/$PERIOD_ID/approve-close" \
  -H "Authorization: Bearer $SECOND_USER_API_KEY"

Step 2 (alternative): Force close

Owners and admins can close immediately without a second approver:
curl -s -X POST "https://api.arcuserp.com/v1/periods/$PERIOD_ID/close" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
The period transitions to closed status. Any attempt to create a journal entry with an entry_date inside a closed period returns:
{
  "error": "period_closed",
  "code": "period_closed",
  "type": "validation_error",
  "hint": "Accounting period May 2026 is closed. Use a date in an open period or reopen this period."
}

Reopening a period

If you need to post a correcting entry after close:
curl -s -X POST "https://api.arcuserp.com/v1/periods/$PERIOD_ID/reopen" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
The period returns to open status. Reopen + correcting entry + re-close is the standard GAAP path for post-close adjustments. All changes are audit-logged.

Year-end closing entries

At fiscal year-end, income statement accounts (revenue, COGS, expenses) are zeroed out and their net balance rolls into retained earnings. Create these as standard manual JEs:
# Close revenue into retained earnings
curl -s -X POST "https://api.arcuserp.com/v1/journal-entries" \
  -H "Authorization: Bearer $ARCUS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: year-end-close-revenue-FY2026" \
  -d '{
    "entry_date": "2026-12-31",
    "description": "FY2026 year-end close - revenue to retained earnings",
    "lines": [
      { "account_id": "gl_sales_revenue_uuid",   "debit": 485000.00 },
      { "account_id": "gl_retained_earnings_uuid", "credit": 485000.00 }
    ]
  }'
Repeat for COGS and expense accounts (the net becomes your net income for the year). After posting all closing entries, the income statement accounts should show zero balances and only balance sheet accounts should have non-zero balances.

Running reports to verify

Before closing, confirm the trial balance is in balance:
curl "https://api.arcuserp.com/v1/reports/trial-balance?period_id=$PERIOD_ID" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
# Check: data.is_balanced == true, data.total_debits == data.total_credits
Check AR and AP subledger reconciliation:
curl "https://api.arcuserp.com/v1/reports/subledger-reconciliation?period_id=$PERIOD_ID" \
  -H "Authorization: Bearer $ARCUS_API_KEY"
# Check: ar_control_balance == ar_subledger_balance
#        ap_control_balance == ap_subledger_balance

Common errors

CodeHTTPMeaning
gl_imbalance422Debit sum does not equal credit sum
account_is_header422Line targets a rollup account; use a leaf account
account_inactive422Account is deactivated; reactivate or use a different account
account_not_found422account_id does not exist for this entity
period_closed422entry_date falls in a closed period
self_approval_forbidden422The approver is the same user as the creator (SoD violation)
period_not_closeable422Checklist has blocking issues; resolve them first
insufficient_scope403Missing accounting:write, accounting:approve, or accounting:close_period

Next steps