MachineMachine
MachineMachine
MachineGetting startedQuickstartAccounts & access
Your project
AgentsLegacy kortix.tomlManifest referenceModelsAgent permissionsSecrets
Running work
Change requestsRuntime & sandboxSessions
Connect & automate
ComputersConnectorsInkbox: iMessage, SMS, email & callsMCP serverPixio: images, video & audioRemindersSlack & channelsMicrosoft TeamsTriggers
Feature flags
Apps

Develop

CLI
TypeScript SDKFull exampleAuthenticationSign in with KortixSessionsAppsReact hooksSDK reference
Machine as a BackendAPI reference
GitHub
SDK

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>.

typescript
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.

The audit log records the credential your getToken returns (a browser session, a personal access token, a connected app), not a label your host reports. clientSource is deprecated and does nothing.

Set clientVersion to your app's name and release version, for example my-app/1.4.0. The SDK sends it as X-Kortix-Client-Version on every request. Machine writes it to request logs only, to see which client versions still call an API route before that route is retired. It never reaches the audit log.

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:

ScopeReach
Account (default)Every project in the account
ProjectOne 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.

:::warning 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:

typescript
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.

Recover from a rejected token

When the API answers 401, the SDK replays the request once with a fresh token. A getToken that caches must tell the SDK what to drop. Attach an invalidate(rejectedToken) function to it. The SDK calls it before the replay, and the replay then reads a new token.

typescript
const getToken = Object.assign(
  async () => cachedSession.accessToken,
  { invalidate: (rejected: string) => cachedSession.refreshIfCurrent(rejected) },
);

Rules:

  • Parallel 401s on one token call invalidate once and share one refresh.
  • The SDK replays a request once. A second 401 returns to the caller.
  • Without invalidate, the replay re-reads getToken(). A cached getter then returns the dead token, and the replay does not happen.
  • invalidateTokenCache() calls invalidate with the last token getToken returned. The live event stream uses it after an auth error.
  • isAuthFailure(error) is true for a 401, a 403 or an AuthError. It reads status, never the message text.

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.

typescript
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
CallRouteNotes
auth.signUp({ email, password, redirect_to? })POST /v1/auth/signuprequires_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-otpThe email carries a link and a 6-digit code.
auth.signInWithProvider({ provider, redirect_to }) → auth.exchangeCode({ code, code_verifier })/sign-in/oauth, /oauth/exchangePKCE: 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/refreshcreateKortixSession 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-outSign-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 buildingUse
A backend, script, or CI jobA personal access token, account-wide or project-scoped
The Kortix CLIkortix login (mints a personal access token)
A web app, CLI, or native app signing users in itselfkortix.auth.* + createKortixSession (headless sign-in, above)
An automated caller with its own project assignmentA service account
Your own app, signed in by its users with their Kortix accountSign in with Kortix — an OAuth access token the SDK manages for you
PreviousFull exampleNextSign in with Kortix

On this page

Personal access tokensService accountsOAuth access tokens (Sign in with Kortix)Supabase JWTRecover from a rejected tokenHeadless sign-in (email, password, magic link, social)Choose a credential