# Authentication

Authenticate the SDK with a personal access token or a service account.

Canonical page: https://taskstation.co/docs/sdk/auth

TaskStation accepts one bearer token per request. Pass it through `getToken` in
`createTaskStation`. The SDK sends it as `Authorization: Bearer <token>`.

```ts
import { createTaskStation } from '@taskstation/sdk';

const taskstation = createTaskStation({
  backendUrl: 'https://api.taskstation.co/v1',
  getToken: async () => process.env.TASKSTATION_API_KEY!,
});
```

`backendUrl` and `getToken` are required. The SDK caches nothing: it calls
`getToken` on every request, so your app owns token storage and refresh.

Set `clientSource` to `api`, `cli`, `mobile`, or `web` when your host needs a
separate source in the centralized audit log. This value identifies the client
surface. It does not change the authenticated actor or their permissions.

## Personal access tokens

A personal access token (PAT) is the credential for the SDK, the CLI, and CI.
Create one in your own settings, at **Settings → API keys**
(`/settings/tokens`). The key starts with `taskstation_pat_` and shows only once, at
creation. Store it as a secret.

A PAT acts as the user who created it and holds exactly that user's role
assignments. It adds no access of its own. Its scope only narrows the reach:

| Scope | Reach |
|---|---|
| Account (default) | Every project in the account |
| Project | One project only; every other project returns `403` |

Choose the project scope for CI and other narrow-purpose credentials.
`taskstation login` mints a PAT and stores it locally — it is the same credential
type, not a separate token kind.

## Service accounts

A service account is a separate credential family for non-human callers,
prefixed `taskstation_sa_`. Create one at **Account → Tokens**
(`/accounts/<account-id>?tab=tokens`), the account-level surface for
credentials that are not a person's.

A service account is its own **principal** (`service_account`), not a person's
credential. It has no membership, so it holds only the roles assigned to it
directly. An agent's identity is a service account, which is how you assign a
role to an agent. See
[Accounts & access](/docs/accounts#one-access-model).

> **Warn**
> A new service account has no assignments and therefore no project access. If
> you point `getToken` at one before you assign it a role, every call returns
> `403 "You do not have access to this project"`. Assign it a project role
> first, or use a personal access token for the SDK, the CLI, and demos
> instead.

## OAuth access tokens (Sign in with TaskStation)

A third-party app that signs users in through TaskStation receives a `taskstation_oat_`
token per user. With the `taskstation` scope it acts as that user on the whole API,
exactly like a personal access token, but it expires after an hour and rotates
through a refresh token. `createTaskStationAuth` in `@taskstation/sdk/server` owns the
whole lifecycle — see [Sign in with TaskStation](/docs/sdk/sign-in).

## Supabase JWT

If your app uses TaskStation's own sign-in, return the live session token instead
of a PAT:

```ts
getToken: async () =>
  (await supabase.auth.getSession()).data.session?.access_token ?? null,
```

The SDK calls `getToken` on every request, so a refreshed token takes effect
automatically.

## Headless sign-in (email, password, magic link, social)

Every ordinary sign-in flow is available through the TaskStation API, so a CLI, a
native app, a script, or your own backend signs users up and in without a
Supabase URL or key — on taskstation.co and on a self-host alike.

```ts
import { createTaskStation } from '@taskstation/sdk';

const session = createTaskStation({ backendUrl, getToken: async () => null }).auth.session({
  storage: {                                    // optional: any get/set/remove
    get: () => localStorage.getItem('taskstation'),
    set: (v) => localStorage.setItem('taskstation', v),
    remove: () => localStorage.removeItem('taskstation'),
  },
});
const taskstation = createTaskStation({ backendUrl, getToken: session.getToken });   // refreshes itself

const { session: s, user } = await taskstation.auth.signInWithPassword({ email, password });
await session.set(s, user);
await taskstation.projects.list();                   // as that user
```

| Call | Route | Notes |
|---|---|---|
| `auth.signUp({ email, password, redirect_to? })` | `POST /v1/auth/signup` | `requires_email_confirmation: true` → no session until the emailed link/code is used. |
| `auth.signInWithPassword({ email, password })` | `POST /v1/auth/sign-in/password` | |
| `auth.sendMagicLink({ email, redirect_to? })` → `auth.verifyOtp({ email, token, type: 'magiclink' })` | `/sign-in/magic-link`, `/verify-otp` | The email carries a link and a 6-digit code. |
| `auth.signInWithProvider({ provider, redirect_to })` → `auth.exchangeCode({ code, code_verifier })` | `/sign-in/oauth`, `/oauth/exchange` | PKCE: keep `code_verifier` until the provider redirects back with `?code=`. `redirect_to` must be on the instance's redirect allow-list. |
| `auth.refresh({ refresh_token })` | `POST /v1/auth/refresh` | `createTaskStationSession` calls it for you. |
| `auth.resetPassword({ email, redirect_to? })` → `auth.verifyOtp({ type: 'recovery' })` → `auth.updatePassword({ password }, token)` | `/password/reset`, `/verify-otp`, `/password/update` | |
| `auth.user(token)` / `auth.signOut(token)` | `GET /v1/auth/user`, `POST /v1/auth/sign-out` | Sign-out revokes at Supabase and in the TaskStation session gate. |

Errors throw `HeadlessAuthError` with `code`, `message` and the upstream `status`
(`invalid_credentials`, `over_request_rate_limit`, …). Each route is limited to
30 attempts per minute per IP. Multi-factor enrolment/challenge is not on the
API yet — it stays on the TaskStation web app.

## Choose a credential

| You are building | Use |
|---|---|
| A backend, script, or CI job | A personal access token, account-wide or project-scoped |
| The TaskStation CLI | `taskstation login` (mints a personal access token) |
| A web app, CLI, or native app signing users in itself | `taskstation.auth.*` + `createTaskStationSession` (headless sign-in, above) |
| An automated caller with its own project assignment | A service account |
| Your own app, signed in by its users with their TaskStation account | [Sign in with TaskStation](/docs/sdk/sign-in) — an OAuth access token the SDK manages for you |
