Self-hosted REST endpoints for warehouse data

Stop sending the same product API reads to your warehouse.

Your warehouse builds the data. Your product should not query it on every request. Offloader serves snapshots from REST endpoints on your own servers — so repeat reads stay cheap until the next refresh.

Runs in your environment. Validated on real production data: dozens of endpoints, ~136M requests a month, and 8 TB served. No Offloader cloud — private data stays with you by default.

Same endpoint, billed on repeat. Illustrative — use your own numbers

How it works

Build the data once.
Read it many times.

Your pipeline exports the answer set to S3 or GCS. Offloader loads it and handles API reads until the next snapshot is ready. The warehouse is used by the export job, not by each customer request.

On your schedule Your warehouse Snowflake · Databricks · BigQuery
snapshot
Object store Parquet + manifest S3 · GCS
load locally
Self-hosted Offloader · DuckDB checked · safe swap
REST API
Every request Your app product API · front-end
Product reads are served entirely from the snapshot — your warehouse is not called for each API request.
  1. 01

    You publish a snapshot

    Export the data to Parquet in S3 or GCS with a small manifest. Pick the freshness you need: 15 minutes, hourly, daily.

  2. 02

    Offloader checks and loads it

    The container checks the files and schema, then loads the latest snapshot into DuckDB before it serves any request.

  3. 03

    Your app calls a REST endpoint

    Your app calls a named endpoint. Callers do not send SQL; API keys decide which endpoint, tenant, and columns they can read.

  4. 04

    A newer snapshot swaps in

    When a newer snapshot passes validation, Offloader switches to it without taking the API down. If it fails, the last good snapshot keeps serving.

Every response tells you exactly what you read
{
  "data": [ { "account_id": "acct_zephyr", "api_calls_total": 56839 }, … ],
  "meta": {
    "endpoint":    "customer_usage_summary",
    "snapshot_id": "2026-06-01T00:00:00Z_r0007",   // which snapshot answered this
    "generated_at": "2026-07-01T18:21:10Z",   // server time — spot a cached CDN hit
    "freshness":   { "watermark": "2026-06-01T00:00:00Z", "age_seconds": 2658070 }
  }
}

What you serve

API reads that are
expensive in the warehouse.

Good candidates have a fixed shape, repeat a lot, and can be 15-120 minutes fresh. Offloader becomes the origin your app or CDN calls instead of warehouse compute.

  • Customer analytics APIs

    /accounts/:id/usage, performance summaries, and insight endpoints your product calls while a customer waits.

  • Usage metering & billing

    Per-account counters that power invoices and the in-app usage meters users watch.

  • Personalization & recommendations

    “Recommended for you,” trending, and related items — computed in the warehouse, served per user through a fixed product endpoint.

  • Public stats & leaderboards

    /leaderboards/current, rankings, counts, and public profile pages that get hammered by traffic.

  • Embedded reports

    Customer report endpoints with known filters, date windows, and result limits — not ad-hoc SQL.

  • Search facets & filters

    /search/facets counts, slices, and filter totals that need freshness, not warehouse latency.

What this replaces

The read service teams
usually build by hand.

Offloader is not a broad database platform. It is the small read service you would otherwise build yourself: define the endpoint, load the snapshot, serve the response, and track freshness and cost.

  • Warehouse-backed product endpoints

    The API routes that currently call Snowflake, Databricks, or BigQuery while a customer waits.

  • Homegrown serving caches

    Redis, Postgres, DuckDB, or custom cache jobs glued together without a manifest contract, rollback, or endpoint docs.

  • Read-only serving databases

    A second system to load and operate when the workload is read-only, snapshot-fresh, and a REST API is all your app actually needs.

  • Manual governance in app code

    Tenant filters, endpoint allowlists, and column limits scattered across handlers instead of compiled into the serving contract.

  • Public-data origin servers

    High-volume stats and leaderboard origins that should be flat-cost and cache-friendly before a CDN ever sees them.

  • Spreadsheet-style exports in product

    Customer report downloads where the shape is fixed, the source is analytical, and the request should not spin up warehouse compute.

Also for AI agents

The same endpoints,
readable by an agent.

Your app reads these endpoints over REST. Set one environment variable and an AI assistant can read the same ones — through the same API keys, the same tenant rules, and the same fixed queries. It is a second door onto what you already serve, not another thing to define. Offloader speaks the Model Context Protocol; see the agent guide.

  • Nothing new is exposed

    An agent sees the endpoints you configured, with the same parameters and the same allowed columns. There is no way to send SQL.

  • Your keys still decide

    An API key sets which endpoints an agent may list and read, and whose rows come back. An endpoint a key cannot use never shows up.

  • Public reads still cache

    A public read carries its whole cache key in one header, so a CDN can answer it without touching your server. Per-customer reads are never marked shareable.

