# If a key leaks

> What to do when a key ends up somewhere public, how to rotate without downtime, and why the browser is the wrong place to hold one.

Source: https://hyperjobs.io/help/key-security

A key is a bearer credential. Anyone holding it can spend your quota, and nothing
else — the API is read-only, so a leaked key can't modify your account, your
billing, or your data. That limits the blast radius to your bill and your rate
limit. It doesn't make a leak fine.

## If a key is exposed right now

<Steps>
  <Step title="Create a replacement first">
    You can hold 10 active keys, so make the new one before you kill the old one.
    Doing it in this order is the difference between a rotation and an outage.
  </Step>
  <Step title="Deploy the new key">
    Update your secret store and let the change reach every worker.
  </Step>
  <Step title="Revoke the exposed key">
    In the [dashboard](/dashboard). Revocation takes effect on the next request
    and cannot be undone.
  </Step>
  <Step title="Check what it did">
    The dashboard shows `last_used_at` per key and a 30-day daily request
    breakdown. A leaked key being used shows up as request volume you can't
    account for.
  </Step>
</Steps>

If the key was committed to git, revoking it is the fix — rewriting history is
not. The commit is already cloned, forked, and indexed. Treat the key as public
from the moment it was pushed and move on to rotation.

<Note>
  We never ask for your API key, and support has no use for it — everything we
  can do for your account, we can do from our side. If you've pasted a key into a
  ticket, a screenshot, or a chat, revoke it.
</Note>

## Rotating on a schedule

There's no automatic rotation and no key expiry. Keys live until you revoke them.
If you rotate periodically, the four steps above are the whole procedure — the
ten-key ceiling exists precisely so the old and new keys can overlap while the
change propagates.

Wait for `last_used_at` on the old key to stop moving before revoking it. That's
the signal that nothing is still holding it, and it's more reliable than your
memory of which services read that secret.

## Don't call the API from a browser

This is the most common way keys leak, and it doesn't announce itself.

<Danger title="A key in browser JavaScript is a published key">
  Anything shipped to the browser is readable by anyone who opens devtools. It
  doesn't matter whether it's in the source, in an environment variable at build
  time, or fetched at runtime — if the browser can send it, the user can read it.
  And it carries your whole account's quota, not a slice of it.
</Danger>

Call the API from your server and expose your own endpoint to your frontend. That
gives you a place to put caching, your own rate limiting, and your own auth — none
of which you can do with a key sitting in a bundle.

The API makes this awkward on purpose. There's no `OPTIONS` handler, so sending
an `Authorization` header from page JavaScript triggers a CORS preflight that
fails. The `?api_key=` query fallback does work from a browser, which is exactly
why it deserves the warning: it working is not the same as it being safe. See
[authentication](/docs/authentication) for the mechanics.

## Where to keep keys instead

- **A secret manager** — your platform's, or a dedicated one. Not a `.env` file
  in the repo.
- **Environment variables at runtime**, injected by the platform, not baked into
  an image or a client bundle.
- **Server-side only.** No browsers, no mobile apps, no desktop clients. Anything
  you hand to a user is published.

Scan your repos for `hj_live_` before you ship. It's a distinctive prefix, which
makes it easy for you to grep for — and easy for everyone else to grep for too.

## Reporting a vulnerability

If you've found a security issue in the API or the dashboard, write to
<Email kind="security" />. If it's your own key that's exposed, you don't need
us — revoke it yourself; it's immediate.
