Company Accounting endpoints use a company ID and accounting grants. Project Financials endpoints use a project ID and project permissions; access to one does not automatically grant access to the other.
The construction ledger, as an API.
Create and post journal entries with the same checks the app runs, close and reopen periods, and read the trial balance and GL detail by project and cost code. Every call runs as the token's user, with their accounting permissions, and every posting is audited.
Scope and coverage
The company-scoped Accounting app is in preview. It exposes ledger, AP, AR, banking, payroll, fixed asset, report and import workflows: 329 operations on 247 paths. Historical AR reconciliation, WIP posting and ERP sync are not available yet. Project Financials APIs are project-scoped and use separate permissions. Request and response fields for each operation are in the Accounting REST reference; see versioning for compatibility.
Every accounting endpoint, relative to the base path:
General ledger, periods and setup (96)
- List GL accounts
- Create a GL account
- Show a GL account
- Update a GL account
- Apply the construction chart of accounts
- Chart of accounts tree
- ACH fraud-monitoring evidence
- ACH returns and NOCs
- Import an ACH return/NOC file
- Resolve a manual NOC
- Apply NOCs
- Generate a prenote file
- Post an allocation run
- Reverse an allocation run
- List approval delegations
- Create an approval delegation
- Revoke an approval delegation
- List approval rules
- Create an approval rule
- Deactivate an approval rule
- Approvals inbox
- Self-approved documents
- Download an attachment
- List GL budgets
- Import a GL budget from CSV
- Show a GL budget with its lines
- Delete a GL budget version
- Post the CECL allowance adjustment
- Post the current-portion reclass
- List debt and lease schedules
- Create a debt or lease schedule
- Deactivate a debt or lease schedule
- List legal entities
- Create a legal entity
- Show a legal entity
- Update a legal entity
- Generate a fiscal year
- Post the year-end close
- Reverse the year-end close
- General ledger detail
- List accounting access
- Set a member's accounting access
- List job cost entries
- Get a job tax profile
- Set a job tax profile
- List journal entries
- Create a draft journal entry
- Show a journal entry
- Update a draft journal entry
- Delete a draft journal entry
- Approve a journal entry
- List attachments on a journal entry
- Attach a file to a journal entry
- Post a journal entry
- Reject a journal entry (return to the preparer)
- Reverse a posted journal entry
- Submit a journal entry for approval
- List leases
- Add a lease
- Show a lease
- Void a lease
- Lease disclosures
- List lease runs
- Preview or post a lease run
- Show a lease run
- Reverse a lease run
- List party profiles
- Create a party profile
- Show a party profile
- Update a party profile
- Approve or reject a bank/TIN change
- List fiscal periods
- Show a fiscal period
- Close or soft-close a period
- Eliminate intercompany at a period end
- Reopen a period
- Rebuild period balance snapshots
- Rotate vendor and bank PII encryption
- List recurring journals
- Create a recurring journal
- Show a recurring journal
- Update a recurring journal
- Delete a recurring journal
- Approve a recurring journal
- Show accounting settings
- Update accounting settings
- List tax codes
- Create a tax code
- Update a tax code
- List customer tax exemption certificates
- Record a customer tax exemption certificate
- Change a certificate expiry
- List payment terms
- Create a payment term
- Update a payment term
- Trial balance
Accounts payable (65)
- AP aging as of a date
- List CP2100 B notices
- Record a CP2100 B notice
- Mark a B notice letter sent or record the cure
- B notice letter
- List bills and vendor credits
- Create a draft bill or vendor credit
- Show a bill
- Edit a draft or pending bill
- Delete a draft bill
- Apply a vendor credit to a bill
- Approve a bill
- List attachments on a bill
- Attach a file to a bill
- Override a hold
- Reject a bill (return to the preparer)
- Release retainage payable
- Submit a bill for approval
- Undo a vendor credit application
- Void a posted bill
- Draft a bill from a requisition or direct cost
- Reprint a check under a new number
- List compliance requirements
- Require a compliance document
- Drop a compliance requirement
- 1099 review
- Export 1099s as IRIS Taxpayer Portal CSV files
- Export 1099s as an IRS Pub 1220 file
- Form 945 data
- EFTPS 945 deposits
- List payment runs
- Create a draft payment run
- Show a payment run
- Delete a draft run
- Check print data
- ACH (NACHA) file
- Re-send the ACH (NACHA) file
- Positive pay file (CSV)
- Reject a payment run release
- Release (approve) and post a payment run
- Payment selection
- List payments
- Create one manual payment
- Show a payment
- Void a payment
- List PO receipts
- Receive against a purchase order
- Void a PO receipt
- Void a retainage release
- AP settings
- Update AP settings
- AP subledger tie-out
- Export an IRS bulk TIN Matching request file
- Import IRS bulk TIN Matching results
- List AP vendors
- Show an AP vendor
- List attachments on a vendor
- Attach a file to a vendor
- Record a compliance document
- Lift or reinstate a compliance record
- List attachments on a vendor compliance record (COI, W-9, license)
- Attach a file to a vendor compliance record (COI, W-9, license)
- Record a lien waiver received
- List attachments on a lien waiver
- Attach a file to a lien waiver
Accounts receivable (32)
- List AR customers
- Customer open balances net of posted receipts and reversals; paginated invoice history
- Latest 100 collection notes for the customer in permitted entities.
- Add a collection note (optional invoice and promise to pay). arEnter.
- Customer statement: open posted invoices, unapplied cash and balance due, with the billing contact.
- Deposit posted receipts held in UNDEPOSITED_FUNDS to a bank account as one journal (Dr bank / Cr undeposited funds). arApprove; idempotent by request_key.
- List scoped invoices (default 100, maximum 100 per page)
- Create a draft; a retry with the same request key and payload returns the original invoice
- Get scoped invoice
- Replace current draft revision; all editors are excluded from approval
- List attachments on a AR invoice
- Attach a file to a AR invoice
- Flag or clear a collections dispute on a posted invoice. Disputed invoices age in the Disputed column. arEnter.
- Invoice PDF
- Append a source-owned reversal. Independent arApprove required. Identical retries return the original reversal.
- Send an invoice to the billing contact
- Submit, return to draft, independently approve, or post an invoice
- Draft an AR invoice from a non-progress billing
- Bill a certified owner pay app in AR (once per pay app)
- G702/G703 PDF of a certified owner pay app
- Set a prime contract's billing method
- Get a prime contract's closeout documents
- Set a prime contract's closeout documents
- Receipt history, 50 per page
- Record a fully allocated draft; matching request-key retries return the original receipt
- Receipt detail and immutable applications
- Apply a posted receipt's unapplied cash to open invoice lines, or transfer its allocations between lines. arApprove; idempotent by request_key.
- List attachments on a AR receipt
- Attach a file to a AR receipt
- Append a source-owned reversal. Independent arApprove required. Identical retries return the original reversal.
- Independently approve or post; balances are revalidated under the company lock
- Open original AR distributions for one customer/entity; pages over open invoices only, oldest due first
Fixed assets and equipment (25)
- List fixed assets
- Add a fixed asset
- Show a fixed asset
- Update a fixed asset
- Post an asset adjustment
- Dispose of a fixed asset
- Record a UOP meter reading
- Undo a disposal
- Void an asset added in error
- Capitalize an AP bill line
- List asset classes
- Add an asset class
- List depreciation runs
- Preview or post a depreciation run
- Show a depreciation run
- Reverse a depreciation run
- Post approved equipment logs
- List equipment internal rates
- Add an equipment internal rate
- Equipment recovery vs cost
- List equipment usage charges
- Charge equipment usage to a job
- Void equipment usage
- Equipment utilization
- Import the fixed asset register
Banking (42)
- List bank and card accounts
- Create a bank account
- Show a bank account
- Update a bank account
- Approve or reject a pending bank number change
- Matching inbox
- Accept all High matches
- Outstanding items
- Generate a positive pay file
- List positive pay batches
- Regenerate a positive pay batch
- List reconciliations
- Start a reconciliation
- Account register
- Reveal bank numbers
- List statement imports
- Import a statement
- Cash forecast
- Cash position
- Show a reconciliation
- Tick lines / change the statement balance
- Cancel an in-progress reconciliation
- Finish a reconciliation
- Review a finished reconciliation
- Undo a reconciliation
- List bank rules
- Create a bank rule
- Update a bank rule
- Delete a bank rule
- Create from a statement line
- Ignore a statement line
- Match a statement line
- Restore an ignored statement line
- Unmatch a statement line
- List cash documents
- Record and post a cash document
- Show a cash document
- Release or approve a pending cash document
- List attachments on a bank transaction
- Attach a file to a bank transaction
- Check print data
- Void a cash document
Imports (10)
- List ERP connections with health and recent sync runs
- Create an ERP connection (manageIntegrations)
- Queue a sync pull (live API connections); it stages coa, vendors and open_ap import runs
- Export posted journals as an ERP import file
- List imports
- Upload CSV and persist validation preview
- Read preview, results and history
- Map, reconcile, approve/apply or reverse an imported entry
- Integration error inbox: sync run errors and blocked or failed import runs
- Get a sync run
Job cost forecast (12)
- Review a tenant/entity-scoped forecast from dated sources
- Save a dated PM cost-to-complete input (jobForecastEdit permission; PM grants only on projects they belong to). With no PM input a cost code's EAC is max(revised budget, actual + remaining commitments) (flag unforecast_budget when the budget is higher); a null cost_to_complete selects actual + remaining commitments explicitly and, like any input, needs approval before WIP reads it. Saving clears any approval; saving on a later date than the carried-forward input adds a new dated input. Frozen forecasts dated on or after the input are superseded (kept, with supersededAt), never deleted; the audit lists their ids. Refused (409) in a soft- or hard-closed job cost period and on or before the latest posted WIP period end for the entity.
- Approve the PM input shown for a cost code on the worksheet date (approveJournal permission; the last editor cannot approve). Only approved inputs may feed WIP. Approving the last pending input for the date freezes the forecast there, superseding (not deleting) an earlier freeze on the same date. Refused (409) in a hard-closed period or on or before the latest posted WIP period end; allowed in a soft-closed period, so a month-end approval freezes at the period end.
- List accessible projects, entities and source assignments
- Assign a project's sources to one entity and base currency (unrestricted manageCoa permission)
- Set a job's WIP contract terms (manageCoa): revenue method (cost_to_cost, or as_invoiced for T&M / cost-plus jobs: revenue equals billings, ASC 606-10-55-18) and/or whether retainage is conditional (WIP presents it as a contract asset, ASC 606-10-45-3). Audited with the reason.
- List WIP snapshots (newest period end and revision first, at most 100).
- Compute the next WIP revision for an entity at an ended period (postJournal and viewLedger). Contract = executed prime SOV + approved prime COs; cost = posted job cost; billed = posted AR; EAC = the forecast frozen at the period end (PM approval or soft close) plus job cost posted since the freeze, else the approved live forecast, unless overridden with a reason. Each job uses its revenue method (cost_to_cost or as_invoiced); pending prime COs are the memo column (pendingChangeOrders) and enter the transaction price only through overrides[].change_orders; retainage receivable comes from AR (retainageContractAsset when the job's retainage is conditional); each row records settings.loss_provision_unit; cash position is AR receipts less AP payments on the job (payroll and other direct cash costs are not included). A job whose inputs fail is a compute_failed exception (the other jobs still compute) and blocks approval. Rows are read without the company posting lock; the insert refuses with 409 if job cost or billings were posted, or job forecast inputs changed, meanwhile. Supports Idempotency-Key.
- Get a WIP snapshot with its job rows; format=csv returns the rows as CSV
- Approve a draft snapshot (approveJournal; the preparer cannot approve). A snapshot with exceptions needs exceptions_note; compute_failed exceptions cannot be acknowledged. Idempotent; a rejected snapshot is refused.
- Post an approved snapshot on journal WIP at the period end with autoReverseDate the next day; idempotent on (snapshot, revision). Supersedes an earlier posted revision for the period.
- Reject a draft or approved snapshot (approveJournal; terminal, compute a new revision), or with return_to_draft send an approved one back to draft (approval and acknowledgment cleared). Audited with the reason.
Payroll (44)
- Post the month-end payroll accrual
- Post the standard-rate burden true-up
- Certified payroll (WH-347)
- Reissue a printed paper check under a new number
- Void one check of a posted run
- List payroll codes
- Create a code
- List employees
- Create an employee
- Show an employee
- Update an employee
- Add a deduction, match or garnishment
- End a deduction
- Reveal an employee SSN and bank numbers
- List payroll employer entities
- Set an employer entity's payroll identity
- Mark a payroll return period filed
- Request or approve a provisional FUTA credit reduction override
- Enter a manual (handwritten) check
- List pay groups
- Create a pay group
- Update a pay group
- Rotate payroll PII encryption
- List pay-rate changes
- Approve or reject a pay-rate change
- List payroll liability remittances
- Remit a payroll liability
- Release or reject a pending bank remittance
- Payroll tax and remittance data
- List pay runs
- Create a pay run
- Show a pay run
- Pay run outputs
- Pay run workflow step
- Show payroll setup
- Update payroll setup
- List union locals
- Create a union local
- Update a union local
- Add a CBA rate
- List wage determinations
- Create a wage determination
- List workers' comp classes
- Create a wc class
Reports (3)
- Report library
- Run a report
- Export a report
Authentication, scopes and permissions
Use a bearer token from /oauth/token with the accounting scopes. The token's company must match company_id.
- accounting:readEvery accounting GET: ledger, periods, reports, AP, AR, banking, payroll, assets, job cost and imports. The grant still limits what you see.
- accounting:writeEvery accounting POST, PATCH and DELETE: drafts, approvals, posting, reversals, close, payments, pay runs, reconciliations and imports.
Scopes are necessary, not sufficient: the user also needs an accounting grant with the right permission. Company admins hold every permission. Every permission:
- view_ledgerRead entries, balances and reports.
- post_journalCreate, submit, post and reverse journal entries; run recurring and auto-reversing generation.
- approve_journalApprove pending entries prepared by someone else.
- manage_coaAccounts, the construction template and settings, including control-account mapping.
- close_periodGenerate fiscal years; soft-close, close and reopen periods (a hard-closed period needs a second close_period user to confirm the reopen); post into soft-closed periods.
- ap_enterEnter and edit AP bills, vendor credits, payment runs, vendor compliance and lien waivers.
- ap_approveVote on AP bill approvals (never on a bill you contributed to).
- release_paymentsRelease payment runs, void payments, override holds, and print checks (and reprint them under a new number), produce positive pay and NACHA files.
- manage_vendor_bankingApprove vendor bank and tax ID changes (never your own request) and reveal full bank account numbers. Every reveal is audited.
- ar_enterPrepare AR invoices and receipts.
- ar_approveApprove AR invoices and receipts prepared by someone else.
- payroll_viewRead payroll setup, employees, runs and payroll reports.
- payroll_runChange payroll setup and employees; prepare, calculate, approve, post and void pay runs; export payroll files.
- view_payroll_piiReveal masked employee personal data. Every reveal is audited and rate limited; API reveals need a write-scoped token.
- bank_reconcileBank transactions, statement imports, matching, rules and reconciliations.
- manage_integrationsUpload, map, approve and apply accounting CSV imports.
- manage_grantsGive and change accounting access.
- job_forecast_editEnter dated PM cost-to-complete inputs on the job cost forecast (project_manager_view grants only on projects they belong to). Approving an input needs approve_journal.
401 means the token is missing or expired. 403 means another company, a missing scope or permission, a segregation-of-duties block, or a read-only workspace. 404 means not found in this company.
curl -X POST "https://app.os.construction/oauth/token" \
-H "Content-Type: application/json" \
-d '{"grant_type":"password","username":"you@example.com","password":"…",
"scope":"accounting:read accounting:write"}'Money is a string
Every amount in a response is a decimal string with exactly two decimals ("2079360.00"), and negative amounts carry a leading minus. Send amounts as strings too, with at most two decimals: "0.005" is refused, not rounded. Parse them with a decimal type (decimal.js, Python's Decimal, BigDecimal), never a float, or 0.1 + 0.2 will eventually show up in someone's trial balance.
Rates and quantities carry up to six decimals. Server-side rounding is half-up to the cent at the last step, and allocations use the largest-remainder method so parts add back to the whole.
Create a journal entry
POST /journal_entries saves a draft. Each line has an account_id and either a debit or a credit, never both, plus optional dimensions: entity_id, project_id, cost_code_id, cost_type_id, vendor_id, customer_id, department_id and more. Set auto_reverse_date to have the accrual reversed on that date. Reversals and recurring entries are generated once a day as of the local date in the company's accounting time zone (the timezone setting), or immediately when a user with post_journal runs Run recurring & auto-reversals on the Journal entries page. Manage recurring templates with /recurring_journals; any edit clears the template's approval, and an auto_post template posts only after a second person approves it.
Drafts may be unbalanced while you build them. Balance per entity, active postable accounts, required dimensions (job cost accounts need a project and cost code) and an open period are enforced at submit and post. Control accounts (AP, AR, retainage) refuse manual lines: post those through their documents.
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/journal_entries" \
-H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"journal_entry": {
"posting_date": "2026-10-31",
"memo": "Accrue October subcontract costs, Riverside",
"auto_reverse_date": "2026-11-01",
"lines": [
{ "account_id": 52, "debit": "68000.00", "project_id": 118, "cost_code_id": 330, "cost_type_id": 4 },
{ "account_id": 52, "debit": "42050.00", "project_id": 118, "cost_code_id": 262, "cost_type_id": 4 },
{ "account_id": 27, "credit": "110050.00" }
]
}}'const base = "https://app.os.construction/rest/v1.0/companies/1/accounting";
const headers = {
Authorization: `Bearer ${process.env.OS_ACCESS_TOKEN}`,
"Content-Type": "application/json",
};
// Money is a string with at most two decimals. Never build it with float math.
const res = await fetch(`${base}/journal_entries`, {
method: "POST",
headers,
body: JSON.stringify({ journal_entry: {
posting_date: "2026-10-31",
memo: "Accrue October subcontract costs, Riverside",
lines: [
{ account_id: 52, debit: "110050.00", project_id: 118, cost_code_id: 330, cost_type_id: 4 },
{ account_id: 27, credit: "110050.00" },
],
} }),
});
if (!res.ok) throw new Error((await res.json()).errors);
const je = await res.json(); // the draft: id, status "draft", lines, totalsimport os, requests
from decimal import Decimal
base = "https://app.os.construction/rest/v1.0/companies/1/accounting"
h = {"Authorization": f"Bearer {os.environ['OS_ACCESS_TOKEN']}"}
amount = Decimal("110050.00") # exact, never float
r = requests.post(f"{base}/journal_entries", headers=h, json={"journal_entry": {
"posting_date": "2026-10-31",
"memo": "Accrue October subcontract costs, Riverside",
"lines": [
{"account_id": 52, "debit": str(amount), "project_id": 118, "cost_code_id": 330, "cost_type_id": 4},
{"account_id": 27, "credit": str(amount)},
],
}})
r.raise_for_status()
je = r.json() # the draft entrySubmit, approve, post, reverse
Submit runs the full validation and moves the draft to pending_approval. Entries at or above the company's approval threshold must be approved by someone other than the preparer; the preparer gets a 403. Post assigns the JE-YYYY-##### number, writes job cost rows for job cost lines and the audit record in the same transaction, and sends accounting.journal_entry.posted after commit.
Posted entries are immutable: there is no PATCH or DELETE for them. Reverse posts a mirror entry on the date you give (in an open period), links the two both ways and marks the original reversed. Entries created by a subledger document are reversed through that document instead.
Retries are safe where it matters. Reversing an already reversed entry returns the same reversal instead of creating another. Entries created by documents are unique per source document revision, so re-posting the same bill or pay app revision returns the existing entry rather than double-posting.
# Validate in full and send for approval (balanced, open period, dimensions)
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/journal_entries/981/submit" -H "Authorization: Bearer $OS_ACCESS_TOKEN"
# A different user approves (the preparer gets 403)
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/journal_entries/981/approve" -H "Authorization: Bearer $APPROVER_TOKEN"
# Post: assigns JE-YYYY-#####, writes job cost rows and the audit record in one transaction
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/journal_entries/981/post" -H "Authorization: Bearer $OS_ACCESS_TOKEN"
# Correct a posted entry with a linked mirror entry (repeating returns the same reversal)
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/journal_entries/981/reverse" \
-H "Authorization: Bearer $OS_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"reversal": {"date": "2026-11-03", "reason": "Wrong cost code"}}'{
"id": 981,
"number": "JE-2026-00413",
"status": "posted",
"journal": "GJ",
"entity_id": 1,
"period_id": 22,
"posting_date": "2026-10-31",
"memo": "Accrue October subcontract costs, Riverside",
"source_type": null,
"total_debit": "110050.00",
"total_credit": "110050.00",
"balanced": true,
"auto_reverse_date": "2026-11-01",
"prepared_by_id": 12, "approved_by_id": 31, "posted_by_id": 12,
"posted_at": "2026-10-31T21:04:11Z",
"reverses_id": null, "reversed_by_id": null,
"lines": [
{ "line_no": 1, "account_number": "5200", "account_name": "JC – Subcontract",
"debit": "110050.00", "credit": "0.00", "project_id": 118, "cost_code_id": 330, … },
{ "line_no": 2, "account_number": "2040", "account_name": "Received Not Invoiced",
"debit": "0.00", "credit": "110050.00", … }
],
…
}When the ledger says no
A refused change is a 422 with an errors array of messages, or an object of field errors keyed by field. The ledger doesn't save a partial or unbalanced entry and doesn't round your amounts to make them balance. Segregation-of-duties blocks are 403. The messages below are illustrative; match on the status code, not the wording.
The same rules are enforced again by the database (balance, immutability of posted rows, closed periods), so there is no sequence of API calls that bypasses them.
HTTP/1.1 422 Unprocessable Entity
{ "errors": ["Debits 110050.00 and credits 110049.99 differ by 0.01"] }
HTTP/1.1 422 Unprocessable Entity
{ "errors": ["October 2026 is closed for AP"] }
HTTP/1.1 422 Unprocessable Entity
{ "errors": { "lines[0].project_id": ["is required for account 5200"] } }
HTTP/1.1 403 Forbidden
{ "errors": ["The user who prepared this entry cannot approve it"] }Read balances and detail
GET /trial_balance returns debit, credit and net per account from posted lines up to as_of (default today), optionally from from, for one entity_id, project_id or department_id, and split by group_by (entity, project, department, cost_code). Net debits always equal net credits.
GET /general_ledger returns posted lines between from and to in posting order. With account_id it adds the opening balance, a running balance per line and the closing balance. For deep reads, pass each response's next_cursor back as cursor: the next page starts after the last line and carries the running balance forward, so page 500 costs the same as page 1 (next_cursor is null on the last page). Lists page with page and per_page and return Total, Per-Page and Link headers.
# Trial balance as of a date, split by project
curl "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/trial_balance?as_of=2026-10-31&group_by=project" \
-H "Authorization: Bearer $OS_ACCESS_TOKEN"
# One account's register: opening balance, running balance per line, closing balance
curl "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/general_ledger?account_id=52&from=2026-10-01&to=2026-10-31" \
-H "Authorization: Bearer $OS_ACCESS_TOKEN"
# Job cost entries behind a job's actuals (page / per_page, Total + Link headers)
curl "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/job_cost_entries?project_id=118&per_page=100" \
-H "Authorization: Bearer $OS_ACCESS_TOKEN"{
"as_of": "2026-10-31",
"from": null,
"entity_id": null,
"group_by": null,
"rows": [
{ "account_id": 8, "account_number": "1110", "account_name": "Retainage Receivable",
"account_type": "retainage_receivable", "normal_balance": "debit", "dimension_id": null,
"debit": "3300000.00", "credit": "0.00", "net_debit": "3300000.00", "net_credit": "0.00" },
…
],
"totals": { "debit": "…", "credit": "…", "net_debit": "…", "net_credit": "…" }
}Fiscal years, periods and close
POST /fiscal_years creates a year and its twelve monthly periods from the settings' start month, and returns the existing year if it's already there. Each period has an overall status and a module_status for ap, ar, payroll, jc, cm and gl, each open, soft_closed or closed.
A closed module refuses postings, enforced again by a database trigger. A soft-closed one accepts postings only from users with close_period. Reopening needs a reason, which is written to the audit trail. A hard close also requires configured coverage, current checklist evidence and independent approval; unavailable adapters remain blockers. Prepare and review these in the Accounting periods workspace before calling close.
# Generate the fiscal year's twelve periods (safe to repeat)
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/fiscal_years" -H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "Content-Type: application/json" -d '{"fiscal_year": {"year": 2027}}'
# Soft-close AP; a hard close also requires the independently approved checklist and all configured coverage
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/periods/22/close" -H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "Content-Type: application/json" -d '{"close": {"module": "ap", "status": "soft_closed"}}'
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/periods/22/close" -H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "Content-Type: application/json" -d '{"close": {"module": "all", "status": "closed"}}'
# Reopen one module, with the reason that goes in the audit trail
curl -X POST "https://app.os.construction/rest/v1.0/companies/{company_id}/accounting/periods/22/reopen" -H "Authorization: Bearer $OS_ACCESS_TOKEN" \
-H "Content-Type: application/json" -d '{"reopen": {"module": "ap", "reason": "Late Summit invoice"}}'Webhooks
Subscribe to these on a webhook (or to accounting.*). Payloads carry ids, numbers, statuses and links only: never amounts, accounts or party details. Fetch the entry with the link, as a user allowed to see it.
A subscription receives accounting events only while the admin who created it could read them in the app: an accounting grant with view_ledger, not project-scoped, covering the event's entity_id. Events with a null or no entity_id (chart of accounts, parties, periods, NOCs) are company-wide and go only to a creator whose grant covers every entity. If the creator loses that access, pending deliveries are cancelled.
- accounting.journal_entry.postedAn entry posted: manual, reversal, or from a subledger document.
- accounting.journal_entry.reversedA posted entry was reversed; carries reversed_by_id.
- accounting.period.closedA period, or one module of it, was closed.
- accounting.period.soft_closedA period or module was soft-closed.
- accounting.period.reopenedA period or module was reopened. The reason is in the audit trail, not the payload.
{
"event": "accounting.journal_entry.posted",
"data": {
"id": 981,
"number": "JE-2026-00413",
"journal": "GJ",
"status": "posted",
"source_type": null,
"source_id": null,
"links": { "self": "https://app.os.construction/rest/v1.0/companies/1/accounting/journal_entries/981",
"app": "https://app.os.construction/companies/1/accounting/journal/981" }
},
…
}Integration checklist
- Use a dedicated user with a narrow accounting grant for each integration, so its postings are attributable and its permissions minimal.
- Keep the preparer and approver different people in automated flows. Approvals by the posting user will be refused.
- Store our
idbeside your own record and retry with it. Never create a second draft because a response timed out; read first. - Date entries in an open period. If you sync from another system, check the period status before pushing.
- Send an
Idempotency-Keyon every create, including master data and pay runs, so a timed-out request can be retried safely. - Pull the ledger into another system with
GET /integrations/exports/journals?target=<name>&since_last=true: each posted entry is sent to a target once, so a back-dated post is never missed. Re-download a lost file withbatch_idfrom theX-Export-Batch-Idheader. - Reconcile: compare
/trial_balancewith your system after every sync, account by account.
More on retries and the error format under Limits, paging & errors.