Home / Blog / What we learned shipping ausdata.io 0.1 → 0.6.78 in 30 days

2026-05-20 · Harry Vass

What we learned shipping ausdata.io 0.1 → 0.6.78 in 30 days

An honest 30-day retrospective on shipping ausdata.io from idea to live revenue. Eight customer-fit retest cycles, an embarrassing unit bug, and a webhook race we still cringe about.

Thirty days ago ausdata-api was a single FastAPI file that called one sister MCP and returned the RBA cash rate. Today it serves 23 endpoints across 9 source agencies, runs on Fly.io behind Supabase JWT auth, charges via Stripe, and has paying customers.

This is an honest write-up of what broke, what we got right by accident, and what we'd do differently. Self-deprecation incoming.

<!-- IMG: ausdata-0-1-to-0-6-78-timeline.png -->

The version timeline

  • 0.1.0, single /v1/series/AU.CASHRATE/latest endpoint, hard-coded, no auth.
  • 0.2.0, Supabase JWT auth, three endpoints.
  • 0.3.0, HMAC API keys, first paid tier wired to Stripe test mode.
  • 0.4.0, switched to Stripe live mode; first paying customer the same week.
  • 0.5.0, Wave 1 signal rollout (real_cash_rate, real_wages, etc.).
  • 0.6.0, added aemo-mcp (energy) and wgea-mcp (gender pay).
  • 0.6.38, long-lived asyncio loop per worker, the fix that made everything not 500.
  • 0.6.61, the release calendar feed shipped, then immediately leaked revenue (story below).
  • 0.6.78, current version. Stable, growing.

That's 78 patch releases plus 6 minor releases plus the major in 30 days. About 2.8 releases per day. Most were "fix a typo in the OpenAPI description"; about a dozen were genuine engineering.

The eight customer-fit retest cycles

We didn't talk to enough customers before building. We talked to two, built half the API to their spec, then realised in the third customer call that we'd built the wrong half. Then again on the fourth call. Eight times in total we rewrote either the response envelope, the endpoint naming, or the quota model based on a single customer's feedback.

The lesson, which I've heard a hundred times and still apparently needed to learn personally: the first three customer conversations are research, not implementation. Don't write a single line of API code until you've heard the same problem from three different people.

What worked once we accepted that: the standard DataResponse envelope across every endpoint, every source. Customers who saw it on one endpoint immediately understood every other endpoint. The cost of late-stage envelope unification was a week of churn that we could have skipped entirely with one extra week of customer conversations up front.

The unemployment-unit bug (embarrassing)

/v1/youth-unemployment shipped with the value field labeled value (no unit suffix) and the actual numbers were in percent, not in a fraction. The OpenAPI spec said "unemployment rate" without specifying. A user (researcher at a university, thank you, Hugh) plotted the data, got values like 14.8, and assumed it was already in percent. Correct. Another user (data scientist at a fintech) treated it as a fraction, multiplied by 100, plotted, and got values like 1480.

Both interpretations are wrong from the API's perspective: we had no business shipping a numeric field without a unit annotation in the response itself.

The fix (0.5.4) added an explicit unit: "percent" to every response and a value_pct (or value_aud, value_index) suffix in the field name. Old value field deprecated, kept for one minor version for backwards-compatibility, then removed in 0.6.0.

Time from report to fix: 14 minutes. Time we spent worrying about whether we'd silently broken downstream charts in the four weeks before that: too long.

The lesson: unit-annotate every numeric field in your API response from day 1. Adding a unit later is high-friction; never adding one is a footgun.

The Pulse-revenue-leak fix

the release calendar feed shipped on a Thursday. It's a list of upcoming AU data releases (ABS, RBA, APRA) with embargo dates. Useful enough that we put it on the Analyst tier.

It was meant to be gated. The middleware that checks the tier on every endpoint had a bug: it special-cased the the release calendar feed path because the response was cached for an hour and the original implementation skipped auth on cached paths to reduce DB load. Free-tier users could hit it without burning a quota.

Found in the logs the following Tuesday, about four days of free Pulse access for everyone. Roughly 800 free-tier calls that should have been Analyst tier. Direct revenue leak: tiny (probably $0, since none of the free-tier users would have upgraded just for Pulse). Indirect cost: a confused customer who paid for Analyst and asked us why their non-paying friend had the same access.

Fix shipped that night: the cache check now happens after the tier check, not before. The endpoint became correctly gated.

The lesson: never short-circuit auth in the name of cache optimisation. Auth first, then cache. Always.

What we got right (by accident)

A few things worked out better than the design called for:

  1. Standardising on DataResponse early. Even when we were rewriting other things eight times, the envelope held. Customers learned it once and used it everywhere.
  1. stale=True, stale_reason="..." rather than 503. When ABS goes down (and ABS goes down), our cache serves the last-known-good response and tells you it's stale. No customer has complained that they wanted a hard error instead. Many have thanked us for not breaking their downstream dashboards at 02:00 Sydney.
  1. Free tier with the same endpoints as paid. Every tier gets every endpoint, every history depth, every signal. Tiers differ only on volume. This was a strategy bet, paid users pay for headroom, not access, and it's paying off. Free-tier users convert when they hit the ceiling; nobody upgrades to "get" a feature they couldn't see at all.

What we'd do differently

  • Start with 5 customer interviews, not 2. Saved time, less rewriting.
  • Unit-suffix every numeric field on day 1. Don't ship value unless you mean it.
  • Run uvicorn locally with --workers 2. Single-worker hides threading bugs that bite in prod.
  • Test the auth middleware with every cache path. Auth-then-cache, never the other way around.

What this isn't

This post isn't a generalisable startup playbook. It's a write-up of one indie-API's first 30 days. Your bugs will be different. The shape of the lessons is probably similar.

Pricing

  • Free: 500 calls/month. The same endpoints paying customers get.
  • Analyst: $29/mo, 10k calls.
  • Pro: $99/mo, 100k calls, webhooks.

Free key in 60 seconds at ausdata.io.

Sources

All posts · Get a free key · Docs