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

# Chart of accounts object

> The fifteen values Beacon publishes about each category in your chart of accounts — its place in your hierarchy, and the cost roles you assign it — and which doors each one reaches.

The chart-of-accounts object carries fifteen values about each category in your accounting system's chart of accounts, at one row per category, and two timestamps that accompany every one of them on every door: `as_of` and `computed_at`. **All fifteen are available through the doors below.**

This object is where your accounting system's own structure meets Beacon's. Your ledger already knows what its accounts are called and how they nest. What it does not know is which of them are the cost of serving a customer, which are the cost of winning one, and which are neither — and that is what the cost role, service layer and cost function on this object hold.

<Note>
  The names, types and availability below are fixed. No read interface or MCP tool serves them yet — this page publishes the shape ahead of the doors.
</Note>

On the read interface this object is `chart_of_accounts_category` and the collection is [`GET /chart-of-accounts`](/api-reference). The longer name is deliberate: `account` on this interface means a *customer* account, and a chart-of-accounts category is a different thing entirely. The two must not share a name, so this one is spelled out rather than shortened.

## Example payload

```json theme={null}
{
  "object": "chart_of_accounts_category",
  "id": "412",
  "as_of": "2026-08-05T06:00:00Z",
  "computed_at": "2026-08-05T06:11:48Z",
  "coa_category_id": "412",
  "account_code": "6120",
  "category_name": "Customer Support Salaries",
  "qualified_name": "Operating Expenses:People:Customer Support Salaries",
  "parent_coa_category_id": "410",
  "is_postable": true,
  "category_status": "active",
  "cost_role": "cost_of_service",
  "service_cost_layer": "support",
  "cost_function": null,
  "out_of_scope_treatment": null,
  "personnel_source": "hr_system",
  "classification_state": "confirmed",
  "statutory_grouping": "Expense.OperatingExpenses",
  "payment_terms_days": 0
}
```

The identifier is your accounting system's own identifier for the category. Where your system's identifiers are not stable across syncs, Beacon builds the key from the account code instead and records that it did — a category that is silently re-keyed loses every cost role you have confirmed against it, so it is never done quietly.

## Attributes

