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

Machine as a Backend

Start and manage Kortix sessions from your backend with explicit connector, model, context, and secret scope.

Use a Machine API key to start sessions from your server. Each session has one Machine owner, one project, and one cost record.

Your application owns its customer identifiers and metadata. Store the relationship between your customer and the returned session_id in your application database.

1. Get an API key

Create a personal access token (kortix_pat_…) in your own settings, at Settings → API keys (/settings/tokens), or a service-account credential (kortix_sa_…) at Account → Tokens. Both authenticate a programmatic session-create request. The API derives origin: "backend" from the credential type.

bash
export KORTIX_API_URL="https://your-kortix-deployment.com/v1"
export KORTIX_API_KEY="kortix_pat_…"
export KORTIX_PROJECT_ID="…"

Use a service account when you need an independently managed principal. A service account is the service_account principal type. It has no membership, so it holds only the roles assigned to it directly. Assign it a project role before use — see Accounts & access.

2. Start a session

bash
curl -X POST "$KORTIX_API_URL/projects/$KORTIX_PROJECT_ID/sessions" \
  -H "Authorization: Bearer $KORTIX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "agent_name": "support",
    "model": "kortix/glm-5.3-flash",
    "runtime_context": { "ticket_id": "ticket-123" },
    "connector_bindings": {
      "gmail": { "connection_id": "<connection-id>" }
    },
    "secrets": ["STRIPE_KEY"]
  }'
typescript
import { createScopedKortix } from '@kortix/sdk/server';

const machine = createScopedKortix({
  backendUrl: process.env.KORTIX_API_URL!,
  getToken: async () => process.env.KORTIX_API_KEY!,
});

const session = await machine.project(projectId).sessions.create({
  agent_name: 'support',
  model: 'kortix/glm-5.3-flash',
  runtime_context: { ticket_id: 'ticket-123' },
  connector_bindings: {
    gmail: { connection_id: connectionId },
  },
  secrets: ['STRIPE_KEY'],
});

Use createScopedKortix when one server process handles concurrent requests. Each client keeps its token and runtime state request-scoped.

Store the session_id returned by either create call:

bash
export SESSION_ID="<session-id>"

Session-create fields

FieldContract
agent_nameSelects a declared agent.
modelSelects the initial model. An unavailable model returns 400 INVALID_SESSION_MODEL. opencode_model is the deprecated name; the API accepts both, and model wins.
runtime_contextStores non-secret scalar context. The API rejects credential-like keys, more than 64 entries, or more than 16 KiB.
connector_bindingsMaps a connector slug to one strategy-compatible connection_id. The credential stays outside the sandbox.
inherit_unboundKeeps strategy-based default resolution for connectors omitted from an explicit binding map. The default is false.
secretsNarrows the selected agent's project-secret grant. An empty list delivers no project secrets. Only backend-origin callers can set it.
require_connectorsDeprecated and inert — accepted and ignored. A session no longer declares connectors it requires; a call to a connector with nothing connected is denied with connector_not_connected and a connect_url instead.

3. Configure connectors and connections

A connector defines the tool surface. It contains a project-unique slug, display name, provider app, authorization strategy, and policies.

A connection stores one connected account or credential for that connector. Every connection inherits the connector's policies.

The authorization strategy has two values:

  • project accepts active project connections.
  • user accepts only an active connection owned by the acting project member.

A service account is a principal, but it is not a person, so it cannot use a member's user connection. Use project connectors for service-account sessions. A personal access token can use an eligible user connection owned by the token's member.

yaml
connectors:
  - slug: gmail-read
    name: Gmail read only
    provider: pipedream
    app: gmail
    authorization_strategy: project
    policies:
      - match: search_email
        action: always_run

agents:
  support:
    connectors: [gmail-read]
    connectors_required: [gmail-read]

The SDK exposes connections under project.connectors.connections:

typescript
const connection = await machine.project(projectId).connectors.connections.reconcile({
  connector_alias: 'gmail-read',
  owner_type: 'project',
  label: 'Support inbox',
});

await machine
  .project(projectId)
  .connectors.connections.updateCredential(connection.connection_id, {
    value: credential,
    kind: 'secret',
  });

await machine.project(projectId).connectors.connections.activate(connection.connection_id);

For a Pipedream OAuth connection, call pipedreamConnect() and pipedreamFinalize(). Do not place its provider token in updateCredential().

:::info The connection object and new session binding input use connection_id. authorization_id remains a deprecated SDK input alias. :::

4. Read and replace session scope

The session scope is authoritative server state. secrets_allowlist contains the session's stored narrowing. A null value means the agent grant applies. connector_bindings contains the materialized connection selection.

