> ## 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.

# Authentication

> How API keys work, how to send them, and the difference between live and test keys.

Every endpoint requires an API key sent as a bearer token:

```bash theme={null}
export GPUOUTLET_API_KEY="gpk_live_…"     # from Settings → API keys

curl https://api.gpuoutlet.ai/v1/me \
  -H "Authorization: Bearer $GPUOUTLET_API_KEY"
```

There is no other authentication method. Cookies, query-string tokens and basic
auth are not accepted — a key in a URL ends up in browser history, proxy logs and
`Referer` headers, which is exactly where a credential should never be.

## The shape of a key

```
gpk_live_YOURKEYHERE000000000000000000
│   │    │
│   │    └─ random secret
│   └────── mode: `live`, or `test` for a test key
└────────── product prefix, so a leaked string is recognisable as ours
```

The prefix is deliberate. Secret scanners — GitHub's, GitLab's, your own
pre-commit hook — match on recognisable prefixes. A key that looked like an
anonymous blob of base64 would sail through every one of them.

## Keys are shown once

At creation we show you the full key exactly once. After that we store only its
SHA-256 hash, so nobody — including us — can read it back. The dashboard shows a
masked form (`gpk_live_YOUR…HERE`) which is enough to tell two keys apart and
useless to anyone who steals it.

<Warning>
  If you lose a key, you cannot recover it. Create a new one, move your integration
  over, then revoke the old one. There is no "show key again" and no support path
  that produces one, because there is no copy to produce.
</Warning>

## Live and test keys

|                            | `gpk_live_…`    | `gpk_test_…`                       |
| -------------------------- | --------------- | ---------------------------------- |
| Reads the real catalog     | Yes             | Yes                                |
| Can create or stop rentals | Yes             | **No** — `test_mode_not_supported` |
| Rate limit                 | Account default | Lower                              |
| Spends money               | Yes             | Never                              |

A test key is for wiring up and demonstrating an integration: it authenticates
exactly like a live key and reads exactly the same real catalog, so your parsing,
pagination and error handling get exercised for real.

What it cannot do is create a rental. There is no sandbox substrate behind this
API — a "test" rental would be an actual machine on actual hardware costing
actual money. Rather than pretend otherwise, we reject the write and tell you
why:

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "test_mode_not_supported",
    "message": "Test keys cannot create or stop rentals. They authenticate like live keys and there is no sandbox behind this API.",
    "doc_url": "https://docs.gpuoutlet.ai/api/errors#test_mode_not_supported",
    "request_id": "req_2c7d90fa1e33"
  }
}
```

Every response to a test key carries a header:

```http theme={null}
X-GpuOutlet-Mode: test
```

Live responses do not. It is worth asserting on in CI — it catches the accident
where a live key finds its way into a test environment, which is otherwise
invisible right up until something bills.

## Checking a key

`GET /me` is the cheapest way to confirm a key works and see what it may do.
Use it as the health check in your integration's setup step, not a catalog call.

```bash theme={null}
curl https://api.gpuoutlet.ai/v1/me \
  -H "Authorization: Bearer $GPUOUTLET_API_KEY"
```

```json theme={null}
{
  "key_id": "key_7f3a2b1c",
  "scopes": ["catalog:read", "rentals:read", "rentals:write"],
  "mode": "live",
  "rate_limit_per_min": null
}
```

<ResponseField name="key_id" type="string">
  Identifies the key in your dashboard and in support conversations. Not a secret.
</ResponseField>

<ResponseField name="scopes" type="string[]">
  What this key may do. See [Scopes](/api/scopes).
</ResponseField>

<ResponseField name="mode" type="'live' | 'test'">
  Whether this key can spend money.
</ResponseField>

<ResponseField name="rate_limit_per_min" type="integer | null">
  A per-key override. `null` means the account default applies.
</ResponseField>

## Authentication failures

All of these are `401`, and all of them mean "get a working key" rather than
"retry":

| Code              | What happened                        |
| ----------------- | ------------------------------------ |
| `invalid_api_key` | Missing, malformed, or unknown key   |
| `api_key_revoked` | The key was revoked in the dashboard |
| `api_key_expired` | The key passed its expiry date       |

We answer *unknown* and *revoked* differently on purpose, because both are
actionable by you and neither leaks anything: you already hold the key, so
telling you its status reveals nothing an attacker could not learn by trying it.

Contrast that with a rental id belonging to someone else, which returns `404`
rather than `403` — there, distinguishing "does not exist" from "not yours"
would confirm the existence of another account's resource. Different question,
different answer.

## Keeping keys safe

* **Server-side only.** Anything shipped to a browser or a mobile app is public,
  however well minified. There is no CORS configuration that makes a bearer token
  in front-end JavaScript safe.
* **Environment variables, not source.** `GPUOUTLET_API_KEY` in your process
  environment or secret manager; never a literal in a repository, however private.
* **One key per integration.** Separate keys for your scheduler, your dashboard
  and your CI mean you can revoke one without an outage in the others — and the
  [usage panel](/api/key-security#watching-what-a-key-does) tells you which one
  is misbehaving.
* **Scope down.** A key that only reads the catalog cannot start a machine even
  if it leaks. See [Scopes](/api/scopes).
* **Cap the damage.** A [daily spend limit](/api/spend-limits) turns a runaway
  loop from an unbounded bill into a bounded one.

<Card title="If a key leaks" icon="fire" href="/api/key-security#if-a-key-leaks">
  Revoke first, investigate second. Revocation takes effect immediately.
</Card>
