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

# invalid_credential

> HTTP 401 on every protected route: the API key is malformed, unknown or revoked, or the session has expired. How to find which, and fix it.

`invalid_credential` means OpenType received a bearer credential and rejected it. Read this page if calls that used to work now fail with a 401, or if a new key is refused.

| HTTP  | `code`               | Retryable                                  |
| ----- | -------------------- | ------------------------------------------ |
| `401` | `invalid_credential` | No. Do not retry with the same credential. |

## What happened

Routes: every protected route under `/v1/`, including runs, keys, usage, quota and billing.

OpenType reads the value after `Bearer`. A value starting with `otsk_` is checked as an API key; anything else is checked as a console session. The credential was refused for one of these reasons:

| Credential      | Refused when                                                             |
| --------------- | ------------------------------------------------------------------------ |
| API key         | the value is not `otsk_` followed by exactly 64 lowercase hex characters |
| API key         | no key with that secret exists                                           |
| API key         | the key has been revoked, or its secret was replaced by a rotation       |
| Console session | the session has expired or cannot be verified                            |

A revoked key gets `invalid_credential`, not `key_revoked`. [`key_revoked`](/problems/key_revoked) is returned only when you try to rotate a revoked key.

The response does not say which reason applied, and it does not carry a `WWW-Authenticate` header.

## How to fix

Check the key, in this order:

1. **Shape.** The secret is 69 characters: `otsk_` plus 64 lowercase hex. A trailing newline, surrounding quotes, or a truncated copy breaks it. Trim the value when you load it.
2. **The right value.** A key id (`key_...`) or a `secret_prefix` (`otsk_` plus 8 characters) is not a secret. The full secret was shown once, when the key was created or rotated.
3. **State.** Find the key on the [API keys page](/console/api-keys) or with `GET /v1/keys`, and match its `secret_prefix` against the first 13 characters of your secret. If its `state` is `revoked`, create a new key.
4. **Rotation.** If the key was rotated, only the newest secret works. Update every service that still holds the old one.
5. **Environment.** Check that the service loads the key you expect, for example after a deploy or a change in your secret store.

In the console, an expired session is handled by signing in again.

Sending the same credential again gives the same answer. Fix the credential, then retry.

## Example

```json theme={"system"}
{"error":{"code":"invalid_credential","message":"the credential was rejected","request_id":"req_7d3f0c1a9b2e4f6a8c0d1e2f3a4b5c6d"}}
```

A startup check that catches a bad key before real traffic, using a cheap read:

<CodeGroup>
  ```bash curl theme={"system"}
  KEY="$(printf '%s' "$OPENTYPE_API_KEY" | tr -d '[:space:]')"

  case "$KEY" in
    otsk_*) ;;
    *) echo "OPENTYPE_API_KEY does not start with otsk_" >&2; exit 1 ;;
  esac

  curl -s -o /dev/null -w '%{http_code}\n' 'https://api.opentype.dev/v1/runs?limit=1' \
    -H "Authorization: Bearer $KEY"
  ```

  ```typescript TypeScript theme={"system"}
  const key = (process.env.OPENTYPE_API_KEY ?? "").trim();
  if (!/^otsk_[0-9a-f]{64}$/.test(key)) {
    throw new Error("OPENTYPE_API_KEY is not a full otsk_ secret");
  }

  const res = await fetch("https://api.opentype.dev/v1/runs?limit=1", {
    headers: { Authorization: `Bearer ${key}` },
  });

  if (res.status === 401) {
    const { error } = await res.json();
    // invalid_credential: unknown, revoked or rotated key. Do not retry unchanged.
    throw new Error(`${error.code}: replace the key (request ${error.request_id})`);
  }
  ```

  ```python Python theme={"system"}
  import os
  import re

  import requests

  key = os.environ.get("OPENTYPE_API_KEY", "").strip()
  if not re.fullmatch(r"otsk_[0-9a-f]{64}", key):
      raise RuntimeError("OPENTYPE_API_KEY is not a full otsk_ secret")

  resp = requests.get(
      "https://api.opentype.dev/v1/runs",
      headers={"Authorization": f"Bearer {key}"},
      params={"limit": 1},
      timeout=30,
  )

  if resp.status_code == 401:
      err = resp.json()["error"]
      # invalid_credential: unknown, revoked or rotated key. Do not retry unchanged.
      raise RuntimeError(f"{err['code']}: replace the key (request {err['request_id']})")
  ```
</CodeGroup>

The check needs a key that holds `runs_read`. A key without it gets [`scope_denied`](/problems/scope_denied), which still proves the key itself is valid.

## Related

* [Authentication](/security/authentication) - API keys, console sessions and the bearer header.
* [Key rotation](/guides/key-rotation) - replace a key without downtime.
* [API key security](/security/api-key-security) - storing and handling secrets.
* [Problem codes](/problems) - every code, its status, and whether a retry can help.
