The proxy holds the key. The client holds a receipt that expires.
Perishable is a process between your frontend and an OpenAI-compatible API. Here is exactly what moves where, and — just as important — what this design does not promise.
Two round-trips, one secret that never travels
The sk-… key exists only on the right-hand side of the
proxy. The browser sees a JWT that stops working within minutes.
What each stage does
1 · Entropy handshake
The client SDK collects real input signals before requesting a session. The proxy checks that entropy looks human before it mints anything, raising the cost of headless automation.
2 · Fingerprint-bound JWT
On success the proxy issues a short-lived JWT bound to the client fingerprint. A token lifted to another browser fails the binding check.
3 · Proxied call
The client calls the OpenAI-compatible surface with the JWT. The proxy validates it, applies the rate limit, and forwards upstream with the real key attached server-side.
4 · Expiry & refresh
The SDK refreshes the session before it expires (expiryBuffer). A leaked token is useless within its short TTL — the perishable part.
What this stops — and what it doesn't
Stops
- Your
sk-…key ending up in a JS bundle, iOS binary, or Electron app. - A scraped long-lived credential being replayed for weeks.
- Casual, high-volume abuse from headless scripts and copied tokens.
Does not stop
- A determined attacker driving a real browser — they can still get a session; they just can't get a million cheaply.
- The need for per-user auth on sensitive actions. A JWT here is not a login.
- Spend tracking — Perishable is not an analytics or observability layer.
Fingerprint + entropy is bot-resistance, not bot-proofing. It raises the cost of casual abuse, which is the common failure mode — not the cost of a determined adversary.
Run it yourself
Follow the quickstart, or read the endpoint and config reference in the docs.