Cost centres
A cost centre is a code a company charges an order line to: a department, a project, a site or a budget. Each company keeps its own list. Cost centres can be turned on by the store setting, or for one company by costCentreMode in its b2bSettings. With optional, the buyer may pick one for each line at checkout; with required, they must pick one for every line. The line keeps the code and name on the order and in the order CSV export, and the company's statement lists each order's codes. Emails, the invoice PDF, the Stripe invoice and webhook payloads do not carry them.
The cost centre object
| Field | Type | Meaning |
|---|---|---|
id | uuid | Orbit's id. |
companyId | uuid | The company it belongs to. |
code | string, 1 to 32 characters on one line, without a semicolon | The code buyers pick. Unique within the company, ignoring case. It cannot be all, none or default, in any casing: the CSV files give those words a meaning. |
name | string, 1 to 120 characters on one line | What the code stands for, shown beside it. |
status | active or archived | Only active cost centres are offered at checkout. |
externalRef | string, 1 to 64 characters on one line, without a semicolon, or null | Your own reference for it, unique within the company, ignoring case. It cannot be all, none or default, in any casing: the CSV files give those words a meaning. |
userIds | array of uuid | The company users given this cost centre: the same assignments as each user's allowedCostCentreIds, seen from the cost centre. |
createdAt, updatedAt | date-time |
null clears externalRef. code, name, status and userIds never take null: a write that sends it answers 400 naming the field (send [] to take a cost centre away from every user).
Cost centres are never deleted. Archiving one takes it out of checkout and leaves every order that used it intact, because each order line stores the code and name it was placed with.
Routes
| Method and path | Scope | Purpose |
|---|---|---|
GET /v1/companies/{companyId}/cost-centres | company:read | The company's cost centres: active, or archived or all with ?status=. |
GET /v1/companies/{companyId}/cost-centres/{costCentreId} | company:read | One cost centre. |
POST /v1/companies/{companyId}/cost-centres | company:update | Add one: code, name, optional externalRef, status and userIds. |
PATCH /v1/companies/{companyId}/cost-centres/{costCentreId} | company:update | Change its code, name, status or externalRef; userIds replaces the assignments. |
DELETE /v1/companies/{companyId}/cost-centres/{costCentreId} | company:update | Archive it. |
PUT /v1/companies/{companyId}/cost-centres | company:update | Replace the company's whole list in one call, matched by code. |
PUT /v1/companies/{companyId}/cost-centres/by-external-ref/{externalRef} | company:update | Create or update one by your own reference. |
GET answers { items, meta, managedExternally }: active cost centres unless status asks for archived or all, up to 1000 per company, and managedExternally is true when the list is kept by the company's accounts system. GET .../cost-centres/{costCentreId} answers the cost centre whatever its status, and POST, PATCH and DELETE answer the cost centre itself.
Like every /v1/companies write, these need the store's B2B feature (403 FEATURE_FROZEN without it). A value outside the limits above is refused with a plain validation 400, and these refusals carry an errorCode:
Status and errorCode | When |
|---|---|
409 B2B_COST_CENTRE_CODE_TAKEN | Another cost centre of the company has the code, ignoring case. |
409 B2B_COST_CENTRE_EXTERNAL_REF_TAKEN | Another cost centre of the company has the reference, ignoring case. |
400 B2B_COST_CENTRE_LIMIT_REACHED | The company already holds 1000 cost centres, archived ones included: cost centres are never deleted, so an archived one keeps its place in the 1000. |
400 B2B_COST_CENTRE_DUPLICATE_CODE | The list sent to PUT .../cost-centres names a code twice, ignoring case. |
400 B2B_COST_CENTRE_DUPLICATE_EXTERNAL_REF | The list sent to PUT .../cost-centres names a reference twice, ignoring case. |
400 B2B_CONTACT_NOT_IN_COMPANY | An id in userIds is not a user of the company. |
Replacing the list by code
Usually an accounts system owns the list: send it whole, as often as you like. The same list again writes nothing and fires no webhook. If you send an Idempotency-Key, mint a new one for each run and reuse it only to retry that run's request: see Retrying writes safely.
PUT /v1/companies/6f2a1c3e-9d4b-4a7f-8e2c-1b3f5a7c9d0e/cost-centres HTTP/1.1
Host: api.myorbitcommerce.net
Authorization: Bearer oc_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Idempotency-Key: costcentres-CUST-004821-run-0042
Content-Type: application/json
{
"items": [
{ "code": "MAINT", "name": "Maintenance", "externalRef": "DEP-0101" },
{ "code": "FLEET", "name": "Fleet and vehicles", "externalRef": "DEP-0102" }
]
}- A code already in the company's list is updated with what you send: its
name, itsstatus(activewhen you leave it out, so an archived code back in the list becomes active again), itsexternalRef(kept when you leave it out, cleared bynull) and, when you send them, itsuserIds. A new code is created. - An active cost centre whose code is missing from the list is archived.
- Codes match ignoring case, and a matched code keeps its stored casing.
- Sending the same list again changes nothing and fires no webhook.
The answer is every cost centre of the company after the change, active and archived, with what changed: { "items": [ ... ], "created": 1, "updated": 1, "archived": 1, "unchanged": 0 }. created, updated and unchanged count the items you sent, so they add up to the length of your list; archived counts the active cost centres your list left out.
Upserting one cost centre by your reference
PUT /v1/companies/{companyId}/cost-centres/by-external-ref/{externalRef} creates the cost centre with that reference, or updates it, in one call. The body takes code and name, both needed even when the cost centre exists, and optionally status and userIds. Leaving status out keeps the current one, so an archived cost centre stays archived (a new one is active); leaving userIds out keeps the assignments, and [] clears them. An externalRef in the body is accepted and ignored without being checked: the reference in the path always wins. The rest of the body is checked as usual.
- The reference matches ignoring case and surrounding spaces, and the cost centre takes your spelling of it from the path.
- A cost centre with that reference is updated, and a different
coderenames it, unless another cost centre has that code. - Otherwise a cost centre with the same code and no reference yet takes the reference.
- A code that belongs to another cost centre answers
409withB2B_COST_CENTRE_CODE_TAKEN. - Otherwise a new cost centre is created.
The answer is { "costCentre": { ... }, "created": true }, with created: false when it updated an existing one. A reference in the path that is not 1 to 64 characters on one line without a semicolon, or that is all, none or default in any casing, answers 400 with B2B_EXTERNAL_REF_INVALID.
Who may charge which cost centre
- A company user's
allowedCostCentreIdslists the cost centres they may pick. An empty list means every active cost centre of the company. Set it withPATCH /v1/companies/{companyId}/users/{contactId}; see Company users. - A cost centre's own
userIdslists the company users allowed to charge it, set withPOSTorPATCHon the cost centre. A user given any cost centre can charge only the active ones they are given: when all of those are archived, they can charge none. costCentreModedecides whether the field isoff,optionalorrequiredat checkout.- With
costCentresManagedExternallysettruein the company'sb2bSettings, the list itself (codes, names, status, references, and users assigned from a cost centre) is read-only in the dashboard, in the storefront's company account area and in the cost centres file import, so only your API calls change it. Who may charge which stays editable everywhere: a user'sallowedCostCentreIdscan still be changed in the dashboard, in the account area, through the company file'sUser Cost Centrescolumn and through the API. Set the flag once for every company your system owns.
On orders
Each order line carries costCentreId, plus costCentreCode and costCentreName as they were when the order was placed. To find the orders charged to one cost centre, use GET /v1/orders?costCentreId=&includeArchived=true; without includeArchived=true the list leaves archived orders out.
Webhooks
| Topic | Fires on | data |
|---|---|---|
cost_centre.created | A cost centre is added, by the dashboard, the storefront company account, an import or the API. | costCentreId, companyId, storeId |
cost_centre.updated | Its code, name, reference, status or assigned users change, including a return from archived to active. A change made through a user's allowedCostCentreIds fires company_user.updated instead. | costCentreId, companyId, storeId |
cost_centre.archived | It is archived. | costCentreId, companyId, storeId |
All three need company:read at subscribe time.
Next steps
- The user fields that decide who may charge which cost centre: Company users.
- The order lines that carry a cost centre: Orders, approvals and pay on account.
- The cost centres file, for bulk loads: CSV imports and exports.