| Value | Type | Method | What it holds |
| - | - | - | - |
| `as_of` | Timestamp, UTC | System | The moment the value describes — a property of the data, not of Beacon's activity. Present on every response and every property. |
| `computed_at` | Timestamp, UTC | System | When Beacon last read or worked the value out. Present on every response and every property. |
| `coa_category_id` | String | Measured | Your accounting system's own identifier for the category. |
| `account_code` | String | Measured | The live account number in your ledger. The live one, never a deprecated predecessor of it — some systems carry both. |
| `category_name` | String | Measured | The category's own name, as your ledger holds it. |
| `qualified_name` | String | Measured | The name with its full parent path — `Operating Expenses:People:Customer Support Salaries`. It matters more than it looks: two categories called "Software", one under Sales and one under Engineering, cannot be told apart by name alone, and you are the one who has to tell them apart when you assign cost roles. |
| `parent_coa_category_id` | String, or null at the top | Measured | The category this one sits under. Only the child-to-parent direction is held — holding both directions of the same relationship invites a tree that disagrees with itself. |
| `is_postable` | Boolean | Recorded | Whether entries post to this category directly, or whether it is a heading that only groups the categories beneath it. **This is the single most consequential value on the object.** Classify a heading as a cost, and every amount underneath it is counted twice — once in the child, once in the heading. A non-postable category is excluded from every cost pool. |
| `category_status` | String, one of three | Measured | `active`, `inactive` or `archived`. An archived category still appears on historical entries, so it stays resolvable — but it is excluded from the setup checklist, so a long-dead account cannot hold up your go-live. |
| `cost_role` | String, one of seven | Recorded | What kind of cost this category is: `revenue`, `cost_of_service`, `cost_of_acquisition`, `product_development`, `general_administrative`, `non_operating` or `out_of_scope`. Every category has exactly one, and the seven cover every chart of accounts. **You set this; Beacon proposes and never commits it for you.** |
| `service_cost_layer` | String, one of four, or null | Recorded | For a cost of service only: `support`, `professional_services`, `csm` or `infrastructure`. Set when the cost role is `cost_of_service`, null otherwise — the two move together. Self-service is deliberately not one of the four: it is a way customers are served, not a pool that costs money. |
| `cost_function` | String, one of three, or null | Recorded | For a cost of acquisition only: `marketing`, `sales` or `blended`. Set when the cost role is `cost_of_acquisition`, null otherwise. It exists because marketing and sales read the same cost pool for different purposes and need to be able to tell their half from the other. |
| `out_of_scope_treatment` | String, one of five, or null | Recorded | For an out-of-scope category only: why it sits outside the cost model — `capital_expenditure`, `balance_sheet_other`, `intercompany`, `contra` or `suspense`. Set when the cost role is `out_of_scope`, null otherwise. It is how spend on long-lived assets is told apart from every other balance-sheet movement, so capital expenditure is never guessed from an account's name. A suspense account is never read as capital expenditure. **You set this; Beacon proposes and never commits it for you.** |
| `personnel_source` | String, one of two, or null | Recorded | For categories carrying people costs: whether the figure comes from your ledger (`ledger`) or from your HR system (`hr_system`). Exactly one of the two supplies it for any pool in any period — never both. That is how a salary is prevented from being counted once in the ledger and again in headcount, and it is prevented here rather than reconciled later. |
| `classification_state` | String, one of three | Recorded | `proposed`, `confirmed` or `overridden`. Whether the cost role above is still Beacon's suggestion, or your decision. Setup is not complete while any postable, non-archived category is still `proposed` — a suggestion nobody has looked at is not a classification. |
| `statutory_grouping` | String | Measured | Your ledger's own classification for this category, recorded word for word, exactly as it arrives. Not normalised, not mapped, not reconciled to anything. It exists so that Beacon's cost role and your accountant's grouping can be shown side by side, and every place they differ is visible rather than buried. **It is never used to decide a cost role** — see below. |
| `payment_terms_days` | Number, or null | Recorded | The typical lag in days between recognising spend in this category and the cash actually leaving. A hook for cash timing, not a forecast, and confirmed by you rather than inferred silently. |

**The cost role is the boundary the rest of the cost model rests on.** Marketing spend, sales spend and the cost of serving a customer are three different things that a chart of accounts does not distinguish on its own, and every downstream figure — cost to serve, acquisition cost, contribution margin — depends on that line being drawn once, in one place, by you. This object is that place.

**Your ledger's own grouping is recorded but never used to classify.** The reason is specific rather than cautious: the same account is grouped differently on different accounting platforms — a current account is filed one way on one system and another way on another. A classification that changes because of which software you bought is not a classification of anything economic, so it is kept as a record of what your ledger said and nothing more.

**This object carries no money and no currency.** Amounts live on ledger entries; a currency tag on a reference row would be a second place to state a fact the entry already carries, and two places to state one fact eventually disagree.

## Availability

✓ available by default · ⊕ available after an opt-in that states who becomes able to see the value · — not available.

**Nothing on this object is served on any door today, and that is true of all fifteen rather than of some of them.** The names, types, ceilings and marks below are fixed and will not change when the doors open. Read the table as what each value *will* reach, not as what is callable now.

| Value | Read interface | MCP tools | Events | CRM property | Audience ceiling |
| - | - | - | - | - | - |
| `as_of` | ✓ | ✓ | in every event | — | `operational` |
| `computed_at` | ✓ | ✓ | in every event | — | `operational` |
| `coa_category_id` | ✓ | ✓ | ✓ | — | `operational` |
| `account_code` | ✓ | ✓ | ✓ | — | `operational` |
| `category_name` | ✓ | ✓ | ✓ | — | `operational` |
| `qualified_name` | ✓ | ✓ | ✓ | — | `operational` |
| `parent_coa_category_id` | ✓ | ✓ | ✓ | — | `operational` |
| `is_postable` | ✓ | ✓ | ✓ | — | `operational` |
| `category_status` | ✓ | ✓ | ✓ | — | `operational` |
| `cost_role` | ✓ | ✓ | ✓ | — | `operational` |
| `service_cost_layer` | ✓ | ✓ | ✓ | — | `operational` |
| `cost_function` | ✓ | ✓ | ✓ | — | `operational` |
| `out_of_scope_treatment` | ✓ | ✓ | ✓ | — | `operational` |
| `personnel_source` | ✓ | ✓ | ✓ | — | `operational` |
| `classification_state` | ✓ | ✓ | ✓ | — | `operational` |
| `statutory_grouping` | ✓ | ✓ | ✓ | — | `operational` |
| `payment_terms_days` | ✓ | ✓ | ✓ | — | `operational` |

