> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gpuoutlet.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Monitoring and key security

> Seeing what a key has done, rotating on a schedule, and the drill for a leaked key.

## Watching what a key does

Open **Settings → API keys** and click a key's **Usage** button. The panel shows
two things:

* **Spend today** against that key's [daily limit](/api/spend-limits), with the
  reset time. If the key has no limit, you get the figure without a progress bar
  — a percentage of nothing would be either always empty or always full, and both
  would be lies.
* **The rentals this key started**, newest first, with their status.

It opens per key rather than rendering for every row, because each panel is a
day's aggregation on the server; ten keys would mean ten such queries to show
something nobody asked to see.

<Note>
  A revoked key keeps its usage panel. What it spent before you turned it off is
  exactly the thing you want to see afterwards — that is usually *why* you are
  looking.
</Note>

### On the Instances page

Rentals started through the API carry an **API** badge, and the **Via API** filter
narrows the list to them. A machine you did not start by hand still spends the
same wallet and still occupies a slot under the [concurrency
ceiling](/api/spend-limits#concurrent-rentals); without the badge it reads as
something you forgot doing.

### From your own code

`GET /rentals` is the account's whole rental list, so it answers "what is running"
but not "which key started it" — attribution lives in the dashboard.

```bash theme={null}
# Everything currently live on the account, whoever started it
curl -s -G https://api.gpuoutlet.ai/v1/rentals \
  -H "Authorization: Bearer $GPUOUTLET_API_KEY" \
  -d active=true
```

If your integration needs per-key attribution in its own logs, record the
`key_id` from [`GET /me`](/api/authentication#checking-a-key) at startup and
attach it to the rentals you create. It is not a secret.

## Rotation

Rotate on a schedule — quarterly is a reasonable default — and always after
someone with access leaves.

<Steps>
  <Step title="Mint the replacement">
    Same scopes, a name that distinguishes it: `prod-scheduler-2026q3`.
  </Step>

  <Step title="Deploy it">
    Both keys are valid at once, so there is no downtime window to coordinate.
  </Step>

  <Step title="Watch the old key go quiet">
    Its usage panel should stop gaining rentals. A key that is still busy is a
    deployment you have forgotten about — find it before you revoke, not after.
  </Step>

  <Step title="Revoke the old key">
    Immediate, and final.
  </Step>
</Steps>

The order matters. Revoking first and deploying second guarantees an outage
between the two; the overlap costs nothing.

## If a key leaks

A key in a public repository, a screenshot, a shared log file, or a chat message
is compromised. Treat "probably nobody saw it" as "someone did" — automated
scrapers find committed credentials within minutes.

<Steps>
  <Step title="Revoke it now" icon="fire">
    **Settings → API keys → Revoke.** Effective on the next request. Do this before
    you investigate; the investigation keeps, the exposure does not.
  </Step>

  <Step title="Stop what it started">
    Revoking the key does **not** stop its rentals. They belong to the account and
    keep billing. Filter the Instances page by **Via API**, check the key's usage
    panel for what it started, and stop anything you do not recognise.
  </Step>

  <Step title="Check the ledger">
    Billing → transaction history. You are looking for top-ups you did not make and
    rentals you did not start.
  </Step>

  <Step title="Mint a replacement and redeploy">
    New key, narrowest scopes that work, and a [spend limit](/api/spend-limits) if
    it can rent.
  </Step>

  <Step title="Purge the exposure">
    Rewriting git history is not enough on its own — the old commit may already be
    cached by GitHub or cloned. Revocation is what makes the leaked string worthless;
    history rewriting is tidying up afterwards.
  </Step>
</Steps>

<Warning>
  If the leaked key had `rentals:write` and no spend limit, assume the worst case
  is "an unbounded number of GPU-hours on your card" and check the ledger
  carefully. This is the scenario a daily limit exists to bound.
</Warning>

## Hygiene checklist

<AccordionGroup>
  <Accordion title="Never ship a key to a client" icon="browser">
    Browsers, mobile apps and desktop binaries are all readable by their users. There
    is no minification, obfuscation or CORS setting that changes this. If a browser
    needs GPU Outlet data, proxy it through your own backend and keep the key there.
  </Accordion>

  <Accordion title="Environment, not source" icon="file-code">
    `GPUOUTLET_API_KEY` from the environment or a secret manager. Not a literal, not
    a committed `.env`, not a default value in a config file. Add `.env` to
    `.gitignore` before the first commit, not after the first leak.
  </Accordion>

  <Accordion title="Keep keys out of logs" icon="scroll">
    The common accident is logging the whole request, headers included. Redact
    `Authorization` in your logging middleware.

    Note that we never put credentials in a URL — SSH and web-terminal passwords come
    back as fields in `access`, not baked into a link — precisely so that logging a
    URL cannot leak one. Extend the same courtesy to your own logs.
  </Accordion>

  <Accordion title="Scope down, then cap" icon="lock">
    [Narrow scopes](/api/scopes) limit what a leak can do at all; a [spend
    limit](/api/spend-limits) bounds what it costs if it can rent. They are different
    controls and worth having both.
  </Accordion>

  <Accordion title="One key per integration" icon="layer-group">
    So that revoking a compromised key does not take down three unrelated systems,
    and so the usage panel tells you which integration is misbehaving.
  </Accordion>
</AccordionGroup>

## What we do on our side

* We store **only a SHA-256 hash** of each key. A dump of our database yields no
  usable credentials.
* The dashboard shows a **masked** form (`gpk_live_YOUR…HERE`) — enough to tell two
  keys apart, useless to a thief.
* Keys carry a recognisable **`gpk_` prefix** so secret scanners can match them.
* Revocation is **immediate**, not eventually-consistent.
* Rate limits are enforced per key and per IP, so a stolen key cannot be used to
  hammer the API from a thousand hosts at once.
