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

# Authentication

> How API keys, platforms, and scopes work.

Hawkings uses two pieces of identity on every authenticated request:

1. **An API key** — `hk-{...}`, 5 segments. Identifies the user.
2. **A learning platform** — picked per request by `code` (slug) or `uuid`.
   Identifies which workspace the call operates on.

A single API key gives access to **every** platform the user belongs to.
You pick which one to act on by passing `platformCode` (preferred) or
`platform` (the 24-char UUID) — the SDK turns them into headers.

<Tip>
  **`code` is the human-readable slug.** Use it when you have it
  (`"acme-academy"`, `"unimiami"`). Fall back to `uuid` for older platforms
  where `code` is still `null`.
</Tip>

## Pass the key to the SDK

The SDK reads `HAWKINGS_API_KEY` and `HAWKINGS_PLATFORM_CODE` (or
`HAWKINGS_PLATFORM` for the UUID form) from the environment by default.
You can also pass them in code:

<CodeGroup>
  ```ts TypeScript theme={null}
  import Hawkings from "@hawkings/sdk";

  // Preferred: identify the platform by code
  const hk = new Hawkings({
    apiKey: "hk-...-...-...-...-...",
    platformCode: "acme-academy",
  });

  // Or by UUID (universal fallback — always works)
  const hk = new Hawkings({
    apiKey: "hk-...-...-...-...-...",
    platform: "abc123def456ghi789jkl012",
  });
  ```

  ```php PHP theme={null}
  require_once 'vendor/autoload.php';

  // Preferred: identify the platform by code
  $hk = new \Hawkings\Client([
      'api_key'       => 'hk-...-...-...-...-...',
      'platform_code' => 'acme-academy',
  ]);

  // Or by UUID
  $hk = new \Hawkings\Client([
      'api_key'  => 'hk-...-...-...-...-...',
      'platform' => 'abc123def456ghi789jkl012',
  ]);
  ```

  ```python Python theme={null}
  from hawkings import Hawkings

  hk = Hawkings(api_key="hk-...", platform_code="acme-academy")
  ```

  ```bash curl theme={null}
  curl https://api.hawkings.education/v1/courses \
    -H "x-api-key: hk-..." \
    -H "X-Learning-Platform-Code: acme-academy"
  ```
</CodeGroup>

### Wire headers

The SDK sends:

| Header                     | When                                          |
| -------------------------- | --------------------------------------------- |
| `x-api-key`                | Always (except on unauthenticated endpoints). |
| `X-Learning-Platform-Code` | When you set `platformCode`.                  |
| `X-Learning-Platform-Uuid` | When you set `platform`.                      |

If both are set, the backend cross-checks them — they must point to the
same workspace.

<Warning>
  The legacy long-form API key (`hk-...-{platformUuid}`) is **no longer
  accepted by the SDK**. Split it: take the first 5 segments as `apiKey` and
  pass the trailing 24-hex as `platform` separately.
</Warning>

## Scopes

Every key has one or more scopes. The default key issued in the
dashboard carries `read:*` and `write:*` for everything in its workspace.
For machine-to-machine integrations create scoped keys:

| Scope               | What it allows                                               |
| ------------------- | ------------------------------------------------------------ |
| `read:courses`      | List, retrieve, expand `Course`, `Cohort`, `Unit`, `Lesson`. |
| `write:courses`     | Above + create / update / delete authoring resources.        |
| `read:students`     | List students and their progress.                            |
| `write:submissions` | Create submissions (use this for student-facing apps).       |
| `ai:generate`       | Run `*.generate*()` calls.                                   |
| `ai:grade`          | Run `submissions.gradeWithAi()`.                             |

The dashboard generates scoped keys via copy-paste; programmatically
you'd use the [Auth API](/api-reference/overview).

## End-user authentication

If you're building a *student-facing* product, you don't want to ship
your platform-wide key to a browser. Use the **token flow**:

<CodeGroup>
  ```ts TypeScript theme={null}
  // Server-side: ask Hawkings for a one-time token for this user
  const { token } = await hk.auth.tokenStart({ email: student.email });

  // Send `token` to your client
  ```

  ```php PHP theme={null}
  // Server-side: ask Hawkings for a one-time token for this user
  $result = $hk->auth->tokenStart(['email' => $student->email]);
  $token = $result->token;

  // Send `$token` to your client
  ```
</CodeGroup>

<CodeGroup>
  ```ts TypeScript theme={null}
  // Client-side: exchange the token for a session-scoped key
  const session = await hk.auth.tokenFinish(token);
  // session.api_key is a short-lived key scoped to this user
  ```

  ```php PHP theme={null}
  // Client-side: exchange the token for a session-scoped key
  $session = $hk->auth->tokenFinish($token);
  // $session->api_key is a short-lived key scoped to this user
  ```
</CodeGroup>

The session key inherits only the scopes the student needs:
`read:lessons`, `write:submissions`, `ai:tutor`. It expires in 24 hours.

## Multiple workspaces

A single API key gives access to every platform the user belongs to.
List them and switch by `code` or `uuid`:

<CodeGroup>
  ```ts TypeScript theme={null}
  const platforms = await hk.platforms.list();
  // → [{ id: 1, uuid: "...", code: "acme", name: "Acme Academy", ... }, ...]

  // Pick by code (preferred)
  const acme = hk.withPlatformCode("acme");
  const beta = hk.withPlatformCode("beta");

  // Or by uuid (when `code` is still null)
  const legacy = hk.withPlatform(platforms[0].uuid);

  await acme.courses.list();
  await beta.courses.list();
  ```

  ```php PHP theme={null}
  $platforms = $hk->platforms->list();
  // → [{ id: 1, uuid: "...", code: "acme", name: "Acme Academy", ... }, ...]

  $acme = $hk->withPlatformCode('acme');
  $beta = $hk->withPlatformCode('beta');
  ```
</CodeGroup>

You can also override the platform on a single call without building a
new client — see your SDK's per-request options.

## Rotating a key

Rotating is non-disruptive:

1. Issue a new key in the dashboard.
2. Deploy it.
3. Revoke the old one.

Revoked keys return a [401 `authentication_error`](/errors-and-retries)
on the next request.

## Self-hosted instances

If you're running Hawkings on your own infrastructure, point the SDK at
your base URL:

<CodeGroup>
  ```ts TypeScript theme={null}
  new Hawkings({
    apiKey: "hk-...",
    baseURL: "https://api.your-school.example",
  });
  ```

  ```php PHP theme={null}
  new \Hawkings\Client([
      'api_key'  => 'hk-...',
      'base_url' => 'https://api.your-school.example',
  ]);
  ```
</CodeGroup>
