Skip to content

Client billing

Generated from contract version 1.0.0 — the same document /v1/api/openapi.json serves.

  • An API key in x-api-key, or a browser session cookie
  • X-Organization-Id naming the workspace to act in, unless the session already has an active one
  • The base URL for your deployment, https://server.hlix.ai by default
Method Path Operation
GET /v1/api/client-billing/billables List the ledger
POST /v1/api/client-billing/billables Record a billable
GET /v1/api/client-billing/billables/{id} Get one ledger row
POST /v1/api/client-billing/billables/{id}/adjust Append a correction to a charge
POST /v1/api/client-billing/billables/{id}/void Reverse a charge

GET /v1/api/client-billing/billables · operation ID listClientBillables

Parameters

Name In Required Type Description
clientAccountId query no string Only this customer’s rows.
projectId query no string Only this project’s rows. A client collaborator asking about a project it cannot reach is REFUSED rather than answered with an empty list — ‘not yours’ must not read as ‘nothing billed yet’.
X-Organization-Id header no string The workspace (organization) to act in. Omitted, the session’s active organization is used.

Responses

Status Body Description
200 ClientBillableView[] The matching rows, each with the derived netAmountMicros and any void that reverses it.
400 ApiError The request named no active organization.
401 ApiError No valid session or API key.
403 ApiError Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project.
404 ApiError A projectId was named that this caller cannot reach. THE ONE PLACE THIS FAMILY REFUSES A READ OUTRIGHT: everywhere else an unreachable row is simply absent, but on a filtered list an empty 200 would make “not yours” indistinguishable from “nothing billed yet”.

POST /v1/api/client-billing/billables · operation ID createClientBillable

Parameters

Name In Required Type Description
X-Organization-Id header no string The workspace (organization) to act in. Omitted, the session’s active organization is used.

Request body — application/json, CreateBillable (required)

The contract does not pin this body’s properties.

Responses

Status Body Description
201 ClientBillable The written row. Its price is SNAPSHOTTED here and never recomputed; for agent_usage the tokens and model come from the run, not from the request, so a caller cannot name its own price.
400 ValidationError | ApiError The JSON body failed schema validation (ValidationError), or the request named no active organization (ApiError).
401 ApiError No valid session or API key.
403 ApiError The caller is a client collaborator; the billable ledger is managed by the agency.
404 ApiError No such client account or project in this workspace. Never a 403 — that would confirm it exists somewhere.
409 ApiError This source was already billed. A retried request is the usual cause.
422 ApiError A database constraint refused the row, and the answer names it. Reserved for a rule added after this contract — no request reaching the route trips one today.

GET /v1/api/client-billing/billables/{id} · operation ID getClientBillable

Parameters

Name In Required Type Description
id path yes string
X-Organization-Id header no string The workspace (organization) to act in. Omitted, the session’s active organization is used.

Responses

Status Body Description
200 ClientBillableView The row, with its derived net and void.
400 ApiError The request named no active organization.
401 ApiError No valid session or API key.
403 ApiError Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project.
404 ApiError No such billable this caller may read.

POST /v1/api/client-billing/billables/{id}/adjust · operation ID adjustClientBillable

Parameters

Name In Required Type Description
id path yes string
X-Organization-Id header no string The workspace (organization) to act in. Omitted, the session’s active organization is used.

Request body — application/json, AdjustBillable (required)

Property Type Required Notes
description string yes min length 1, max length 500
note string null no
quantity number yes
unit string yes min length 1, max length 50
unitPriceMicros integer yes min 0, max 1000000000000

Responses

Status Body Description
201 ClientBillable The correction — a NEW row naming the charge. The charge itself is untouched.
400 ValidationError | ApiError The JSON body failed validation, or the request named no active organization.
401 ApiError No valid session or API key.
403 ApiError The caller is a client collaborator; the billable ledger is managed by the agency.
404 ApiError No such billable this caller may reach.
409 ApiError A correction cannot correct another correction, and a voided charge cannot be corrected. The answer names the rule that refused.
422 ApiError A database constraint refused the row, and the answer names it. Reserved for a rule added after this contract — no request reaching the route trips one today.

POST /v1/api/client-billing/billables/{id}/void · operation ID voidClientBillable

Parameters

Name In Required Type Description
id path yes string
X-Organization-Id header no string The workspace (organization) to act in. Omitted, the session’s active organization is used.

Request body — application/json, VoidBillable (required)

Property Type Required Notes
reason string yes min length 1, max length 500

Responses

Status Body Description
201 ClientBillable The reversal — a NEW row. Its amount is the charge PLUS every correction already naming it, so a void can never leave a partial credit behind.
400 ValidationError | ApiError The JSON body failed validation, or the request named no active organization.
401 ApiError No valid session or API key.
403 ApiError The caller is a client collaborator; the billable ledger is managed by the agency.
404 ApiError No such billable this caller may reach.
409 ApiError It was already voided, or the rule that refuses a void names itself in the answer. A charge can be voided exactly once.
422 ApiError A database constraint refused the row, and the answer names it. Reserved for a rule added after this contract — no request reaching the route trips one today.

These apply to every operation on this page.

  • 400 — the body failed schema validation, or the request named no active organization. A ValidationError body carries the failing fields; an ApiError body carries only error.
  • 401 — no valid session or API key.
  • 403 — authenticated, but not a member of the named organization, or a client collaborator without the required access.
  • 404 — no such resource in this workspace. Absent and not-visible are deliberately the same answer; do not infer cross-tenant existence from it.
  • 409 — the expected revision or state is stale, or another operation currently owns the transition.
  • 500 — the operation failed server-side. Retry only where an idempotency key makes that safe.