> For the complete documentation index, see [llms.txt](https://docs.layeronecloud.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.layeronecloud.com/platform/billing/revenue-and-reports.md).

# Revenue, KYC and reporting

Four console screens, four different questions.

## 4.16 Revenue, insights and automatic charges <a href="#id-416-revenue-insights-and-automatic-charges" id="id-416-revenue-insights-and-automatic-charges"></a>

Four console screens, four different questions. The commercial figures are read-time views recomputed from the ledger. Two stored exceptions: the hourly AI outlook on Revenue, which freezes the facts the model saw so a page load does not call OpenAI and the prose cannot drift from those numbers, and the monthly targets below, which are the only figures on these screens a human typed.

These screens are **information surfaces, not landing pages**. Every one opens on figures — a table of numbers, not a hero value or a narrative heading — and the prose that explains a measure lives in a single "How to read these numbers" disclosure at the foot rather than above each table. Panel headings are nouns. The honesty rules below are not softened by that: a caveat that travels with a number (paid invoices are not cash, credit held is not a bank balance, the two Automatic charges totals are never added) stays next to the number it qualifies.

**Targets** (`/console/billing/targets/`, the third Revenue tab, `apps/billing/revenue_targets.py`) is the monthly commercial plan. A `RevenueTarget` row is one goal, for one metric, in one calendar month — `period_month` is always the 1st, and `(period_month, metric)` is unique, so a month cannot publish two targets for the same measure. The four metrics are the populations Revenue already reports under the same names: paid invoice income, hourly usage, monthly run rate and new orders. `METRICS` is the single list, and a metric without a resolver there would render a goal nothing is measured against.

The whole feature is **actual ÷ target**, and deliberately nothing else. No projection, no required daily pace, no ahead/behind banding: an operator reading 31% against a month that is half gone does that arithmetic unaided, and every derived figure was another number that could disagree with the ledger it came from. The page is one table — a month per row, a metric per column, the target you type with the actual and the percentage under it — and Revenue carries the same percentages as a single line above its metrics.

Rules that must not be softened. **A target never changes a measured figure** — attainment divides the same populations this section already defines, measured to an inclusive upper bound like every other figure here, so a charge posted at the measurement instant cannot count on one screen and not the other. **A month with no target is blank, not zero**, because treating a missing goal as `$0` reports every unplanned month as infinitely over-achieved; a target *of* zero is real and is kept, which is why a blank form field clears a target instead of storing one. **Monthly run rate is a reading taken now**, not a sum over the month: there is no stored history of it, so it is shown only for the month that is running and is blank beside any other. Setting a plan writes `billing.revenue_target_set` to the audit log. Support is default-deny on the whole Revenue area, and this route both reads the book and writes the plan, so it stays off `SUPPORT_CONSOLE_ROUTES` for the read *and* the write.

**Revenue** (`/console/billing/`, sidebar entry **Revenue**) — the commercial picture, always for the current month. Any other window, and every Excel download, is [4.19](#id-419-reports-and-data-export). It opens on one line of target percentages for the current month, omitting any metric with no target rather than padding the line with em-dashes, and saying so plainly when no plan is set at all. Below that the page leads with the two questions it exists for, side by side: **Money in** — paid invoices, hourly usage, one-offs, credit deducted and income to date, each against the same clock time in the prior month — and **Pace** — the seven-day chart with the completed-week comparison, month forecast, today, and the daily average. Outstanding balances, top accounts, the credit-category split and the supporting series follow underneath. Its figures separate month-to-date hourly usage, paid invoice value, recent hourly usage projected across 730 hours, and current-year income to date. All figures share **one measurement timestamp**. Comparisons stop at the same local day and clock time in the prior period, publish the actual cutoff when the current day does not exist in a shorter prior month, have **no percentage against a zero baseline**, and distinguish growth from decline from flat. February 29 compares with February 28 when last year is not a leap year.

The main daily chart is the rolling seven-day ledger series including today, with a zero-based axis and one scale for both periods, continuing across month and year boundaries. Its prior-month overlay is seven consecutive complete days ending on the same date one month earlier (clamped to that month's final day), so only the endpoint shifts and shorter months cannot duplicate or skip buckets. The estimated closing segment is dashed without an underlying measured segment, and a native disclosure provides a captioned table so the figures are available without hover. The month outlook extends the month-to-date hourly total across the actual calendar-month duration including DST, and is **withheld until a full hour has elapsed** to avoid magnifying the first reservation charge posted just after midnight.

The **hourly outlook** on Revenue (`apps/billing/revenue_outlook.py`) writes a **day overview** (today vs yesterday at the same local clock time) and a **week overview** (the last seven local days vs the seven days before that, both through the same local clock time — not a completed yesterday or prior week) once an hour via Celery (`billing.refresh_revenue_outlook`). Commentary uses the stored OpenAI key and `AssistantConfig.model` — the same chat-assistant model as the public dock, ticket titles and AI campaigns — **not** the documentation-search model and **not** gated on the public assistant switch. The prompt is aggregates only: no account identifiers, emails or names. Paid-invoice honesty, hourly-usage meaning, and mixed-currency rules are restated in the instructions so the model cannot "correct" them. Failures keep the last successful write-up; missing key leaves the figures and says so. Support stays default-deny: this is the Revenue route.

Those write-ups are **not a wall of text on the page**. Super admins get a circular **bot button** (bottom right of Revenue). Opening it shows the frozen day and week commentary and a composer. Follow-up questions POST to `billing:admin-revenue-outlook-chat` (its own url name, also Support-denied) and are answered from **that same stored snapshot** — a page load still never calls OpenAI; asking a question does. The model still must not name accounts or recommend refunds, charges, destroys, or suspends. Snapshot figures (usage, paid invoices, deploys, destroys) sit behind a disclosure in the panel.

**Usable cards** on that panel, and the matching Client insights headline, count VPS clients with a saved card that can auto-recharge **and whose last automatic recharge did not decline**. A credit-only, one-time or non-Stripe default is not cover; a declined card is not cover either — the customer believes they are funded and they are not.

**"Last" means last, including a charge this platform never saw the answer to.** When Stripe stops responding mid-charge, `charge_stripe_payment_method` raises with `outcome_known=False`: the payment may already have been captured. The recharge path records `credit_auto_charge_failed`, because from here that is all it can honestly say, and stores that uncertainty on the attempt under the same `stripe_outcome_known` key the saved-card checkout path uses, so a retry replays the attempt's Idempotency-Key instead of charging twice. It does **not** email the customer that the payment failed — either the charge went through and the `payment_intent.succeeded` webhook posts the credit and sends the normal receipt, or it never happened and the next maintenance sweep reports a real decline. When that webhook does repair the charge it also writes `credit_auto_charge_succeeded` carrying the original slot `event_key`, because `insights._auto_charge_states` decides cover from the **newest** of those two actions — without it, a card that had just paid kept reporting "Last charge declined" and stayed out of this figure. It is written only when the webhook actually changed the attempt's status, so a replay cannot report one recharge as two. Deploy and destroy counts on the outlook use the same local-day fleet movement as Client insights (first provision vs DESTROYED/DELETING teardown activity), not unique customers.

Honesty rules that must not be softened: **paid invoices are face values marked paid during the period**, potentially settled using existing credit — they are not processor cash receipts, net revenue or a bank cash-flow statement. **Positive customer credit** sums positive per-account balances and shows negative balances separately rather than netting them against other customers' credit; it can include staff and promotional adjustments and is not a cash-held statement. **Mixed currencies are never summed.** An invoice with no due date is never counted overdue. Every figure that links to a list carries the filter it was counted with, so the number and the list cannot disagree.

The operations home (`/console/admin/`) opens with a four-box strip in the WHMCS pattern. Super admin sees Income (USD invoices marked paid today, with this local week beside it), Orders (awaiting payment, with new orders this week), Tickets and To-Do (failed deployment jobs, unresolved incidents and checks to review). Support sees Clients, Services, Tickets and To-Do — never the money boxes. Remaining overview counts sit under that strip as compact tiles. The board **Sales** widget is the WHMCS System Overview graph: new orders, activated (paid/provisioning/active) orders, and that same paid-invoice USD income, with Today (hourly), This Month (daily) and This Year (monthly) timescales. The Income figures and the graph are **not hourly usage** and the full commercial picture stays on Revenue.

**Non-hourly charges** are reported as the exact complement of the hourly rows within the negative USD ledger for the current local month — bandwidth blocks, bandwidth overage, API overage, refund clawbacks, staff deductions, and Other for unknown or source-less deductions — with a total, count, share and breakdown and **no run rate or projection**. Hourly plus non-hourly is therefore a complete partition of USD credit deductions: nothing unknown is silently discarded or allowed to distort the recurring trend.

**Automatic charges** (`/console/billing/automatic-charges/`, the second Revenue tab) — "what did we charge people automatically this month". Its own url name, not a `?tab=` branch, because `staff_access.py` is default-deny keyed on the resolved `(app_name, url_name)` and folding it behind the Revenue route would make one allowlist entry stand for two pages. Neither is on `SUPPORT_CONSOLE_ROUTES`.

`apps/billing/automatic_charges.py` owns the answer, and **"automatic" is a property of the mechanism, not of the resource**. The classification reads `metadata["source"]`, because three pairs of rows are otherwise indistinguishable — same entry type, same resource, same account, sometimes the same amount:

| Counted                            | Not counted                                 | Why                                                                                                                                                                                                                                |
| ---------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tick_usage_aggregation`           | `initial_first_hour`                        | The first reserved hour is charged the instant a customer deploys, reserves an IP or turns managed services on. They clicked. It shares `_record_vm_runtime_usage` with the metering pass, so only the source can tell them apart. |
| `auto_renewal`                     | `client_purchase`                           | The same prepaid bandwidth block, rolling over versus being bought.                                                                                                                                                                |
| `bandwidth_overage`, `api_overage` | `admin_credit_adjustment`, `payment_refund` | Both excluded ones follow a decision a person made; the ledger row is only its bookkeeping.                                                                                                                                        |

`hourly_usage_aggregation` is one source over two resources, so `AutomaticChargeKind` carries an optional `resource_type` and `kind_for` resolves most-specific-first. **A source in `AUTOMATIC_CHARGE_SOURCES` that no kind claims is counted in the total and reported as Unclassified** rather than folded into a sibling, because a charge missing from both the total and the table is the worse failure. `entry_type__in=BALANCE_NEGATIVE_ENTRY_TYPES` is belt-and-braces imported from the same set `account_credit_balance` sums.

**Card top-ups sit beside the credit draw, never inside it.** An auto-recharge turns cash into credit, which the meter then spends; adding the two counts the same dollar twice. The card side reads `PaymentAttempt` rather than the credit the charge posts, because **a decline leaves no ledger row** — and a declined automatic charge is exactly what an operator opens this screen to find (the balance keeps falling, the escalation stays silenced until the failure is recorded, and the account is heading for suspension). Failures are listed; only successes are totalled. `credit_auto_recharge` is a literal in `client_services` and a constant here, and `StripeWebhookTests` drives the real recharge path with a stubbed Stripe charge and looks for the row through the page's own query, so a rename on the writer's side cannot silently empty a column.

**That query reads `metadata__source`, not a column.** Unlike `AccountLedgerEntry` — which mirrors `source` / `resource_type` / `usage_idempotency_key` out of metadata into indexed columns (`DERIVED_METADATA_FIELDS`, backfilled in migration `0106`) — `PaymentAttempt` has no such column and never has. Reading it as one raises `FieldError` on every load of this page, which is exactly what happened when the ledger's JSON-to-column change was applied to this one call site as well.

`AutomaticChargeKind.itemised` splits what is listed from what is only summed. Bandwidth overage, bandwidth renewals and API overage are lumpy and few, so they are listed row by row. **Hourly metering is summed and never listed**: a hundred servers write about 72,000 ledger rows a month, and listing them would bury a $180 bandwidth overage under a wall of $0.0041 charges. **Only the metered half carries a run rate** — projecting month-to-date would scale a $180 overage charged once on the 3rd into $1,800 by month end. The projection scales the **unrounded** sum, because rounding first and multiplying by \~60 doubles a half-cent month.

The **local** calendar month (`TIME_ZONE` is Eastern) is the boundary, matching the Revenue month panel and the customer statement, so the three screens cannot disagree about which day a charge fell on. Bandwidth and API allowances run on UTC months of their own; that only shifts which month a boundary-hour charge is *reported* in.

**Insights** (`/console/billing/clients/insights/`, `apps/billing/insights.py` and `apps/billing/product_adoption.py`) — two questions on one page: what the book of business *uses*, and which accounts need something done.

Headline measures: estimated monthly account burn, attached add-on revenue and the share of clients taking at least one add-on, monthly burn on low-runway accounts without armed auto-charge, and the count of accounts carrying any signal. The funding figures are measured on the **VPS retention cohort** (provisioned VPS that are not deleting or destroyed, stopped and suspended included); adoption is measured on the wider **active book** — that cohort plus live hosting accounts plus anyone still holding a product without a server, so no share can exceed 100%. The page labels both scopes explicitly.

*What clients use* is one row per product, each with the same four facts: distinct clients and their share of the book, the volume that makes the count mean something, attached monthly revenue, and the one thing going wrong with that feature.

| Product             | Counted from                                                                                                                                  |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| Managed services    | Live VMs with `metadata.managed_service.status == "active"` — the population `account_monthly_recharge_amounts` prices                        |
| Backup storage      | `BillingAccountAddons.backup_storage_gb` on the organization payer, fanned out to every sibling as `backups.backup_storage_quota_gb` reads it |
| Scheduled backups   | Enabled `VmBackupSchedule` on a live VM; `consecutive_failures > 0` is the alert                                                              |
| Backups & snapshots | AVAILABLE `VmBackup` + `VmSnapshot`, held bytes as `GREATEST(size_bytes, reserved_bytes)`                                                     |
| ISO library         | AVAILABLE `IsoImage` with a `billing_account` (customer rows, not the operator catalog)                                                       |
| Floating IPs        | Unreleased FLOATING `IPAllocation`                                                                                                            |
| Private networks    | `PrivateNetwork` rows                                                                                                                         |
| Bandwidth blocks    | Active `BandwidthBlock` covering now — the window the allowance is computed from                                                              |
| Web hosting         | `WEBHOSTING_UNCONDITIONAL_ACCOUNT_LIABILITY_STATUSES` services                                                                                |
| Client API          | `ApiKey.objects.active()` with a default tenant                                                                                               |

Monthly value is only filled where the feature is itself billed. Snapshots, stored backups and ISOs draw on a pool somebody already bought, so those rows say what funds them instead of counting the money twice.

*Clients* is **one** paginated table over the union of those populations, and every adoption row and funding signal is a lens onto it (`?focus=`). An account appears once, carrying all of its overlapping reasons and all of its products, because "who runs backups and is about to run dry" used to mean reading two tables and intersecting by eye. It sits **collapsed at the foot of the page**: at a thousand accounts nobody reads the rows, and a page that opens on them buries every figure above. `?focus=` or `?page=` opens it, so arriving from a lens link lands on the filtered table; the summary carries the active lens and the matching count, which is the part worth reading at a glance. Signal lenses rank by how soon something breaks (low runway without cover, then declines, staff blocks, transfer, stale invoices); product lenses rank by burn, because there the question is who is worth talking to. `no add-ons` is the upsell list.

Bandwidth, staff blocks and every product lens deliberately reach accounts outside the VPS cohort: pooled transfer still bills after teardown and an ISO library outlives the server it was uploaded for.

**Client churn** counts customers rather than servers, which is what separates it from VPS movement: a client who replaces three boxes with four moves seven servers and churns not at all, while a client whose only server goes away is the whole loss and one line in a fleet count. Each billing pool's servers merge into runs on the rewards streak (`rewards.merge_service_runs`, the same population and the same `TENURE_REJOIN_GRACE`), so a destroy-and-rebuild inside 24 hours — including across midnight, which a same-day test would miss — is one unbroken run and is not churn, exactly as it does not reset the loyalty tier. A pool's first run opening is **gained**, a later one is **returned** (kept out of gained so new business reads as new business), a run closing is **lost**, and net is gained plus returned less lost. Windows are today, yesterday and trailing 30 and 365 days; the two calendar days use local midnight like every other "today" on the page. One rule for all four: a boundary counts the moment it happens, and today is provisional, because waiting out the grace before counting a loss would make "lost today" structurally zero and leave yesterday moving until tonight. Pool membership is read as it stands now, the same simplification `account_tenure_map` makes.

Notes that matter: **"no auto payment" is not "no card"** — a credit-only, one-time or non-Stripe default is one the recharge path will never charge, and a *declined* card is the loudest row because the customer believes they are covered. Destroy source is derived on read by `classify_destroy_source` from `VirtualMachineAction.requested_by`/`reason` — there is no `destroyed_by` column; API is matched before the actor because an API key acts as the owner, a termination with no actor is billing policy, and the earliest DESTROY action wins so a staff retry of a client's failed teardown stays the client's request.

**Everything is bulk.** The per-account helpers in `services.py` are correct but cost a query each, and a hundred-client page calling four of them per row is four hundred queries. `_balances_by_account` builds its CASE from the shared entry-type sets, `_auto_charge_states` imports `payment_method_allows_auto_recharge`, and `product_adoption` groups the same predicates the per-account helpers use, so none of them can drift from the single-account versions.

`billing:admin-client-insights` stays off `SUPPORT_CONSOLE_ROUTES` and the sidebar entry is wrapped to match.

## 4.17 Identity verification (KYC) <a href="#id-417-identity-verification-kyc" id="id-417-identity-verification-kyc"></a>

Requesting Didit KYC uses `set_auto_recharge_blocked` **in the same transaction** as the account hold, so the saved card is not charged while identity is unresolved, and it writes the normal auto-recharge audit event. Approval or staff cancellation does **not** silently turn automatic billing back on — staff make that safety decision explicitly. Generic account unsuspension never cancels KYC; there is a separate **Cancel KYC** action for waiving the hold deliberately.

* Didit verification URLs are **Fernet-encrypted at rest** and decrypted only for the authenticated client's KYC page. Tickets link to that internal page rather than persisting the provider's bearer URL. A newly requested verification emails the billing address with the client-area link; reusing an existing open session sends no duplicate.
* Webhook payloads are reduced to provider event ID, type and status; identity decision data is **not retained**. Accepted event IDs have their own unique rows, timestamps older than five minutes are rejected, and provider creation timestamps prevent a delayed event with a distinct ID from overwriting a newer decision.
* Webhook authentication implements Didit's V2 canonical-JSON signature, exact raw-body signature, and field-based simple signature in that order. **The simple fallback trusts only its signed timestamp, session ID, status and event type** — not decision data and not an unsigned provider event ID.
* Provider settings validate the workflow ID as a UUID, and a legacy invalid value is treated as unconfigured so it cannot create an account hold or make a rejected request.
* "Awaiting User" is an open session state. An approval can lift a hold only when it belongs to the account's **current** verification. Closed accounts reject new requests; an account already suspended before KYC stays suspended after approval.
* Creating a session happens **outside** the billing-account transaction, with a short locked reuse check both before and after the provider call, so a slow provider cannot hold up checkout or maintenance.

## 4.18 Daily super-admin account credit <a href="#id-418-daily-super-admin-account-credit" id="id-418-daily-super-admin-account-credit"></a>

The first live billing-maintenance pass each calendar day resets existing, non-closed billing accounts owned by **active super admins** to exactly **$100.00**. The day follows Django's `TIME_ZONE` (`America/New_York`), independent of any request's active timezone; with the five-minute cadence the reset happens on the first successful pass after midnight, and a missed run catches up.

Support, clients and disabled users are excluded. Usage and overage post first, then the reset runs **before** automatic recharge and suspension decisions, so later usage spends the balance normally. Balances above $100 are reduced to $100. Each reset appends an adjustment or debit for the difference and records a `super_admin_credit_reset` billing event with the date and before/after balance; an account already at $100 records the event **without** a zero-value ledger entry, so spending later that day cannot trigger another reset. Account and user row locks protect the eligibility and duplicate check. Dry runs only report eligible accounts and do not consume the day's reset. Normal recovery continues to respect non-billing holds such as identity verification.

***

## 4.19 Reports and data export <a href="#id-419-reports-and-data-export" id="id-419-reports-and-data-export"></a>

**Reports** (`/console/billing/reports/`, sidebar entry **Reports** under Billing, `apps/billing/reporting.py` + `apps/billing/exports.py`) is the period-scoped counterpart to Revenue. Revenue answers "how is this month going, right now" and cannot be pointed anywhere else; Reports takes a window -- this/last month, this/last quarter, this/last year, the last 12 months, or a custom range up to 366 days -- and shows one dataset as a spreadsheet. Its own url name, absent from `SUPPORT_CONSOLE_ROUTES`, sidebar entry wrapped to match. The window is `?period=` and the sheet is `?dataset=`; unusable period input falls back to the current month, an unknown dataset falls back to `service_revenue`. The page is the sheet, not a dashboard: pick the report, read the rows, download Excel.

The commercial questions are the same four measures Revenue uses, because four different figures were all being read as "how much money did we make", and on a prepaid platform each is a different step in one flow:

| Measure        | Population                                       | What it is not                                                  |
| -------------- | ------------------------------------------------ | --------------------------------------------------------------- |
| Cash received  | `PaymentAttempt` succeeded in the window         | Gross; before processor fees, not reduced by later refunds      |
| Revenue earned | Ledger entries in `BALANCE_NEGATIVE_ENTRY_TYPES` | Credit spent, not cash banked                                   |
| Credit issued  | Ledger entries in `BALANCE_POSITIVE_ENTRY_TYPES` | Includes adjustments, promotions and refunds, so it is not cash |
| Invoices paid  | `Invoice.paid_at` in the window                  | Can be settled from existing credit, so not a cash receipt      |

Those four are **never summed**. What relates them is `credit_reconciliation` on the **Money map & reconciliation** sheet: **opening credit + credit issued - revenue earned = closing credit**, an exact identity because all four are sums over the same append-only ledger under the sign convention in `models.py`. The reconciliation figures are **netted across accounts** so the arithmetic closes; the liability figure beside it (`held_positive_closing`) excludes overdrawn accounts instead of netting them, matching [4.16](#id-416-revenue-insights-and-automatic-charges).

Boundaries and comparisons follow the same rules as Revenue. **Local (Eastern) calendar months**, so Reports, the customer statement and Automatic charges cannot disagree about which day a charge fell on. **USD only** on summary sheets: non-USD ledger activity in the window is counted and published as excluded, never converted. `as_of` is the **last instant inside** the window -- now for a running period, `end - 1µs` for a finished one -- and every measurement filter is `__lte=as_of`. The opening balance is the one deliberately exclusive bound (`_net_balance_before`). A running period's baseline is cut at the **same number of local days and the same clock time** into the prior period (`_matching_cutoff`), clamped to that period's own end. No percentage against a zero baseline.

**Revenue by service** reuses `_hourly_credit_predicate` and its exact complement, so the hourly/non-hourly split matches Revenue's; the hourly axis is the indexed `resource_type` column, where **blank means the server itself**. Hourly rows carry `charges` (meter ticks), `avg_units_out` (those ticks divided by hours from period start through `as_of` -- what was out on average), and `avg_usd_per_day` (amount divided by measured days). A charge count of 36,384 server-hours over two weeks is \~110 machines, not 36,384 of them; $0.00 with three ticks is a run-rate, not "nothing". Non-hourly lines leave `avg_units_out` blank. **By plan** groups server runtime on `metadata["virtual_machine_id"]` through a JSON key transform, and reads the plan **as the server stands today**. **By account** is one grouped query per measure, never a per-account helper in a loop.

**Exports** (`billing:admin-report-export`) are the same ten datasets as `.xlsx` (`openpyxl` write-only). **Summary** datasets call the functions the sheet renders from, so a download cannot disagree with the screen it came from. **Record** datasets -- invoices, invoice lines, payments, ledger entries -- stream with `.iterator(chunk_size=2000)` and paginate on screen (100 rows). Summary files are USD; **record files carry every currency** with a currency column. The ledger file writes a `direction` column rather than leaving Excel to infer the sign from `entry_type`. Every file opens with a provenance line naming the dataset, period, bounds, cutoff, `window_hours`, currency scope and generation time.

**One export route, not ten.** An unknown dataset on the download is a 400. Each download records `AuditAction.BILLING_REPORT_EXPORTED` **before** the file is handed over: a client that disconnects mid-download still received whatever was already sent, and an audit trail of completed exports only is the one an exfiltration would not appear in.

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.layeronecloud.com/platform/billing/revenue-and-reports.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
