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

# Authentication

> API keys, the Authorization header, scopes, and the webhook signing secret.

Every request to the Link Skipper API is authenticated with an API key sent as a bearer
token. Keys are created and managed in the
[developer dashboard](https://linkskipper.app/developers/keys).

## API keys

A key is a secret string that looks like:

```
sk_live_XXXXXXXXXXXXXXXXXXXXXXXX
```

Each key is bound to your account and spends credits from your account balance. A key also
carries:

<ParamField path="scopes" type="string[]">
  The permissions granted to the key. Currently every key needs the `resolve` scope to call
  the resolve endpoint. Requests with a key that lacks the required scope return
  `forbidden_scope` (`403`).
</ParamField>

<ParamField path="rate limit" type="number">
  A per-minute request limit (default **60/min**, configurable per key in the dashboard).
  See [Rate limits](/docs/rate-limits).
</ParamField>

<ParamField path="daily quota" type="number | null">
  An optional cap on resolve calls per UTC day. `null` means no daily cap. See
  [Rate limits](/docs/rate-limits).
</ParamField>

## The Authorization header

Send the key in the `Authorization` header on every request:

```http theme={null}
Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX
```

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.linkskipper.app/v1/account \
    -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
  ```

  ```ts JavaScript theme={null}
  import { LinkSkipper } from "@linkskipper/sdk";

  const client = new LinkSkipper({ apiKey: process.env.LINKSKIPPER_API_KEY! });
  const account = await client.account();
  ```

  ```php PHP theme={null}
  <?php

  use LinkSkipper\LinkSkipper;

  $client = LinkSkipper::create(getenv("LINKSKIPPER_API_KEY"));
  $account = $client->account();
  ```
</CodeGroup>

If the key is missing, malformed, or revoked the API returns `invalid_key` (`401`):

```json theme={null}
{
  "type": "https://linkskipper.app/developers/docs#invalid_key",
  "title": "Invalid API key",
  "status": 401,
  "code": "invalid_key",
  "detail": "A valid API key is required."
}
```

## Scopes

Scopes describe what a key is allowed to do. The only scope today is:

| Scope     | Grants                                    |
| --------- | ----------------------------------------- |
| `resolve` | Call `POST /v1/resolve` to resolve links. |

The read endpoints (`/v1/jobs/{id}`, `/v1/account`, `/v1/providers`) are available to any
valid key. A key without `resolve` can still read its account and job history but cannot
start new resolves — those calls fail with `forbidden_scope`.

## Creating a key

<Steps>
  <Step title="Open the dashboard">
    Go to [linkskipper.app/developers/keys](https://linkskipper.app/developers/keys).
  </Step>

  <Step title="Create a key">
    Name the key, confirm the `resolve` scope, and optionally set a custom per-minute rate
    limit or daily quota.
  </Step>

  <Step title="Copy and store it">
    The full `sk_live_…` value is shown **once**. Store it in your secret manager or
    environment (`LINKSKIPPER_API_KEY`). You cannot recover it later — only rotate it.
  </Step>
</Steps>

<Warning>
  Never embed an API key in client-side code, a mobile app, or a public repository. A leaked
  key can spend your credits. Call the API from your server only, and revoke compromised
  keys in the dashboard immediately.
</Warning>

## The webhook signing secret

If you use [webhooks](/docs/webhooks), each key also has a signing secret that looks like:

```
whsec_XXXXXXXXXXXXXXXXXXXXXXXX
```

Link Skipper signs every webhook delivery with this secret so you can verify the request
really came from us. The secret is shown in the dashboard alongside the key. Store it as
`LINKSKIPPER_WEBHOOK_SECRET` and pass it to `verifyWebhook` (JS) or `Webhook::verify`
(PHP). See [Webhooks](/docs/webhooks) for the full signature scheme.

<Info>
  The webhook secret is different from the API key. The API key authenticates your requests
  **to** Link Skipper; the webhook secret verifies webhook requests **from** Link Skipper.
</Info>