One read — the cache key rides in the header
POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: resources/read
Mcp-Name:   offloader://customer_usage_summary?from=2026-05-30   // a CDN caches on this alone

{
  "contents":   [ … the same rows your REST endpoint returns … ],
  "ttlMs":      7200000,     // good for two hours
  "cacheScope": "public"     // "private" as soon as a customer is involved
}

Proof

Measured against real production data.

8 TB / mo

Real scale, not a fixture

Validated on real production datasets — dozens of production endpoints, cold-booted from an object-storage bucket and serving 8 TB of traffic a month.

p95 66 ms

Measured in production

p50 37 ms, p95 66 ms at the load balancer, across ~136 million requests a month — down from multi-second behind the warehouse of a top-tier vendor.1

tenant = you

Isolation is compiled in

The tenant filter is inserted server-side from the caller’s key. A request cannot widen it or read another tenant’s rows — no arbitrary SQL, ever.

0 downtime

Config that reloads live

Push config to a bucket and it hot-reloads with no restart — blue-green even when a schema changes. A broken revision is ignored; the running one keeps serving.

1. Latency from a real production deployment — ~136 million requests a month, 8 TB served, ~94% cached, on two small VMs, measured at the load balancer. Your latency and savings depend on your data, payloads, and hardware; the benchmark method and harness are in the docs.

Pricing

Priced on measured savings.
Not on requests.

First we measure the endpoints that can actually move off the warehouse. Then we compare the current warehouse cost with the cost of running Offloader. For workloads we can offload, the ongoing fee is 20% of the measured saving. You keep the other 80%. If there is no saving, there is no ongoing fee.

If you don’t save, we don’t earn.

A real cutover What that $440 served in real life: 136M requests a month, 8 TB, 94% cached — at the CDN edge, with no unplanned downtime since cutover, on the customer’s own two VMs.

01

How the ongoing bill works

The paid diagnostic exists because “savings” has to survive finance review. We agree which reads are offloadable, what they cost today, how many requests they serve, and which savings are real versus only possible. After that, the fee is 20% of measured savings, reconciled quarterly, after subtracting the cost of running Offloader. For reserved-capacity warehouses, fees start only after you actually downsize.

Prefer a number finance can book? The same diagnostic can quote a fixed annual license instead — same product, no reconciliation.

Estimate your savings →

02

Public data? Serve it from our edge.

If the data is already public, like leaderboards, counts, or public profile stats, we can also serve it from our global CDN edge. Private data stays out of that path. This is optional, quoted per case, and uses the same share-of-savings model.

Book a paid diagnostic Books a call with me — pick a time that works; you talk to a person, not a queue. Fixed scope; we measure the spend that can actually move before you commit.

Fit

Made for one job.
Honest about the rest.

Offloader handles repeated API reads with a known shape. It is not a warehouse, a BI tool, a streaming database, or a place to run ad-hoc SQL. If native acceleration or your BI cache already solves the problem, we’ll tell you.

A fit when

  • Your app calls the warehouse while a user is waiting.
  • The same endpoint shapes repeat a lot.
  • 15-120 minute freshness is fine.
  • You can export snapshots to S3 or GCS.
  • You want to cut warehouse cost without rewriting the product.

Not a fit when

  • It’s an internal BI dashboard and the BI tool’s cache or extracts already solve refresh.
  • Every query is different or ad-hoc.
  • You need up-to-the-second data.
  • You can’t produce snapshots.
  • Native warehouse acceleration already covers it.

FAQ

The questions we actually get.

How it’s different

Isn’t this just a cache in front of my warehouse?

Not exactly. A normal cache still needs a warehouse-backed origin for misses, and it usually does not know which tenant or columns a caller may read. Offloader answers from the snapshot only. The endpoint is named in config, the caller cannot send arbitrary SQL, and tenant and column limits are applied before the query runs.

We already run a CDN in front of it — isn’t that enough?

Keep the CDN. It helps when many users ask for the exact same response. The problem is the origin behind the CDN. If a cache miss still goes to the warehouse, a busy endpoint can still send millions of reads to metered compute. Offloader makes the origin a small service you run. It emits proper ETag/Cache-Control headers, so the CDN keeps caching, and the misses land on Offloader instead of the warehouse.

How is this different from Databricks Lakebase?

