Client invoices
Client invoices are the agency-billing surface: draft, issue, pay, void, and render an invoice for a client account.
Generated from contract version 1.0.0 — the same document /v1/api/openapi.json serves.
Prerequisites
Section titled “Prerequisites”- An API key in
x-api-key, or a browser session cookie X-Organization-Idnaming the workspace to act in, unless the session already has an active one- The base URL for your deployment,
https://server.hlix.aiby default
Operations
Section titled “Operations”| Method | Path | Operation |
|---|---|---|
GET |
/v1/api/client-billing/invoices |
List invoices |
POST |
/v1/api/client-billing/invoices |
Assemble a draft invoice from billables |
GET |
/v1/api/client-billing/invoices/{id} |
Get one invoice with its lines |
POST |
/v1/api/client-billing/invoices/{id}/issue |
Issue a draft, assigning its number |
GET |
/v1/api/client-billing/invoices/{id}/logo |
Download the logo frozen onto this invoice |
POST |
/v1/api/client-billing/invoices/{id}/pay |
Record an out-of-band settlement (bank transfer, cheque) |
POST |
/v1/api/client-billing/invoices/{id}/payment-link |
Open a provider-hosted checkout for an issued invoice |
GET |
/v1/api/client-billing/invoices/{id}/pdf |
Download the invoice as a PDF |
POST |
/v1/api/client-billing/invoices/{id}/void |
Withdraw an invoice, or discard a draft |
List invoices
Section titled “List invoices”GET /v1/api/client-billing/invoices · operation ID listClientInvoices
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
clientAccountId |
query | no | string | Only this customer’s invoices. |
status |
query | no | string | One of draft, final, void, paid. |
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 |
ClientInvoice[] | Invoice headers, newest first. lines is empty on this projection — read one invoice to get them. |
400 |
ApiError | The request named no active organization. |
401 |
ApiError | No valid session or API key. |
403 |
ApiError | The caller is a client collaborator. Invoices are agency-only, reads included. |
Assemble a draft invoice from billables
Section titled “Assemble a draft invoice from billables”POST /v1/api/client-billing/invoices · operation ID createClientInvoiceDraft
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, CreateInvoiceDraft (required)
| Property | Type | Required | Notes |
|---|---|---|---|
billableIds |
string[] | yes | |
buyerAddress |
string | yes | min length 1, max length 500 |
buyerBillingEmail |
string (email) | null | no |
buyerLegalName |
string | no | min length 1, max length 200 |
dueAt |
string (date-time) | yes | |
paymentTerms |
string | no | min length 1, max length 200 |
replacesInvoiceId |
string | null | no |
sellerAddress |
string | no | min length 1, max length 500 |
sellerLegalName |
string | no | min length 1, max length 200 |
taxRatesBps |
object | no |
Responses
| Status | Body | Description |
|---|---|---|
201 |
ClientInvoice | The draft, with its lines snapshotted from the ledger and its totals computed once and stored. |
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. Invoices are agency-only, reads included. |
404 |
ApiError | A named billable or the client account is not this workspace’s. |
409 |
ApiError | The set cannot be invoiced: it spans two customers or two currencies, a charge nets to zero, a tax class has no rate, a correction’s charge was never invoiced, or the total would exceed what an invoice can represent. The message names which. |
422 |
ApiError | A database constraint refused the draft. |
Get one invoice with its lines
Section titled “Get one invoice with its lines”GET /v1/api/client-billing/invoices/{id} · operation ID getClientInvoice
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 |
ClientInvoice | The invoice and every line it was built from. |
400 |
ApiError | The request named no active organization. |
401 |
ApiError | No valid session or API key. |
403 |
ApiError | The caller is a client collaborator. Invoices are agency-only, reads included. |
404 |
ApiError | No such invoice in this workspace. Never a 403 — ‘forbidden’ would confirm that an invoice exists for a customer the caller was never shown. |
Issue a draft, assigning its number
Section titled “Issue a draft, assigning its number”POST /v1/api/client-billing/invoices/{id}/issue · operation ID issueClientInvoice
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, IssueInvoice (required)
The contract does not pin this body’s properties.
Responses
| Status | Body | Description |
|---|---|---|
200 |
ClientInvoice | The issued invoice. invoiceNumber and invoiceSequence are assigned by the database at this moment and are gapless per workspace. |
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. Invoices are agency-only, reads included. |
404 |
ApiError | No such invoice in this workspace. Never a 403 — ‘forbidden’ would confirm that an invoice exists for a customer the caller was never shown. |
409 |
ApiError | Not issuable: already issued, no lines, the header does not reconcile with its lines, a charge on it is already invoiced, or a correction of one is missing from it. |
422 |
ApiError | A database constraint refused the issue. |
Download the logo frozen onto this invoice
Section titled “Download the logo frozen onto this invoice”GET /v1/api/client-billing/invoices/{id}/logo · operation ID getClientInvoiceLogo
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 |
string (binary) | The agency’s logo AS IT WAS AT ISSUE — not the workspace’s current one. The Content-Type is the format recorded on the invoice, accompanied by X-Content-Type-Options: nosniff. |
400 |
ApiError | The request named no active organization. |
401 |
ApiError | No valid session or API key. |
403 |
ApiError | The caller is a client collaborator. Invoices are agency-only, reads included. |
404 |
ApiError | No such invoice, or it carries no logo. |
Record an out-of-band settlement (bank transfer, cheque)
Section titled “Record an out-of-band settlement (bank transfer, cheque)”POST /v1/api/client-billing/invoices/{id}/pay · operation ID payClientInvoice
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, PayInvoice (required)
The contract does not pin this body’s properties.
Responses
| Status | Body | Description |
|---|---|---|
200 |
ClientInvoice | The settled invoice. This is an OUT-OF-BAND settlement — an agency member asserting money arrived somewhere hlix cannot see — recorded under its own audit action so it is never confused with provider-verified payment. Any outstanding hosted checkout is cancelled, so the invoice cannot also be paid through the provider. |
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. Invoices are agency-only, reads included. |
404 |
ApiError | No such invoice in this workspace. Never a 403 — ‘forbidden’ would confirm that an invoice exists for a customer the caller was never shown. |
409 |
ApiError | Only an issued, unpaid invoice can be settled. |
422 |
ApiError | A database constraint refused the payment. |
Open a provider-hosted checkout for an issued invoice
Section titled “Open a provider-hosted checkout for an issued invoice”POST /v1/api/client-billing/invoices/{id}/payment-link · operation ID createClientInvoicePaymentLink
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, PaymentLink (required)
| Property | Type | Required | Notes |
|---|---|---|---|
successUrl |
string (uri) | no | max length 2000 |
Responses
| Status | Body | Description |
|---|---|---|
200 |
ClientPaymentLink | A PROVIDER-HOSTED checkout URL and the provider’s reference for it — and nothing else. No card field, no payment token and no credential of any kind crosses this boundary: the payer enters those on the provider’s own page. Calling this does NOT mark the invoice paid. A PROVIDER payment settles the invoice only through the provider’s signed webhook, reconciled on amount, currency and invoice id; the one other route to paid is POST /:id/pay, an audited out-of-band settlement recorded by an agency member under its own audit action. |
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 attempting something other than reading or paying its own invoice. |
404 |
ApiError | No such invoice in this workspace. Never a 403 — ‘forbidden’ would confirm that an invoice exists for a customer the caller was never shown. |
409 |
ApiError | Not payable: the invoice is a draft, void or already paid, it already has a checkout open with a different provider, or its total is above what the provider will take in one payment. |
501 |
ApiError | No payment provider is connected for this deployment. The invoice can still be issued, sent and paid outside hlix. |
Download the invoice as a PDF
Section titled “Download the invoice as a PDF”GET /v1/api/client-billing/invoices/{id}/pdf · operation ID getClientInvoicePdf
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 |
string (binary) | The rendered invoice, every line included. |
400 |
ApiError | The request named no active organization. |
401 |
ApiError | No valid session or API key. |
403 |
ApiError | The caller is a client collaborator. Invoices are agency-only, reads included. |
404 |
ApiError | No such invoice in this workspace. Never a 403 — ‘forbidden’ would confirm that an invoice exists for a customer the caller was never shown. |
409 |
ApiError | The invoice is a draft, or its stored totals no longer reconcile with its stored lines. Rendering a document that does not add up is refused rather than attempted. |
415 |
ApiError | The invoice is valid but this renderer cannot encode it — a party name or line description outside Latin-1. Retrying will not help; read the invoice through getClientInvoice instead. |
Withdraw an invoice, or discard a draft
Section titled “Withdraw an invoice, or discard a draft”POST /v1/api/client-billing/invoices/{id}/void · operation ID voidClientInvoice
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, VoidInvoice (required)
| Property | Type | Required | Notes |
|---|---|---|---|
reason |
string | yes | min length 1, max length 500 |
Responses
| Status | Body | Description |
|---|---|---|
200 |
ClientInvoice | The voided invoice. An issued one KEEPS its number — a hole in the sequence reads as a deleted document. Corrections are a replacement invoice naming this one, never an edit. |
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. Invoices are agency-only, reads included. |
404 |
ApiError | No such invoice in this workspace. Never a 403 — ‘forbidden’ would confirm that an invoice exists for a customer the caller was never shown. |
409 |
ApiError | The invoice is already void. |
422 |
ApiError | A database constraint refused the void. |
If a request fails
Section titled “If a request fails”These apply to every operation on this page.
400— the body failed schema validation, or the request named no active organization. AValidationErrorbody carries the failing fields; anApiErrorbody carries onlyerror.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.