Ship AI features without shipping your API keys.
Perishable is a small Node proxy that sits between your frontend and any OpenAI-compatible API. Your secret key stays on the server. Clients get short-lived, fingerprint-bound JWTs that expire on their own — like a tub of yoghurt.
The problem
You want to call OpenAI (or Anthropic, or OpenRouter, or a local
Ollama) from a browser, an iOS binary, or an Electron app. The
naive path puts your sk-… key into something a
stranger can dump with strings.
A scraped key gets you a billing surprise and a key rotation sprint. Both are avoidable.
The shape
Run npx perishable-proxy next to your app. The proxy
holds the real key. The client SDK collects browser entropy and
asks for a session. The session is a JWT bound to a fingerprint,
with a short TTL, behind a rate limiter.
It's a process, not a SaaS. Your traffic doesn't leave your infra.
What actually happens
Drawn from the project README. The exact endpoints, header names and token TTLs are in the docs.
Five things, on by default
Client fingerprinting
A stable ID derived from browser characteristics. The session JWT is bound to it, so a copied token from a different browser is rejected.
Entropy collection
The client SDK watches for real input (mouse, keyboard) before
it's allowed to mint a session. A headless scraper sitting at
document.ready gets nothing.
Short-lived JWT sessions
Tokens expire on a clock you control. The client SDK refreshes
them before expiry (sessionOptions.expiryBuffer).
Leaked tokens rot.
Per-client rate limits
Configurable points-per-window with optional block duration. Enough to stop a casual abuser from racking up an OpenAI bill while you sleep.
OpenAI-compatible surface
If a provider speaks the OpenAI API shape — OpenAI itself,
Anthropic via compatible base URL, OpenRouter, Ollama — point
OPENAI_BASE_URL at it and go.
CLI and SDK
npx perishable-proxy for the 90% case;
new server.PerishableServer({…}) when you need to
embed it in an existing Node service.
What Perishable is not
- It is not an analytics / observability layer. It proxies; it does not aggregate spend dashboards per tenant.
- It is not a hosted gateway. You run the process. Traffic flows through your infra, not Skelf-Research's.
- It does not eliminate the need for server-side auth on sensitive endpoints. A short-lived JWT raises the cost of abuse; it does not authorise a user to delete their account.
- Fingerprint + entropy is bot-resistance, not bot-proof. A motivated attacker with a real browser can still get a session. They just can't get a million of them cheaply.
Install, set a key, point a client at it
npm install perishable
# Start the proxy (OpenAI by default)
OPENAI_API_KEY=sk-... npx perishable-proxy
# Or proxy Anthropic / OpenRouter / Ollama instead
OPENAI_API_KEY=sk-ant-... \
OPENAI_BASE_URL=https://api.anthropic.com/v1 \
npx perishable-proxy import { client } from 'perishable';
client.PerishableOpenAI.initEntropyCollection();
const ai = new client.PerishableOpenAI({
proxyUrl: 'https://your-proxy.example.com'
});
const res = await ai.createChatCompletion({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }]
}); That's verbatim from the README. The rest — config file, programmatic server, refresh tuning — is in the docs.
From the workbench
Why a proxy beats a serverless function for AI key safety
A serverless function can hide your API key. It will also cost you streaming, latency, observability, and your sanity. Here is the trade space, drawn honestly.
Short-lived tokens for AI: the OAuth-style answer
OAuth solved this problem for the web a decade ago. Here is what the same idea looks like wrapped around an AI provider's API.
Your client-side AI key is in 4 mobile screenshots already
A walkthrough of how API keys baked into mobile and browser bundles get extracted in minutes, and what to do other than pray.