Lakebase is managed Postgres inside Databricks. It can be the right answer if you are all-in on Databricks, need read-write tables, or want Postgres clients. Offloader is narrower: one self-hosted, read-only container that serves approved Parquet snapshots from Snowflake, BigQuery, Redshift, Spark, Databricks, or any pipeline that can write the files. The API is REST, not Postgres. The trade is narrower scope, simpler deployment, and no managed database bill for this read path.

Couldn’t we build this ourselves?

Yes, if the scope is small. DuckDB behind an HTTP handler is not the hard part. The expensive part is everything that makes it safe to run: rejecting half-written exports, checking schema changes, swapping snapshots without downtime, preventing tenant bypass, keeping the last good snapshot on failure, generating docs, exposing diagnostics, and proving the before/after cost. Offloader gives you that surface on day one.

How is this different from ClickHouse, Tinybird, or warehouse-native acceleration?

Use a dedicated serving database when you need live OLAP, ad-hoc queries, or a new serving data model. Offloader is for the narrower case: the warehouse already produced the answer set, the API shape is known, and the data can be snapshot-fresh. Your warehouse remains the source of truth; Offloader serves its snapshots from a flat-cost box you own.

Will it fit your data

How fresh is the data?

As fresh as your snapshot schedule. The normal target is 15-120 minutes. Every response includes its snapshot_id and freshness watermark, so clients know exactly how old the data is. If you need up-to-the-second data, use a streaming or transactional serving path instead.

How do isolation and security work?

The API key decides what a caller can read. The server inserts the tenant filter before the query runs, so the caller cannot widen it. Keys are stored as SHA-256 hashes, endpoints are allow-listed per key, and a public (auth: none) mode is only accepted when no endpoint is tenant-scoped. Your private data never leaves your environment. Read the security model →

My data is per-user — can a snapshot handle the cardinality?

Yes, if the total data size fits the serving shape. A million users can share one snapshot because each request reads only its tenant slice. The thing to size is total bytes, not tenant count. In validation, the active datasets used about 4 GiB of RAM on one instance. If the working set will not fit a reasonable box, we should disqualify it or partition the snapshots.

Which warehouses does it work with?

Any source that can publish Parquet plus a small manifest: Snowflake, BigQuery, Redshift, Spark, Databricks, or your own pipeline. Offloader does not query the warehouse when a user calls your API. It reads only the snapshots you publish.

What does it actually take to run?

A container and a bucket. The workload we validated against runs ~136 million requests a month; a single small instance sustains that comfortably — about 52 requests a second on average, well inside the ~2,500/s a single 4-vCPU instance hit in benchmark — so you’d run two small instances for HA, not for capacity. The cost is the box, flat, no matter how many reads hit it: the expensive computation already happened once, in your pipeline.

Cost & commitment

Is there lock-in?

No. It’s a container you run on your own infrastructure; the config is a handful of YAML files and the data is your own Parquet in your own bucket. Turn it off and your warehouse, data, and app are exactly as they were.

How does pricing actually work?

A paid diagnostic measures which reads can move and what they cost today. You agree a baseline before committing. From there it is 20% of measured savings, after subtracting what Offloader costs to run on your infrastructure. You keep the other 80%. If quarterly reconciliation is annoying, the same diagnostic can quote a fixed annual license. No per-request meter. See pricing →

Is it free to self-host?

Under $1M in annual revenue, yes — run it in production for free under the Business Source License. Past that line, production use needs a commercial license — the paid engagement above. Either way the source is public and every release turns Apache-2.0 two years after it ships.

Who’s behind Offloader — and what if it’s just you?

Offloader is built and supported by Andrew Dryga. It’s deliberately the kind of thing that survives a solo maintainer: the code is public on GitHub, it runs entirely on your own infrastructure, and there’s no lock-in — if we vanished tomorrow, your container, config, data, and app keep working exactly as they are. Support is a response-time commitment, not an uptime SLA (you run it, so uptime is yours); you email a person, not a queue.

Are you insured?

Yes. The diagnostic and pilot are backed by $1M professional indemnity (errors and omissions) insurance, valid worldwide including the US and Canada, underwritten by Colonnade (a Fairfax company). The pilot contract caps liability to match, and we’ll send a certificate of insurance to your procurement team on request.

Get started

Run a real endpoint in fifteen minutes.

Install the CLI, start the public demo, and call an endpoint backed by a snapshot. No credentials, clone, or build step. When you’re ready, point the same flow at your own bucket.

Weighing a real migration? A paid diagnostic groups your repeated reads and estimates what can actually move — before you commit. Estimate it yourself or book one directly.

Talk to a person

See the number before you commit.

A paid diagnostic measures which reads can move, what they cost today, and what Offloader would cost to run. You get a baseline you can defend before starting a pilot.