KeyPulse pools every free-tier and paid AI key you own behind one local OpenAI-compatible endpoint, measures all of them continuously, and routes each request to whichever key is healthiest right now — failing over the instant one rate-limits.
A desktop app. Runs entirely on your machine. No account, no proxy service, no telemetry.
Collecting free AI inference keys is easy. Groq, Cerebras, SambaNova, Together, Mistral, Gemini, OpenRouter, and a new one every other week on Hacker News. Living with them is the hard part.
Free tiers are unstable by design. A key that answered in 300 ms this morning is rate-limited by lunch, silently degraded by dinner, and revoked on Friday. So you end up doing the same three things by hand, forever:
- Switching keys. Comment out one
base_url, paste a different key, restart the agent, hope. Repeat when that one dies. - Guessing. Which provider is actually fastest for you, on your connection, at this hour? Nobody's public benchmark answers that. Yours would, if you had one.
- Finding out too late. A new provider launches with a generous free tier and you hear about it three weeks after the thread scrolled off the front page.
KeyPulse turns all three into background processes.
It probes every key on a schedule with one small chat completion, and from that
single call records time-to-first-token, total latency, tokens per second, and
rolling jitter. It scores each key against the others. Then it exposes
http://127.0.0.1:8787/v1 — plain OpenAI API — and sends each request to the
best candidate, moving to the next one on a 429, an auth failure, a timeout or a
5xx, with exponential backoff that honours Retry-After. Your agent, your
editor plugin, your script: one base URL, changed once, and never again.
A second tab watches GitHub, Hacker News and any RSS feed you pin for new free and trial inference offers, so the next Groq shows up as a card with an Add to my providers button instead of a thread you missed.
Nothing leaves your machine. Keys are sealed with AES-256-GCM under a master key in your OS keychain. State is a SQLite file in your app data directory. There is no KeyPulse account, no hosted control plane, and no telemetry — the only network calls are to the providers you added and the discovery sources you enabled.
http://127.0.0.1:8787/v1 speaks the OpenAI API, so any SDK, CLI or editor
plugin can point at it unchanged.
curl http://127.0.0.1:8787/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"keypulse-auto","messages":[{"role":"user","content":"hi"}]}'keypulse-auto (or auto, or an empty model) means you choose — KeyPulse picks
per the current strategy and each key's own default model. Name a model
explicitly and routing narrows to the keys that list it. Non-OpenAI providers are
translated in both directions, streaming included: a chat stream is re-encoded
frame by frame, not buffered.
| Endpoint | Purpose |
|---|---|
POST /v1/chat/completions |
OpenAI-compatible, streaming or not |
POST /v1/embeddings |
OpenAI-compatible (OpenAI-shaped keys only) |
GET /v1/models |
every model across your routable keys, plus keypulse-auto |
GET /health |
how many keys are routable and which is next |
GET / |
a one-glance status page for the gateway itself |
A background scheduler probes every enabled key on your interval with one small chat completion. From that single call it records time-to-first-token, total latency, tokens/sec, and rolling jitter (standard deviation over the recent window) — deliberately not a large-payload download test, which measures your network rather than the provider.
Failures are classified as rate limit, auth, timeout, server, network, bad request or bad response. A 429 puts the key in cooldown so the gateway stops choosing it. Quota comes from the provider's rate-limit headers when it sends them and from figures you enter when it doesn't, with a time-to-exhaustion estimate based on your observed usage.
Best composite score · lowest latency · fastest TTFT · highest throughput · lowest error rate · round robin · weighted round robin · strict priority · random.
The composite score weights success rate .4, latency .3, throughput .2 and jitter .1 by default — all four adjustable. Whatever you pick, Settings shows the exact candidate order the strategy produces right now, before a single request depends on it.
The Benchmark card is separate and manual. Run on every key, or the gauge button on one row, fires several requests at that key on demand — the same small prompt, repeated. Use it to answer "which of these is fastest for me?" without waiting for the probe schedule.
Discovery runs on a schedule or on demand across:
- GitHub — the documented search API, over rotating queries about free LLM API lists.
- Hacker News — the Algolia search API.
- Pinned sources — RSS/Atom feeds, GitHub markdown lists and plain pages you add. Five community feeds are seeded on first run and can be deleted.
- Web search — optional, only if you store a Brave or Tavily key.
Findings pass a relevance gate (it has to look like an AI/LLM API and like a free or trial offer) and become cards: name, website, docs, base URL, whether it looks OpenAI-compatible, free-tier details, pricing notes, models, context length, capabilities, first-seen, last-verified, source link. Cards are de-duplicated against each other and against the keys you already have. Each one has a one-click Add to my providers → that opens the Tab 1 dialog pre-filled — you only paste the key.
On politeness: only official APIs, public feeds and documented endpoints. A
pinned page is fetched only after its host's robots.txt says a generic agent
may; if it says no, the source is reported as skipped rather than worked around.
One source failing never fails a run. Findings are leads, not facts — verify a
free tier on the provider's own pricing page before you rely on it.
There are excellent tools in this space. KeyPulse overlaps with them and is not trying to replace all of them.
LiteLLM is the more complete production gateway — more providers, more routing knobs, team/spend management, Kubernetes-native. If you are putting a gateway in front of a product, use LiteLLM. Helicone is an observability platform first: it is the better answer to "what did my app send last Tuesday." OpenRouter is the least work of all — one key, hundreds of models, someone else's problem.
KeyPulse is for the case none of them target: you, on your own laptop, with a drawer full of free-tier keys and no infrastructure. Three things follow from that.
1. Zero infrastructure. No Docker, no Postgres, no Redis, no YAML, no account. Install the app, paste a key, done. State is one SQLite file.
2. Your keys never leave the machine. They are sealed under your OS keychain and used only for calls you initiated. There is no vendor in the request path because there is no vendor.
3. It tells you about providers you don't have yet. Every other tool manages the keys you already own. Provider Radar is how you get the next one.
And because the probes run from your connection, the numbers are about your actual situation — not a status page in another region.
Checked against the linked official docs on 2026-07-30. Corrections by PR are welcome; if something below is wrong or out of date, open an issue and it gets fixed.
| KeyPulse | LiteLLM | Helicone | OpenRouter | |
|---|---|---|---|---|
| Shape | Desktop app | Self-hosted server (Docker / K8s) | Hosted service, also self-hostable | Hosted service |
| Works with no account anywhere | ✅ | ✅ | Self-hosted only | ❌ — account + credits |
| Infrastructure to run it | None (one SQLite file) | Postgres + Redis for production | Postgres · ClickHouse · Minio | None (it's hosted) |
| Keys stay on your machine | ✅ OS keychain | ✅ your infra | ✅ self-hosted | Encrypted server-side (BYOK) |
| OpenAI-compatible endpoint | ✅ local | ✅ | ✅ | ✅ |
| What gets ranked | your individual API keys | model deployments in config.yaml |
providers / models | provider endpoints for a model |
| Latency-aware routing | ✅ 9 strategies | ✅ latency-based-routing, cost, usage, least-busy |
Routing across 100+ models | ✅ sort: "latency" / "throughput", preferred_max_latency |
| Active health probing | ✅ scheduled, per key | ✅ background_health_checks (300 s default) |
— | Deprioritizes providers with outages in the last 30 s |
| Per-key TTFT + jitter history | ✅ | — | Latency tracking in dashboards | Rolling 5-min latency/throughput percentiles, per provider |
| Automatic failover on 429 | ✅ backoff + Retry-After |
✅ cooldowns + fallbacks | ✅ | ✅ |
| Discovers new free-tier providers | ✅ | ❌ | ❌ | ❌ |
| Fee on top of provider cost | None | None | 0% markup on gateway credits | No token markup; ~5% on credit purchases, and 5% on BYOK above 1M req/mo |
| GUI included | ✅ the whole product | Admin UI | ✅ | ✅ |
| License | MIT | MIT (separate licence for enterprise/) |
Apache-2.0 | Proprietary service |
— means we could not find it documented on the official pages linked in the header row. It may well exist; absence here is absence of evidence, not a claim about the product.
One distinction the table can't show: OpenRouter's latency and throughput percentiles are measured across its own fleet, and LiteLLM's health checks run from wherever you deployed it. KeyPulse's numbers are measured from the machine you are sitting at, per key, which is the only vantage point that answers "will this be fast for me right now."
Under two minutes from clone to a running gateway.
- Rust 1.82 or newer (
rustuprecommended) - Node.js 20.19+ or 22.12+ and npm
- Platform build dependencies for Tauri v2:
- Windows — WebView2 runtime (preinstalled on Windows 11) and the MSVC build tools
- macOS — Xcode Command Line Tools (
xcode-select --install) - Linux —
webkit2gtk-4.1,libayatana-appindicator3,librsvg2,patchelf(see the Tauri prerequisites for your distribution's exact package names)
git clone https://github.com/Matinj031/KeyPulse.git
cd KeyPulse
npm install
npm run app:dev # Vite dev server + the Rust core, with hot reloadThe first launch creates the database and a master key. On a machine with no usable keychain (a bare Linux container, say) KeyPulse falls back to a file-backed master key with restricted permissions and tells you so in Settings.
Prebuilt installers are not published yet — npm run app:build produces them into
src-tauri/target/release/bundle/ if you want one now. Watch
releases for the first tagged
build.
- Add key, top right of Tab 1.
- Name it something you'll recognise —
Groq — personal, notkey 1. Several keys may share a name; they are still scored separately. - Base URL: the API root, e.g.
https://api.groq.com/openai/v1. Pasting the full.../v1/chat/completionsalso works. - Wire format: OpenAI-compatible covers Groq, Together, OpenRouter, Cerebras, SambaNova, DeepInfra, Fireworks, Mistral and most others. Pick Anthropic or Gemini for those two.
- Authentication:
Bearerfor almost everything; Custom header for providers that wantx-api-keyor similar. - API key: pasted once, encrypted before it touches disk. On a later edit, leaving the field blank keeps the stored key.
- Custom headers (optional):
Name: value, one per line — sent on probes, model listing and every proxied request for this key. For gateways that want a tenant id, a beta flag, or a particularUser-Agent.⚠️ These are stored and exported in the clear, so the key itself belongs in the field above. - Models: one per line, or hit Fetch from provider once the key is saved
to pull them from the provider's own
/models. The probe model is the one health checks call — pick the cheapest, fastest one. - Capabilities: the gateway refuses to route a request a key cannot serve, so tick only what the key actually supports.
- Save. The first probe runs on the next cycle, or immediately via Probe all now.
Enable the proxy in Settings, then change one line:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="unused")
client.chat.completions.create(
model="keypulse-auto", # or a specific model id
messages=[{"role": "user", "content": "hi"}],
)The listener binds to loopback only, so it is not reachable from the network. That is not sufficient on a shared or multi-user machine — any local process can reach a loopback port — so Require a token in Settings adds a bearer token the client must present. Turn it on if you are not the only user of the box.
src/ Vue 3 + TypeScript frontend
views/ GatewayView · RadarView · SettingsView
components/ provider table, charts, dialogs, ui/ primitives
stores/ Pinia: health · discovery · settings · toast
lib/ typed invoke() wrappers, shared types, formatters
src-tauri/src/ Rust core
commands.rs the Tauri command surface the frontend calls
proxy.rs local OpenAI-compatible endpoint + failover loop
health.rs probe scheduler, error classification, quota parsing
discovery.rs the radar's sources, relevance gate and de-duplication
scoring.rs one key's samples → score, status, series
routing.rs a health snapshot → an ordered candidate list
adapters/ per-provider wire formats (openai · anthropic · gemini)
db.rs / crypto.rs SQLite storage and secret sealing
state.rs shared app state, event emission, proxy handle
Data flows one way. The Rust core owns all state; the frontend reads snapshots
through invoke and receives pushes on three events (keypulse://health,
keypulse://failover, keypulse://discovery), so both tabs stay live without
polling.
scoring and routing are pure functions over plain structs — no clock, no
network, no database. That is what makes the decision logic testable without
mocks, and it is why the routing preview in Settings can show you the real
candidate order instead of an approximation of it.
The adapter is the one abstraction worth investing in. Everything above it
speaks the OpenAI chat-completions shape; an adapter is the only thing that knows a
provider's real wire format. Supporting a new non-OpenAI API means one new file
implementing eight small methods, plus a variant in AdapterKind. There is no
repository layer, no DI container and no DDD scaffolding — db.rs is a thin typed
wrapper over SQL, and the modules that need it take a &Db.
Why this stack
| Choice | Reason |
|---|---|
| Tauri v2 over Electron | API keys belong in the OS keychain, not in a Chromium profile. A real systems language for the network layer, and no bundled browser to keep patched. |
| Rust core | The gateway is a long-lived server doing concurrent I/O with a hard requirement that one provider's failure never takes the app down. tokio + reqwest + typed errors make that boring. |
| axum for the gateway | Same async runtime as the rest of the core, and streaming bodies are a first-class type — a chat stream is re-encoded frame by frame, not buffered. |
| sqlx + SQLite | Probe history is time-series data that outlives the process. SQLite is a file with no daemon, and every query here is parameterised — key material and user text never reach a SQL string. |
| keyring + AES-256-GCM | The keychain holds one master key; each API key is sealed under it. A stolen database file is useless on its own. |
| Vue 3 + Composition API | Two dense dashboards with a lot of live state. <script setup> with strict TypeScript keeps components short, and Pinia setup stores read like the functions they are. |
| Tailwind v4 + shadcn-vue (reka-ui) | Unstyled, accessible primitives we own the markup of, rather than fighting a component library's opinions about a data-dense table. |
| vee-validate + Zod | One schema states the provider rules, and the same object types the form and the invoke payload. |
| uPlot + vue-echarts | uPlot draws a hundred sparklines without breaking a sweat; ECharts is the better tool for the interactive historical charts. Different jobs, different libraries. |
| GSAP | Small, deliberate transitions on state change — a key going red should be noticeable without being a distraction. |
Everything is in Settings inside the app; there is no config file to edit. Values are clamped by the core, so a typo cannot put the app in a bad state.
Health checks, gateway, notifications, discovery, backup
Health checks
| Setting | Default | Range |
|---|---|---|
| Probe interval | 90 s | floor of 30 s; a warning below 60 s |
| Probe timeout | 20 s | 5–120 s |
| History retention | 7 days | 1 hour – 90 days |
| Rolling window | 20 samples | 3–200 |
| Rate-limit cooldown | 120 s | 10–3600 s |
| Benchmark runs | 5 | 1–20 |
| Score weights | success .4, latency .3, throughput .2, jitter .1 | any non-negative set |
Local gateway
| Setting | Default | Range |
|---|---|---|
| Enabled | off | — |
| Port | 8787 | ≥ 1024 |
| Routing strategy | best score | 9 strategies |
| Max attempts per request | 3 | 1–10 |
| Request timeout | 300 s | 10–3600 s |
| Backoff base | 250 ms | 50–5000 ms, doubling, capped at 8 s |
| Require a token | off | — |
Notifications — on by default, for keys going down and for quota warnings at 15% remaining (1–90%).
Discovery — off by default; interval 12 h (1 h – 7 days); GitHub, Hacker News and pinned sources individually switchable; optional Brave or Tavily key.
Backup — export your configuration as JSON, with or without the keys. With
keys, every key in the bundle is sealed under a passphrase you choose
(PBKDF2-HMAC-SHA256 + AES-256-GCM, at least 8 characters); lose the passphrase and
the keys in that file are gone. Exports are also written to an exports/ folder in the data directory and
offered back as import sources, so the usual restore needs no file picker. Import
merges by default — a provider you already have is skipped rather than duplicated —
or, in replace mode, deletes every provider and pinned source first. Replace asks
for confirmation; export first if you might want it back.
Files and environment
| Database, master-key fallback, exports | %APPDATA%\dev.keypulse.app\ (Windows) · ~/Library/Application Support/dev.keypulse.app/ (macOS) · ~/.local/share/dev.keypulse.app/ (Linux) |
| API keys | OS keychain (Keychain / Credential Manager / Secret Service) |
KEYPULSE_LOG |
log filter, e.g. KEYPULSE_LOG=keypulse=debug |
- API keys are sealed with AES-256-GCM under a master key held in the OS keychain. Where no keychain is available, the master key falls back to a file with restricted permissions and the app says so in Settings.
- Keys are masked to their last four characters everywhere in the UI. The plaintext is decrypted only for a request you asked for, and revealing one is an explicit action.
- Raw keys are never written to logs. Provider error messages sometimes echo a key back; those strings are redacted before they are stored or logged.
- The gateway binds to
127.0.0.1only. Enable the bearer token if other people or untrusted processes share the machine. - Custom request headers are stored and exported in the clear — only
api_keyis encrypted. Put secrets in the key field, not in a header. - Exported bundles contain no plaintext keys unless you ask for keys and supply a passphrase.
Found something? Open a regular issue for anything non-sensitive. For a suspected vulnerability, please report it privately via GitHub security advisories rather than a public issue.
KeyPulse is built to be a good citizen of the free tiers it watches, and it is your responsibility to keep it that way.
- Probing costs the provider something. Each health check is one small chat completion, but multiplied by your key count and divided by your interval it adds up. The interval has a hard floor of 30 seconds and warns below 60; on a free tier, minutes are usually more appropriate than seconds. Providers with tight daily caps should get a longer interval, and you can stop probing a key by disabling it.
- Read the terms of the keys you add. Some free tiers prohibit sharing a key across users, automated load distribution, or use in a service that resells access. Pointing the local gateway at your own tools is not the same thing as pooling keys for other people, and only you can tell which side of a given provider's terms you are on.
- Rate limits are signals, not obstacles. A 429 puts a key in cooldown; that is
the intended behaviour, not something to tune away. KeyPulse never retries a key
faster than the
Retry-Afterit was given. - The radar does not crawl. Documented APIs, public feeds and pages you pinned
yourself. It checks
robots.txtbefore fetching a pinned page and reports a source as skipped when it is disallowed. Please don't add sources whose terms forbid automated access.
npm install # frontend deps
npm run app:dev # the real thing: Rust core + Vue, hot-reloaded
npm run dev # frontend only — Tauri APIs are absent, so most of the app is inert| Command | What it does |
|---|---|
npm run app:dev |
Run the full desktop app in dev mode |
npm run app:build |
Build the installer for your platform into src-tauri/target/release/bundle/ |
npm run typecheck |
vue-tsc --noEmit |
npm run build |
Typecheck, then build the frontend bundle |
cd src-tauri && cargo test |
The Rust test suite |
cd src-tauri && cargo clippy --all-targets |
Lints, including test code |
cd src-tauri && cargo fmt |
Format |
Tests. 120 tests, green at the time of writing, over scoring, routing, probe
error classification, crypto round-trips, the command surface, discovery parsing and
de-duplication, all three adapters, and the proxy's failover behaviour — retryable
classification, attempt clamping, backoff doubling and its cap, Retry-After, and
SSE frames that arrive split across chunks. Because scoring and routing are pure
functions, those tests are plain tables of input and expected output with no mocks in
sight.
There is no CI yet — see the roadmap. Until there is, please run cargo test,
cargo clippy --all-targets and npm run build before opening a pull request.
Nothing below is a dated promise. It is the order in which things would land, and an
honest list of what today's 0.1.0 does not do.
In 0.1.0
- Local OpenAI-compatible gateway with 9 routing strategies and failover
- Scheduled health probing — TTFT, latency, throughput, jitter, error taxonomy
- Quota tracking from rate-limit headers, with time-to-exhaustion estimates
- On-demand benchmarking and historical charts
- Provider Radar — GitHub, Hacker News, pinned feeds, optional search APIs
-
openai,anthropicandgeminiwire formats - Keychain-sealed storage, per-provider custom headers, passphrase-sealed backups
Next
- CI on push —
fmt,clippy,cargo test,vue-tsc - Tagged releases with prebuilt installers for Windows, macOS and Linux, so running KeyPulse does not require a Rust toolchain
- Screenshots, GIFs and an architecture diagram — every image slot in this README is still a placeholder
- A native Anthropic
/v1/messagesendpoint on the gateway, so clients that speak that shape rather than the OpenAI one can route through it too - More non-OpenAI wire formats — AWS Bedrock and Vertex AI are the obvious next adapters; anything with an OpenAI-compatible endpoint already works today
- Tool-call and vision passthrough for the non-OpenAI adapters. Today the
Anthropic and Gemini adapters translate text only, so a request carrying
toolsor an image is routed only to keys whose wire format already speaks it. The gateway detects the need and filters the candidate pool rather than silently dropping the field — but the translation itself is not written yet - Rate-limit-aware pacing — spread gateway traffic to stay under a published limit, instead of reacting after the 429 has already been spent
- Headless mode — run the gateway without the window open
Ideas, not yet designed
- Per-model scoring instead of per-key, for providers whose models fail independently
- Price-aware routing for paid keys, alongside the latency-aware strategies
- A metrics endpoint for people who already run Prometheus or Grafana
Deliberately not planned: a hosted version, telemetry of any kind, and any feature whose purpose is sharing one key between several people.
Contributions are welcome, including the unglamorous ones. The repository is young — one commit, no releases — which is the best possible time to shape it.
Why it is a pleasant codebase to work in
- The decision logic is pure.
scoring.rsandrouting.rstake plain structs and return plain structs. No clock, no network, no database, so a change you make is a test you can write in a minute. - One abstraction, on purpose. No repository layer, no DI container, no DDD. If you are looking for where something happens, it is in the file named after it.
- The tests are real. 120 of them, including a socket-level test that pins header
precedence, because a provider once rejected requests over a
User-Agent. - MIT, no CLA. Your commit is yours.
Good first contributions
There is no issue tracker with labels on it yet, so here is the honest list of self-contained gaps:
| # | Gap | Where |
|---|---|---|
| 1 | A new provider adapter | src-tauri/src/adapters/ |
| 2 | A new routing strategy | routing.rs — a pure function and a table test |
| 3 | The CI workflow this repo does not have | .github/workflows/ |
| 4 | CONTRIBUTING.md, issue and PR templates, a code of conduct |
repo root, .github/ |
| 5 | Screenshots and GIFs for the placeholders above | docs/assets/ |
| 6 | A keyboard-navigation and ARIA pass on the provider table | src/components/ |
| 7 | A discovery source worth pinning that the seed list misses | discovery.rs |
| 8 | macOS and Linux verification | everything was developed and checked on Windows |
Adding a provider adapter, end to end
This is the extension point the architecture was built around. A provider that speaks the OpenAI chat-completions shape needs no code at all — add it in the UI with its base URL and auth scheme. A provider with its own wire format needs one file:
-
Add a variant to
AdapterKindinmodels.rsand wire it intoas_str. -
Create
adapters/yourprovider.rsand implementAdapter:Method Answers chat_urlWhere does a completion go? translate_requestOpenAI-shaped body → the provider's body translate_responseThe provider's body → an OpenAI envelope decode_eventOne SSE data:line → aStreamEventstream_is_openai_compatibleCan the stream be passed through untouched? extra_headersConstant headers the API requires models_urlIs there a model list to fetch? parse_modelsThat list → model ids The last four have defaults; the first four do not.
-
Return it from
for_kindinadapters/mod.rs. -
The shared tests in that file — every adapter translates a minimal request, every adapter produces a valid envelope, no adapter mistakes junk for content — will pick your variant up automatically. Add the cases specific to your provider next to them.
Do not touch auth. adapters::apply_headers is the single choke point: adapter
constants first, the user's custom headers next, authentication last and never
overridable.
House rules
- Never log, print or commit a raw key. Provider errors can echo one back;
health::redact()exists for that reason. Test fixtures use obviously fake keys. - Keep the probe-interval floor. 30 seconds is a hard minimum and is not a knob to loosen. Probing costs the provider something.
- Discovery uses documented APIs, feeds and pages the user pinned. If
robots.txtor a source's terms say no, the answer is to report it as skipped, not to route around it. - Pure functions stay pure. If
scoringorroutingneeds the clock or the network, the design is wrong — pass the value in. - No new layers of indirection unless it removes more code than it adds.
- Commit messages follow the existing history:
feat:,fix:,docs:.
Is this a hosted service or a proxy I sign up for?
No. KeyPulse is a desktop application. The gateway is a server that runs on your
machine, bound to 127.0.0.1, and requests go straight from your computer to the
provider. There is no account, no KeyPulse backend, and nothing to sign up for.
Do my keys leave my machine?
Only to the provider the key belongs to. They are stored sealed with AES-256-GCM under a master key in your OS keychain, masked to their last four characters in the UI, and never written to logs. See Security.
Is this for pooling free keys with other people?
No, and please don't. It routes your keys to your tools. Several free tiers explicitly forbid sharing a key across users or redistributing access, and the gateway listens on loopback precisely so it is not a service other people can reach. See Playing fair with free tiers.
Which clients can point at it?
Anything that lets you set a custom OpenAI-compatible base URL and an API key —
the OpenAI SDKs in any language, Cursor, Continue, Aider, LangChain, LlamaIndex,
curl. Use http://127.0.0.1:8787/v1, and any key value at all unless you turned the
bearer token on. Clients hard-wired to a provider's native API shape — the Anthropic
Messages format, for instance — are not covered yet; that endpoint is on the roadmap.
Which providers are supported?
Every provider that exposes an OpenAI-compatible /v1/chat/completions — Groq,
Together, OpenRouter, Cerebras, DeepSeek, local llama.cpp or Ollama endpoints, and so
on — plus Anthropic and Google Gemini in their own formats. You add providers yourself
by base URL and auth scheme, so a new OpenAI-compatible service needs no release from
us.
Do I have to run the gateway?
No. It is off by default. The health monitor, benchmarking, quota tracking and radar all work on their own — plenty of people will want the dashboard and nothing else.
Nothing is answering on port 8787.
Two likely reasons, in order. The gateway is disabled by default, so check the toggle
in Settings. If it is on and GET /health answers but completions return
503 no_routable_provider, the gateway is fine and no key is currently healthy — the
Gateway tab will say which and why.
Does it need Docker, Postgres or Redis?
No. One binary and one SQLite file.
Does it work with paid keys?
Yes. Nothing in the gateway or the health monitor is specific to free tiers; free tiers are simply where the flakiness that motivated it lives.
Is there an installer?
Not yet — there are no tagged releases, so today you build it with
npm run app:build. Publishing signed installers is the top item on the roadmap.
What does probing actually cost me?
One small chat completion per enabled key per interval, and the tokens/sec figure is measured from that same call rather than from a separate large download. At the default 90-second interval that is 40 tiny requests per key per hour, which is why the interval has a floor and why free tiers with hard daily caps deserve a longer one.
MIT — Copyright (c) 2026 KeyPulse contributors.
Use it, fork it, ship it. There is no hosted tier to upsell you to and no server to switch off: your keys and your probe history are files on your own disk.
If KeyPulse saves you one "why is this key 401-ing" afternoon, a star helps the next person find it.