bash
curl -sS \
  "$KORTIX_API_URL/projects/$KORTIX_PROJECT_ID/sessions/$SESSION_ID/scope" \
  -H "Authorization: Bearer $KORTIX_API_KEY"
typescript
const scope = await machine.session(projectId, sessionId).scope();

Replace scope with PUT or rescope():

typescript
const nextScope = await machine.session(projectId, sessionId).rescope({
  secrets: ['STRIPE_KEY'],
  connector_bindings: {
    'gmail-read': { connection_id: connection.connection_id },
  },
});

Each supplied field uses set semantics. The new value replaces the complete previous value. Omit a field to leave it unchanged.

Connector changes apply to the next tool call. Secret removal stops future delivery. It cannot remove a value from an existing model context or process. Rotate the secret when prior disclosure matters.

5. Read session costs

The session-cost API combines finalized LLM cost and billed sandbox compute cost. Every session appears in the list, including sessions with zero cost.

bash
curl -sS \
  "$KORTIX_API_URL/usage/session-costs?project_id=$KORTIX_PROJECT_ID&limit=25&offset=0" \
  -H "Authorization: Bearer $KORTIX_API_KEY"

curl -sS \
  "$KORTIX_API_URL/usage/session-costs/$SESSION_ID?project_id=$KORTIX_PROJECT_ID" \
  -H "Authorization: Bearer $KORTIX_API_KEY"

The list returns session, project, owner, LLM, compute, total, request, token, model, and compute-duration fields. It also returns reconciliation for account usage that has no session.

The detail response adds:

  • model_usage, grouped by provider and model
  • ledger_entries, with discriminated llm and compute rows

Use the SDK for typed reads:

typescript
const page = await machine.billing.sessionCosts.list({
  accountId,
  projectId,
  limit: 25,
  offset: 0,
});

const detail = await machine.billing.sessionCosts.get(sessionId, {
  accountId,
  projectId,
});

const sameDetail = await machine.session(projectId, sessionId).cost();

session.cost() does not start the session runtime.

6. Stream the answer

Await runtime readiness before using the runtime methods:

typescript
const handle = machine.session(projectId, session.session_id);
await handle.ensureReady();

const stream = await handle.stream({
  onEvent: (event) => {
    // Render or persist the event.
  },
});

await handle.send('Summarize the support queue.');

Use useSession(projectId, sessionId) for React hosts. It owns startup, readiness, the live event stream, and message synchronization.

Idempotent retries

Generate one Idempotency-Key for each logical session-create operation. Reuse that key only when the request body is identical.

A replay with the same key and body returns the same session. A replay with a different secret allowlist, connector binding map, or runtime context returns 409.

Security rules

  • The API derives session origin from the credential. The request body cannot select it.
  • Connector credentials resolve server-side for each tool call.
  • A connection must match its connector's authorization strategy.
  • Connector policies apply to every connection under that connector.
  • Project guardrails apply above connector-connection policies.
  • Secret scope can narrow an agent grant. It cannot widen one.
  • A session can only do what the role verdict and the agent's manifest grant both allow. Neither one widens the other. See One vocabulary, two bindings.
  • Session scope replacement cannot select a connection owned by another member.
  • Store application customer metadata outside Machine.

Common errors

StatusCodeMeaning
400INVALID_SESSION_MODELThe selected model is not available to the account.
400INVALID_SESSION_CONNECTOR_BINDINGSThe binding map is malformed.
400INVALID_SESSION_RUNTIME_CONTEXTRuntime context violates its shape, key, entry, or size limits.
403origin_override_forbiddenA non-backend caller supplied a secret allowlist.
403CONNECTOR_NOT_ASSIGNEDThe selected agent is not granted the connector.
404 create / 403 rescopeCONNECTOR_CONNECTION_NOT_FOUNDThe connection does not exist in this project or violates the connector's authorization strategy.
404SECRET_IDENTIFIER_NOT_FOUNDThe secret allowlist contains an unknown project-secret identifier.
409CONNECTOR_PROVIDER_UNSUPPORTEDThe alias is a connector on the project but its provider has no hosted authorization page, so no connect link exists for it.
409CONNECTOR_PIPEDREAM_APP_MISSINGThe Pipedream connector names no app, so no connect link can be built.
409 create / 403 rescopeCONNECTOR_CONNECTION_INACTIVEThe selected connection or connector is inactive.
409IDEMPOTENCY_*_CONFLICTThe idempotency key was replayed with a different request body.
402subscription_required / insufficient_creditsThe account cannot start a billed session.
PreviousSDK referenceNextAPI reference

On this page

1. Get an API key2. Start a sessionSession-create fields3. Configure connectors and connections4. Read and replace session scope5. Read session costs6. Stream the answerIdempotent retriesSecurity rulesCommon errors