Repository navigation
Releases: getvelox/velox
Release list
v0.8.2
Fixes before the first partner installs. Long invoice PDFs go onto more pages, and every email has a Date and a Message-ID. Webhook URLs with a private IPv6 address are refused when saved, and sign-ins record the real client address. No database migrations.
Before you upgrade
- No migrations in this release. The database stays at 0180, as in v0.8.0 and v0.8.1. Take a backup as for any upgrade.
OTEL_TRACES_SAMPLERnow takes effect. If you set it, Velox keeps only the traces it selects; before, every trace was exported. Unset, nothing changes. The Compose stack now passesOTEL_TRACES_SAMPLERandOTEL_TRACES_SAMPLER_ARGfrom.env. (#1206)- A webhook endpoint saved earlier with a private IPv6 URL still fails every delivery. Change its URL to a public address. (#1199)
What changed
Invoices
- Long invoice and credit-note PDFs go onto more pages. Before, rows after about the 30th and the totals were drawn below the page edge. Later pages repeat the table header and are numbered "Page N of M". Amounts are unchanged. (#1172)
- Thai baht amounts on PDFs print as
THB 1,234.00; the PDF font has no ฿ sign. (#1172)
- Every email has a
Dateand aMessage-IDon yourSMTP_FROMdomain, and accented names and long lines travel as quoted-printable in short lines. The wording is unchanged. (#1187)
Security
- Dashboard sign-ins and invite accepts record the client address
TRUST_PROXYresolves, not the firstX-Forwarded-Forentry, which any client can write. (#1203) - Webhook endpoint create and update refuse private IPv6 addresses such as
fd00::1, using the same check as delivery. (#1199)
Self-hosting
- A failed release of an
Idempotency-Keyis logged at ERROR and counted invelox_idempotency_cache_errors_total{stage="release"}. (#1206) deploy/compose/.env.examplegives the tracing endpoint on the OTLP/HTTP port 4318. (#1206)
Every change, with its reasoning: CHANGELOG.
v0.8.1
Safer upgrades: a repair command for a database upgrade that stopped midway, and a limit on how long a migration waits for a table lock. Also a faster invoice list, and the Compose stack passes the Postmark webhook login and VELOX_API_BASE_URL. No database migrations.
Before you upgrade
- No migrations in this release. The database stays at 0180, the last migration of v0.8.0. Take a backup as for any upgrade.
- A migration waits at most 5 seconds for a table lock, then stops. This applies to later upgrades: while a migration waits for a lock, every other query on that table waits behind it. A migration that runs in one transaction leaves nothing behind when it stops, and the error says to run the upgrade again.
MIGRATION_LOCK_TIMEOUTchanges the wait (for example30s, or0for no limit). Index builds markedCONCURRENTLYhave no limit. Steps: runbook. (#1167) - A dirty
schema_migrationshas a named error and a repair command. Velox still refuses to start or migrate. The error now names the migration, says whether some of its changes may be in the database, and gives thevelox migrate force <version>commands to run.velox migrate statusprints the same advice. Steps: runbook. (#1165) - Compose reads
POSTMARK_WEBHOOK_USER,POSTMARK_WEBHOOK_PASS,VELOX_API_BASE_URLandMIGRATION_LOCK_TIMEOUTfrom.env. If you passed them with an override file or by editingdocker-compose.yml, move them to.env. (#1164)
What changed
Upgrades
velox migrate force <version>clears the dirty mark a failed migration leaves. It refuses while a migration is running, when nothing is dirty, and for a version that is not next to the dirty one. (#1165)- Each migration session sets a Postgres
lock_timeout(5 seconds unlessMIGRATION_LOCK_TIMEOUTsays otherwise). CI now fails a new migration that builds an index or adds a constraint the write-blocking way. (#1167)
Dashboard
- The invoice list reads each banner fact (card on file, Stripe connection, dunning, pause, payment page) once per page instead of once per row. Banners are unchanged. (#1166)
Self-hosting
- The Compose stack passes the Postmark bounce webhook login and
VELOX_API_BASE_URLthrough to Velox, anddeploy/compose/.env.examplelists them. (#1164) - Measured sizing for one small server: a €6.49/month Hetzner CX23 took about 1 million events a day with 0 errors (scale). The one-VM production guide got fixes from a first walk through it. (#1163)
Every change, with its reasoning: CHANGELOG.
v0.8.0
LiteLLM usage for an unknown customer is held instead of lost, and new accounts retry failed payments. Also: Settings → Email with a test send, a dashboard that says what each page is for, and dates that follow the billing timezone. Three database migrations.
Before you upgrade
- Run migrations first, as for any upgrade. This release adds three: 0178 (a column on Stripe credentials), 0179 (a new
usage_held_eventstable) and 0180 (a column on dunning policies). None rewrites existing rows. - New accounts start with a "Default retries" dunning policy; existing accounts do not get one. An account created after the upgrade retries a failed payment on day 3, 7 and 14, then leaves the subscription and invoice alone. An account that had no default policy before the upgrade still retries nothing until you create one; the Dunning pages now say so. (#1155)
- LiteLLM usage for a customer or meter that does not exist yet is held, not dropped. The callback still answers 200, and its response gains a
heldcount. Held rows are listed on the Usage page and atGET /v1/usage-events/held. Create the customer, then release them. Released usage from before the customer's subscription started is stored but not invoiced. (#1151) - Email actions answer 409
email_not_configuredwhenSMTP_HOSTis not set. This covers sending an invoice or credit note, sending a payment-method setup link and inviting a teammate. They used to report "sent" while the queued email failed. Save & send still finalizes the invoice. Password reset is unchanged. - A charge Stripe never answered returns
payment_outcome_unknown, notprovider_error. A decline is stillprovider_error. If you branch on the code, add the new one. - Due dates and trials count calendar days in the billing timezone. "Net N" and
trial_daysadded N×24 hours, so across a daylight-saving change a due date printed one day early and a trial ended an hour late. Dates that do not cross a clock change are the same as before. - A date-only
tofilter now ends at 23:59:59.999999 UTC (was 23:59:59.000), so CSV exports, the invoice list and the audit log include the last second of that day. - Many messages were reworded; codes are unchanged.
attention.messageon an invoice is now up to three short lines, and the longer explanation moved toattention.detail. Subscription, pricing, recipe, webhook and password refusals read plainly. Match onreasonandcode, not on the text. - Set your company name if you have not (Settings → Business details). With none set, PDFs no longer print "Velox" as the seller. That includes already-issued invoices, because a PDF is rendered when it is opened. Totals and lines do not change. (#1093)
What changed
LiteLLM and usage
- Usage for an unknown customer or meter is kept, shown on the Usage page and released once, under its original key. Discard marks rows as never billed, even if LiteLLM resends them. (#1151)
- The Usage page shows each meter's value by its own aggregation, so a Maximum meter no longer adds up its readings. (#1097)
- Export CSV downloads every filtered row from the server (it stopped at 50,000), up to 365 days per file.
GET /v1/exports/usage-events.csvtakescustomer_id,meter_idanddimensions. (#1123)
Dunning and payments
- New accounts retry failed payments from day one with "Default retries". Your first enabled policy takes over as the default. (#1155)
- Charge now on an invoice paid entirely from credit closes its dunning run. (#1061)
- The customer payment page no longer shows a Pay button when Stripe is not connected; it says who to contact. (#1118)
- Stripe key errors say whether Stripe refused the key or could not be reached (
last_verified_error_kind). (#1067)
Billing and dates
- The next-invoice preview prorates like the invoice: a calendar first period, an added item and a quantity change show prorated lines. (#1096)
- Net N due dates and trial ends count calendar days in the billing timezone. New manual invoices store the account's timezone, so their PDFs print local dates. (#1077, #1087, #1081)
- Dashboard charts bucket by UTC day whatever the database server's time zone. (#1070)
- A margin request without dates follows the customer's test clock, and customer usage accepts a full leap year. (#1082, #1088)
- A new trial no longer hides the running cycle's usage. (#1073)
- Draft PDFs carry a DRAFT mark, and no PDF prints "Velox" as the seller when no company name is set. (#1093)
Settings and email
- Settings → Email shows whether this server can send email and sends a test email with the mail server's real answer. API:
GET /v1/settings/emailandPOST /v1/settings/email/test. (#1121) GET /v1/whoamigainssmtp_configured,can_send_invitesandcan_email_setup_links, and the Team tab says when invites are off.
Dashboard
- The customer Margin card shows each model's margin next to its billed amount and cost. API: each row of the margin response's per-model breakdown gains
margin_bps. The demo now bills two models, so it shows two rows. (#1159) - A task-grouped sidebar, one notice per page with wording that matches what billing does, and amounts in each invoice's or customer's own currency. (#1062 to #1067, #1091, #1092)
- A new account's Home explains Velox and opens a "Bill your first customer" checklist, and each page says what it is for. (#1138, #1145)
- Credits on the customer page show granted, used, expired, canceled and deducted amounts. (#1098)
- Margin shows "—" with the missing setup when no provider cost is known, instead of 100%. (#1095)
- Pages fit a 390px phone screen.
Docs
- New pages: a first-invoice tutorial, "How Velox works", "Production on one VM" and a quick-start page for each demo step. The operations runbook is one page per problem.
Every change, with its reasoning: CHANGELOG.
v0.7.0
A one-command Docker install, late usage billed, issued invoices that never change, a complete API reference, and fixes from a pre-launch check. One database migration.
Before you upgrade
- Every usage event needs its own
idempotency_key.POST /v1/usage-events,/batchand/backfillrefuse an event without one, or with one longer than 255 characters, with 422. Pick one key per event and send the same key when you retry it. TheIdempotency-Keyheader does not count. The LiteLLM integration already sends keys. - Invoices are built 1 to 2 hours after a period ends (was within an hour), so usage that arrives a little late is billed. Webhooks, charges and dunning move later by the same amount; invoice dates do not. Set
INVOICE_GRACE_PERIOD=0for the old timing. - A scheduled cancel takes effect up to an hour later. The subscription stays
activeuntil then, thoughcanceled_atis still the date you set. - Removing a meter's last price can be refused (409) while a plan with a live subscription uses that meter.
- Run migrations first, as for any upgrade. This release adds four columns.
What changed
Install
deploy/compose/quickstart.shstarts Velox from the release images with only Docker and git, prints a login and a test key, and./quickstart.sh demoruns token usage through to an invoice. (#959)- Login works in Safari when Velox runs at
http://localhost. docker psno longer shows healthy containers as unhealthy, and nginx follows the API after it is recreated.
Billing
- Usage that arrives within the grace window after a period ends is billed on that period's invoice. (#961)
- An issued invoice or credit note keeps the customer and company details it was issued with. Editing them later no longer changes old PDFs or the hosted page. (#964)
- Several changes made just after a period ended, before its invoice, acted on the wrong period. They now apply to the new one. (#961)
- Usage that matches no price is counted and logged, including on spend-threshold invoices. (#981)
- A retried usage event without a key was billed twice. Keys are now required. (#980)
Payments
- A void, write-off or bank payment waits for a card charge in progress. (#975)
- A Stripe rate limit during a charge no longer parks the invoice. (#978)
- A card payment and its Stripe webhook arriving together no longer deadlock. (#989)
Margin and LiteLLM
- Provider cost no longer counts prompt-cache reads twice for LiteLLM calls that send
cost_breakdown. - The margin card and API name their currencies and do not compute a margin across two currencies.
API
- The API reference documents 175 operations, up from 68: every route you call. (#962)
- An unknown dunning policy answers 404, and error responses carry
typeandcodein the right fields.
Docs
- About 80 sentences across the README and the main docs now claim only what the code does.
Every change, with its reasoning: CHANGELOG.
v0.6.1
A small release that corrects the live credit figure and adds a way to read it from your backend. No database migration, and nothing to do when upgrading.
What was wrong
The public cost page shows a customer how much prepaid credit they have left (estimated_remaining_cents). In 0.5.0 and 0.6.0 that figure was wrong in five situations:
- After a billing-threshold invoice: usage the invoice had already taken from credit was subtracted a second time. The figure read too low, often zero.
- Around a trial: usage that is never billed was subtracted in some setups.
- Several subscriptions on different billing dates: one subscription's dates were applied to all of them.
- After a plan change in the middle of a period: usage from before the change was dropped.
- Credit past its expiry date: still counted for up to an hour.
For a customer with one ordinary subscription the figure was right. No invoice and no real balance was affected, only this estimate.
The figure now comes from the same calculation that builds the invoice, so what it subtracts is what the invoice will take.
What is new
GET /v1/credits/balance/{customer_id}/livereturns the live figure with a secret key: the credit a customer can spend, the usage not yet invoiced, and what is left.- A guide: Stop usage when credit runs out. Poll the read on an interval, cache it, and check your cache before serving a request. Velox records usage and never refuses it, so the stop happens in your code.
- The cost page's
creditsobject gainsunbilled_usage_cents, so its three numbers add up.
Worth knowing
- The live read adds up the customer's usage for the period, so it takes longer as usage grows: about 0.2 seconds at 100,000 events and about 2 seconds at 1,000,000, measured on one laptop. Poll it; do not call it on every request.
- It is an estimate. It leaves out tax, a base fee billed at the end of the period, and unpaid invoices. The guide lists what it includes and what it does not.
- The
credit.balance_low,credit.balance_depletedandcredit.balance_recoveredwebhooks follow the settled balance, which moves when an invoice is issued. They do not follow usage. Webhooks on the live figure are tracked in #1019.
Every change, with its reasoning: CHANGELOG.
v0.6.0
Fixes from a security review and a billing audit, plus dashboard screens for subscriptions whose billing is failing. No database migration.
Before you upgrade
- Set
METRICS_TOKENif you scrape/metricsin production. WithAPP_ENV=productionand no token,/metricsnow answers 401. The Compose stack runs in production mode. - Check three settings. Velox now refuses to start on an
APP_ENVsuch asprod(it must beproduction), aTRUST_PROXYentry that is not an IP or CIDR, or aDB_*pool setting that is not a whole number. GET /health/readyno longer fails when the billing scheduler stalls. It answers 503 only while draining or when the database ping fails. Alert onvelox_scheduler_last_run_timestamp_secondsinstead.- A cancel can answer 409
unbilled_periodwhen a billing period has ended and cannot be invoiced. Retry billing, then cancel again. If you script cancels, handle this code. - Several endpoints answer 404 or 422 for an id that is not yours, where they used to answer 2xx.
- Webhook endpoints in
100.64.0.0/10are refused. Some private overlay networks use that range. - Images are signed with cosign 3. Verifying an image from this release needs cosign 3 or newer.
What changed
Billing
- A subscription cancelled after its billing period ended, and before the billing run invoiced it, is now billed for that period. Before, that period's usage and base fee were never invoiced. (#991)
- Cancelling just after a renewal no longer voids the previous period's usage on a plan billed in advance.
- Usage ingest answers 503, not 422 "not found", when the database cannot look up the customer or meter. A client that treats a 4xx as final dropped that usage.
Dashboard
- Subscriptions whose billing is failing are marked in the list, with a
Billing failingfilter. The subscription page shows the reason, which attempt failed, when the next one is, and aRetry billingbutton. (#960) - A price box left blank no longer saves a $0 rate.
- A failed load on the customer page shows an error with Retry, not "$0.00" or a blank setup form.
Security
- Eleven endpoints accepted the id of an object in another tenant, or in the same tenant's other mode (live or test), and wrote a row under the caller's tenant pointing at it. They now refuse it and write nothing. The customer's owner got no spendable credit from this. The changelog lists the endpoints and says how to find rows written earlier.
- Webhook deliveries check the address they connect to, not only the host name.
/metricsrequires a bearer token in production.- The readiness check no longer returns database error text.
- A sent email's database row no longer keeps its link token or PDF. Rows from before this release are not cleaned up.
- A failure at the end of an API key rotation answers a logged 500, not a 422 carrying the error text.
- The dashboard image is now scanned and signed, and moves from nginx 1.27 to 1.30.
- A network error while sending no longer marks the customer's address as bounced.
Docs
- Billing thresholds are described as what they are: an early invoice, once per period, at the next hourly billing run. They do not stop usage. The page said "hard cap".
Every change, with its reasoning: CHANGELOG.
v0.5.0 — LiteLLM now records usage; failing subscriptions are saved and retryable
One day after 0.4.0, with one fix that matters to anyone using LiteLLM, two requests from a user, and the first of the audit fixes.
If you use the LiteLLM integration, read this first
Before this release, Velox recorded nothing from a real LiteLLM proxy. A proxy logs the caller as end_user and puts token counts at the top level of each record. Velox read only user and usage, so it refused every record inside a 200 response. It now reads both shapes.
- Usage sent before you upgrade was not stored and cannot be recovered from Velox.
- The guide was checked against LiteLLM 1.83.9 with a real proxy: LiteLLM integration.
Behaviour changes
POST /v1/billing/runskips a subscription whose billing is failing until its wait has passed. Use the new retry endpoint to try one now.velox_billing_due_subscriptionsandvelox_billing_oldest_due_age_secondsno longer count failing subscriptions. The newvelox_billing_failing_subscriptionsdoes. Update alerts that relied on the old meaning.- Run migrations first, as for any upgrade. This release adds five columns to
subscriptions.
What changed
Billing
- A subscription whose billing fails now keeps a saved record: how many attempts failed, when, and when the next attempt is due. It waits 15 minutes, then 1 hour, 4 hours, 12 hours, then a day at a time, and is set aside after eight failures.
POST /v1/subscriptions/{id}/retry-billingclears the failure and bills at once. The retry is written to the audit log.GET /v1/subscriptions?billing=failinglists the failing ones.- A lost database connection or a shutdown mid-run does not count as a failure.
- A panic while billing one subscription no longer stops every later billing run.
Credits
GET /v1/public/cost-dashboard/{token}now returns acreditsobject: the balance, what is left of the prepaid commit and of promotional credit, and an estimate of what remains after this period's usage so far. A customer-facing page can show a balance without a secret key. Requested in #86.
Invoices
- The buyer's tax ID no longer prints on top of the tax line in the invoice PDF. It appears once, under the buyer's name. Reported in #164.
Running it
- Release images are built for ARM as well as Intel/AMD, so
docker pullworks on Apple Silicon Macs and ARM servers. - Dashboard build tooling updated; 28 of 35 dependency advisories cleared.
Docs
- Nine sentences that claimed more than had been shown are corrected, including the README line about several businesses in one install.
- Thanks to @KeuqS for correcting two claims on the features page (#990).
Every change, with its reasoning: CHANGELOG.
v0.4.0 — the October money-path fixes; two or more replicas supported
The first release since August. It carries every money-path fix from September and October, and makes running two or more replicas the supported production setup.
Before you upgrade from v0.3.2 or earlier
- Do this one rollout as stop-all-then-start, not rolling. An old binary and a new one would each run the background jobs for the length of a rolling deploy. Steps: Upgrading.
- Production now refuses to start without
REDIS_URLandTRUST_PROXY. Set both before you deploy. See Self-host. - Run migrations first, as for any upgrade.
- Dashboards keyed on the HTTP metrics'
pathlabel now see route patterns such as/v1/customers/{id}instead of raw paths. - Set
SMTP_FROMto an address on your own domain if you send email. The built-in default changed tobilling@velox.local, a placeholder.
What changed
Invoices and credits
- A billing tick bills every subscription that is due, not only the first 50.
- A usage threshold no longer restarts graduated or package tiers for the rest of the period.
- A credit note issued after customer credits were applied no longer relieves an invoice twice.
- A cancel, downgrade or plan change records the customer's refund for the unused period in the same step, so it cannot be lost.
- Credit-note numbers have no gaps.
- "Due on receipt" (Net 0) applies to every invoice.
- Voiding an invoice reverses all of its tax.
Payments and collection
- A customer paying on the hosted invoice page and a Velox charge can no longer both take money for one invoice.
- A charge's late result can no longer undo a payment or a decline recorded while it was in flight.
- An invoice finalized just before a crash is collected on the next tick.
- Dunning retries keep their spacing, and an interrupted final attempt is finished.
- A card used on the hosted invoice page is saved to the customer.
Failing safely
- Billing, tax, payments, plan changes and credit notes stop on a failed read instead of carrying on with a guessed value.
Running it
- Two or more replicas behind a load balancer is the supported production setup. Background jobs run on leader leases, and a dead replica is replaced within seconds.
- PgBouncer in transaction mode and RDS Proxy are supported.
- Shutdown announces itself before it stops listening and fits a 90-second budget.
- Go 1.26.9.
Dashboard
- Sign-in over plain HTTP at an address other than localhost now says why it fails: it needs HTTPS or
http://localhost. - "Report an issue" and the sign-in help link open GitHub Issues.
Housekeeping
- v0.1.0 and v0.2.0 are retracted. They ship two commands that were later deleted and predate this release's billing fixes.
- Nothing in Velox points at
velox.devany more. The project does not own that domain.
Docs
- The self-host page and the install guide are rewritten in plain words.
- A one-screen README, a one-page list of engineering principles, and a sourced comparison with Lago, OpenMeter, Flexprice and Kill Bill.
Every change, with its reasoning: CHANGELOG.
v0.3.2
Same product code as v0.3.1. This release exists to validate and ship the CI supply-chain fix (#849): tag runs now complete the full chain — image publish → Trivy scan → SBOM attached to this release → Grype scan → Cosign signature. The v0.3.0 and v0.3.1 images on GHCR are unsigned (their tag runs died at the SBOM step before signing); operators should pin v0.3.2+.
v0.3.1
Patch: the "Fewer dependencies" README heading now uses typographic quotes — pkg.go.dev's outline nav HTML-escapes straight ASCII quotes and was showing literal " entities. No code changes.