**The CRM door is closed on this object, and not by an opt-in.** The CRM door writes a property onto a customer record. A chart-of-accounts category is not a fact about a customer — it is a row in your accounting system with its own identity — so there is no record for it to be written onto. That is a hard limit rather than a setting, which is why it reads `—` and not `⊕`.

**Every value on this object sits at `operational`.** That is derived rather than assumed: it comes from reading who, inside Beacon's own model, is entitled to read this object at full scope. The role that owns it reads the whole model across every domain and sits below leadership level, so the ceiling is the lower one. There is one qualification, stated rather than hidden: Beacon's own access rules gate money fields to finance and data-governance roles. This object carries no money, and the roles that gate would name are the same ones already reading it — so the ceiling does not move.

**No one outside your company holds a standing read on the records behind this object.** Your chart of accounts is read from your accounting system into Beacon and consolidated there. That is worth stating because it is not true of every object — the revenue figures on the [company and period object](/data-model/company-period) *are* read from records your external auditor also reads.

## What your accounting system changes

**Beacon reads your chart of accounts through an accounting connector, and the two mainstream connectors do not deliver the same things.** Which one your accounting system sits behind changes how two values on this object are produced. This is stated on the object rather than left to be discovered during setup, because it changes how much you have to check.

| Value | Where it is read directly | Where Beacon has to work it out |
| - | - | - |
| `parent_coa_category_id` | Delivered as a field. The hierarchy arrives as your ledger holds it. | Reconstructed by reading the full parent path in `qualified_name`. The relationship is inferred from a name rather than delivered as a link. |
| `is_postable` | Delivered as a field — your ledger's own heading flag, inverted on the way in. | **Not delivered at all.** Beacon proposes it by checking whether any other category's full path runs through this one, and **you confirm or override every one.** |

**Where `is_postable` is proposed rather than read, the proposal is only as good as your naming.** Two categories with the same name in different parts of the tree will break it, and that is why it is put to you rather than applied silently. It is worth the check: this is the value that, wrong, double-counts every amount under a heading.

**The suggestion Beacon can make for `cost_role` is also stronger on some systems than others.** Some accounting models carry a cost-of-sales classification Beacon can read as evidence; others carry a shorter list with no such category at all, in which case more rows reach you as `proposed` and fewer arrive already sensible. Nothing is silently weaker — `classification_state` is exactly the value that tells you which rows have been looked at. The same holds for `out_of_scope_treatment`: no accounting system delivers it, and how good Beacon's first suggestion is depends on how much your system says about each account's type. You confirm every one either way.

## Freshness

| Value | Recomputed | Sealed |
| - | - | - |
| The seven structural values — identifier, code, name, qualified name, parent, postable, status | On each sync from your accounting system | Not sealed. Structure is current, not periodic |
| `cost_role` · `service_cost_layer` · `cost_function` · `out_of_scope_treatment` · `personnel_source` · `classification_state` · `payment_terms_days` | When you change them | Fixed inside the locked record of any period that used them |
| `statutory_grouping` | On each sync from your accounting system | Not sealed |

**The values you set do not drift.** A cost role stays as you left it until you change it. What moves underneath it is your chart of accounts itself — a new account appears, and it arrives as `proposed` with nothing assigned, which is the state that tells you there is something to look at.

**A period that has already been locked keeps the classification it was locked with.** Changing a cost role today does not silently restate last quarter's cost of service. The change applies from the point you make it.

## Versioning

The published object is versioned separately from Beacon's internal model. A value is never removed from a published version — it is deprecated, and the removal lands in the next version. A published name is permanent; a rename ships as a redirect.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.