Invoices
An Axowl invoice is a statement of what was charged and whether the ledger accepted it — not a demand for payment.
Axowl runs on prepaid credit. Charges are taken from your balance as they are billed, so there is no due date, no overdue state, no late fee, and no receivable. An invoice never asks you to pay; it records what already happened.
The invoice does not compute prices. It records the debits that were actually attempted against your credit, and each line carries the ledger reference (refId) it was charged under — so you can verify any amount against the credit ledger independently of this API.
Authentication
Section titled “Authentication”The list and get endpoints take an org API key:
X-Api-Key: ah_live_…The key identifies the org; there is no org path parameter.
Invoices are billing data, so the key must be org-wide and issued by someone who can see billing (the organization owner). If the key lists allowed scopes, view.billing must be one of them.
| Status | When |
|---|---|
401 | The key is missing or invalid |
403 {"error": "insufficient_scope"} | The key is scoped to an App Group or an application; or it is org-wide, but its issuer cannot view billing, or its allowed scopes do not include view.billing |
GET /api/public/v1/invoices/{id}/verify is anonymous and needs no key.
List invoices
Section titled “List invoices”GET /api/public/v1/invoices?limit=24limit defaults to 24 and is clamped to 1–120. Newest period first. The response is a JSON array.
[ { "id": "3f2a…", "number": "INV-202607-0007", "period": "202607", "periodStart": "2026-07-01T00:00:00Z", "periodEnd": "2026-08-01T00:00:00Z", "status": "Issued", "settlement": "settled", "currency": "USD", "subtotal": 124.00, "settled": 124.00, "unsettled": 0.00, "issuedAt": "2026-08-01T00:12:03Z", "lines": [ { "category": "plan", "memo": "plan plan.pro (2026-07)", "amount": 25.00, "refId": "plan:3f2a…:202607", "outcome": "ok" }, { "category": "addon", "memo": "add-on addon.audit_analytics (2026-07)", "amount": 99.00, "refId": "addon:3f2a…:addon.audit_analytics:202607", "outcome": "ok" } ], "seal": { "selfHash": "9c1f…", "recordHash": "0a44…", "sealedAt": "2026-08-01T00:12:03Z" } }]Get one invoice
Section titled “Get one invoice”GET /api/public/v1/invoices/{id}Same shape as a list element. 404 if the invoice does not belong to the calling org.
Fields
Section titled “Fields”| Field | Meaning |
|---|---|
id | Invoice id (GUID). |
number | INV-{yyyyMM}-{seq}, the sequence zero-padded to four digits. null while the period is still open — a number is assigned only at issue. The sequence counts your org’s invoices, so it reveals nothing about anyone else. |
period | yyyyMM. The billing month (UTC). |
periodStart · periodEnd | First instant of the month (inclusive) and of the next month (exclusive), UTC. |
status | Open (the current month, still accruing) · Issued (closed and sealed, immutable). |
settlement | See below. |
currency | USD. |
subtotal | Total charged this period. |
settled | How much the ledger accepted — the sum of the lines with outcome: "ok". |
unsettled | subtotal − settled. This is not a debt — see below. |
issuedAt | When the invoice was issued. null while Open. |
lines[].category | plan · proration · addon · sso · usage · other. Derived from the refId prefix. |
lines[].memo | Human-readable description of the charge. |
lines[].amount | Amount of the line. |
lines[].refId | The credit-ledger reference this line was charged under. Your verification handle. |
lines[].outcome | ok (taken from your credit) · insufficient (the balance did not cover it) · error (the ledger could not be reached; retried). |
seal.selfHash | Document hash, sealed into the integrity chain. null until issued. |
seal.recordHash | Row-level integrity hash. |
seal.sealedAt | When the row was last sealed. |
settlement — “did the ledger accept it”, not “did you pay”
Section titled “settlement — “did the ledger accept it”, not “did you pay””Because charges are drawn from prepaid credit at billing time, an invoice has no payment step. What varies is whether your balance covered the charge:
| Value | Meaning |
|---|---|
settled | Every line was taken from your credit. |
partial | Some lines went through, others did not — usually a large line failed while smaller ones succeeded. |
awaiting_credit | Nothing could be taken; your balance was empty at billing time. |
none | Nothing was charged this period. |
A line marked insufficient is retried on every billing run (every 12 hours) while its billing cycle is current. It is not a debt and it accrues nothing — no interest, no late fee, no collections.
When the month closes, the invoice is issued as it stands. An Issued invoice is immutable, so a line that was still insufficient at that moment stays on it and the invoice keeps unsettled > 0. A charge that is collected later is recorded as an ok line, under the same refId, on the invoice of the month in which it was collected. Retries of a plan, add-on or SSO line end with its billing cycle; what happens to an organization that stays unpaid is described under Staying unpaid turns paid things off.
End-user MFA texts sent on grace. When your credit can’t cover end-user MFA texts, Axowl keeps sending them for 3 days (see SMS coverage). Those texts appear as one other line per grace period (refId sms:grace:…, outcome: "insufficient"), whose amount grows by $0.05 per text. They are collected when you top up, or with the next text that is charged normally — also after the period has closed, for grace lines from the last 3 months. The collected amount appears under the same refId with outcome: "ok" on the invoice of the month it was collected.
How charges are computed — proration
Section titled “How charges are computed — proration”The invoice only records the output of the pricing formulas, but two rules are worth knowing because they change what your first line looks like:
Add-ons: the first month is prorated by remaining days. When you enable a monthly add-on mid-month, the first charge is
first month = monthly price × remaining days in month / days in monthwith the enabling day counted (days are UTC days), the result rounded down to the cent (minimum $0.01). Enabling on the 1st charges the full price. From the next month on, the monthly billing run charges the full price. Enabling a $99/mo add-on on January 30th therefore charges $6.38 (2/31), then $99.00 from February.
The prorated charge and that month’s billing run share the same ledger refId (addon:{org}:{key}:{yyyyMM}), so the same month is never collected twice — the run’s attempt lands as an idempotent replay, and the invoice keeps the prorated line. The rule is identical on both payment rails: the prepaid-credit rail and the KRW card rail (which converts the same prorated USD amount at the fixed display rate).
Plan changes mid-period are prorated too. An upgrade from one paid plan to another charges the new plan for the remainder of the current period minus a credit for the unused portion of what you already paid (never below zero); the result appears as a proration line. Moving from Free to a paid plan starts a new billing period that day at the full price, with no proration. Downgrades charge nothing now — they take effect at the end of the period already paid for.
Staying unpaid turns paid things off
Section titled “Staying unpaid turns paid things off”An unpaid balance does not accrue, but it does not sit forever either. The grace clock runs for any organization with something paid at stake — a paid plan, paid monthly add-ons (including on the Free plan), or managed databases:
| When | What happens |
|---|---|
| First failed attempt | A 3-day grace period opens. Owners get an in-app notification and an email that names the exact deadline and exactly what stops — the plan, each paid add-on by name, managed databases. |
| During the 3-day grace | Retries continue on every billing run. Nothing else changes — everything stays active and no further notices are sent. |
| Still unpaid after 3 days | Whatever was named is enforced: a paid plan is downgraded to Free, paid monthly add-ons are turned off (one-time purchases and complimentary grants are never touched), managed databases are suspended. The unpaid plan and add-on lines are removed from the open invoice — they are no longer owed. Owners get a second notification and email saying what happened. |
| Topped up at any point | The grace period is cleared. A later shortfall starts a fresh 3-day clock. |
Your data is never touched by any of this — only paid features turn off. Top up to restore them; turned-off add-ons are re-enabled from Billing.
One exemption: Enterprise agreements are settled contractually rather than by this clock.
Verifying an invoice
Section titled “Verifying an invoice”Three independent checks, in increasing depth:
- The document is unmodified —
seal.selfHashexists in the integrity chain. Any change to the period, the totals, the invoice number or issue date, or to a line’srefId, amount or outcome produces a different hash. - Each amount really moved — look up
lines[].refIdin the credit ledger and compare the amount. A line that claimsoutcome: "ok"has a matching ledger entry; one that does not, does not. - The ledger entry itself is unmodified — each ledger row carries its own seal and chain anchor.
An Open invoice has no selfHash yet; only step 2 applies to it.
Verify endpoint
Section titled “Verify endpoint”GET /api/public/v1/invoices/{id}/verifyAnonymous — no API key. Share the invoice id with an auditor or counterparty and they can check the sealed statement without an Axowl account. The server recomputes the statement hash from the stored period, lines and totals and compares it with the sealed selfHash. The response carries no amounts.
Available when the organization that owns the invoice is on the Business plan or higher; otherwise the endpoint answers 403 {"error": "UPGRADE_REQUIRED", "feature": "billing.verify"}. An unknown id answers 404.
{ "status": "issued", "valid": true, "invoiceNumber": "INV-202607-0007", "period": "202607", "issuedAt": "2026-08-01T00:12:03Z", "selfHash": { "stored": "9c1f…", "recomputed": "9c1f…", "match": true }, "recordSealed": true}valid is false when the recomputed hash differs from the sealed one. For an invoice that is still Open the response is { "status": "open", "valid": null, "invoiceNumber": null, "period": "202608", "detail": "…" } — there is nothing sealed to compare yet.
Prepaid balance — top-ups and withdrawals
Section titled “Prepaid balance — top-ups and withdrawals”Your prepaid balance is two books, never one:
- Cash — real money you topped up. Refundable and withdrawable.
- Credit — welcome credit. Non-refundable; usage always burns credit first, so your refundable cash survives as long as possible.
Withdrawals are self-service (dashboard → Billing → Axowl Credit → Withdraw) and go back as a refund to the original card payment — no bank onboarding. One deduction applies, recorded as its own ledger line so the statement shows exactly what moved:
| Deduction | Amount | Why |
|---|---|---|
| Cost recovery fee | 2.9% + $0.30 of the withdrawn amount | The payment processor does not return its processing fee on refunds; this recovers that cost — at cost, no margin. Recorded as a separate Fee line. |
Card refunds usually land within 5–10 business days. Cash that did not arrive via a refundable card payment (or whose payment was already refunded) returns not_refundable_via_original_payment and is handled manually. Full terms: the Refund Policy.
When an invoice is issued, the statement email sent to Owners carries a PDF copy attached, named after the invoice number (INV-{yyyyMM}-{seq}.pdf). The PDF mirrors the sealed document — amounts are recorded, not recomputed.
The API itself returns invoices as JSON only: the invoice resource has no PDF field and there is no PDF endpoint. The PDF is delivered by email.
Not included
Section titled “Not included”- Payment operations — there are none. To add credit, use the dashboard’s top-up flow (from $5 to $10,000 per top-up; other amounts answer
400 invalid_amount).