Authentication
Authenticate the SDK with a personal access token or a service account.
Kortix accepts one bearer token per request. Pass it through getToken in
createKortix. The SDK sends it as Authorization: Bearer <token>.
import { createKortix } from '@kortix/sdk';
const kortix = createKortix({
backendUrl: 'https://api.kortix.com/v1',
getToken: async () => process.env.KORTIX_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 kortix_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.
kortix 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 kortix_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.
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 Kortix)
A third-party app that signs users in through Kortix receives a kortix_oat_
token per user. With the kortix 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. createKortixAuth in @kortix/sdk/server owns the
whole lifecycle — see Sign in with Kortix.
Supabase JWT
If your app uses Kortix's own sign-in, return the live session token instead of a PAT:
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 Kortix 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 kortix.com and on a self-host alike.
import { createKortix } from '@kortix/sdk';
const session = createKortix({ backendUrl, getToken: async () => null }).auth.session({
storage: { // optional: any get/set/remove
get: () => localStorage.getItem('kortix'),
set: (v) => localStorage.setItem('kortix', v),
remove: () => localStorage.removeItem('kortix'),
},
});
const kortix = createKortix({ backendUrl, getToken: session.getToken }); // refreshes itself
const { session: s, user } = await kortix.auth.signInWithPassword({ email, password });
await session.set(s, user);
await kortix.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 | createKortixSession 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 Kortix 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 Kortix 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 Kortix CLI | kortix login (mints a personal access token) |
| A web app, CLI, or native app signing users in itself | kortix.auth.* + createKortixSession (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 Kortix account | Sign in with Kortix — an OAuth access token the SDK manages for you |