# Authentication

> API keys, how to send them, and how to keep them out of places they shouldn't be.

Source: https://hyperjobs.io/docs/authentication

Every endpoint except [`/v1/health`](/docs/api-reference/health) requires an API
key.

## Getting a key

Keys are created in the [dashboard](/dashboard). A key looks like:

```
hj_live_3f9a2c8e14b7d05629fa1c3e8b4d70a5162c9df83e0b7a41
```

That's the prefix `hj_live_` plus 48 hex characters (24 random bytes).

<Warning title="The key is shown once">
  Only a SHA-256 hash of your key is stored. The full value crosses the wire
  exactly once, at creation, and cannot be recovered afterwards — not by you,
  not by support. Copy it into your secret store immediately. If you lose it,
  revoke it and create another; revocation takes effect on the next request.
</Warning>

You can hold up to **10 active keys** at a time. Revoked keys don't count
against that limit.

## Sending the key

Two ways, in precedence order.

<Tabs>
  <Tab title="Authorization header (preferred)">
    ```bash
    curl -H "Authorization: Bearer $HYPERJOBS_KEY" \
      "https://api.hyperjobs.io/v1/jobs?limit=1"
    ```

    Use this everywhere you can. The key stays out of URLs, which means it stays
    out of proxy logs, browser history, and `Referer` headers.
  </Tab>

  <Tab title="Query parameter">
    ```bash
    curl "https://api.hyperjobs.io/v1/jobs?limit=1&api_key=$HYPERJOBS_KEY"
    ```

    Supported as a fallback. Prefer the header — a key in a query string
    gets written to every log between you and us.
  </Tab>
</Tabs>

If both are present, the header wins.

<Note>
  The `Bearer ` prefix is optional — the server strips it if present and
  otherwise treats the whole header value as the key. `Authorization: hj_live_…`
  works. Send `Bearer` anyway; it's what every HTTP client and proxy expects.
</Note>

## Calling from a browser

Don't ship a key to the browser — but the mechanics work now.

Every response carries `Access-Control-Allow-Origin: *`, and the API answers
the `OPTIONS` preflight (`204`, with `Authorization` in
`Access-Control-Allow-Headers`), so a `fetch` with an `Authorization` header
works from page JavaScript. Use the header in a browser like anywhere else —
`?api_key=` still works but puts the key in every log along the way.

<Danger title="A browser-side key is a public key">
  Anything in browser JavaScript is readable by anyone who opens devtools. A key
  shipped to the browser is a key you have published, and it carries your whole
  quota. Call the API from your server and expose your own endpoint to your
  frontend.
</Danger>

## Verifying a key

`/v1/health` is unauthenticated, so it can't confirm a key. The cheapest
authenticated check is a one-row query:

```bash
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Authorization: Bearer $HYPERJOBS_KEY" \
  "https://api.hyperjobs.io/v1/jobs?limit=1"
```

`200` means the key is live. `401` means it's missing, malformed, unknown, or
revoked — the response doesn't distinguish, deliberately:

```json
{
  "error": "invalid or missing api key",
  "hint": "Authorization: Bearer <key>"
}
```

## Scopes

Keys carry a `jobs:read` scope. It is currently the only scope, and every
endpoint is readable with it — there is nothing to configure. The field exists
so that write or admin scopes can be added later without reissuing keys.

## Rotating

There's no automatic rotation. To rotate without downtime:

<Steps>
  <Step title="Create the new key">
    You can hold 10 active keys, so the old one keeps working while you roll.
  </Step>
  <Step title="Deploy it">
    Update your secret store and let the change propagate everywhere.
  </Step>
  <Step title="Confirm the old key is idle">
    The dashboard shows `last_used_at` per key. Wait until the old key stops
    moving.
  </Step>
  <Step title="Revoke the old key">
    Revocation is immediate and permanent. The key's usage history is retained
    for your billing records.
  </Step>
</Steps>

## Next

<CardGroup cols={2}>
  <Card title="Rate limits & quotas" icon="Gauge" href="/docs/rate-limits">
    What a key is allowed to do.
  </Card>
  <Card title="Errors" icon="TriangleAlert" href="/docs/errors">
    Every status code the API returns.
  </Card>
</CardGroup>
