Authentication

Your people call with a session from your sign-in application. Your systems call with a machine token an admin creates in Studio. Every call names your organization in the path, and every route checks the caller against it.

Header
Authorization: Bearer
Scope
One organization per call
Environments
DEV · UAT · PROD

01Three credentials

CredentialWho holds itWhere it works todayStatus
User sessionClerk session tokenA member signed in to Studio or to your app on your instance's sign-in application.Every REST route on the four services, within the member's role.Shipped
Machine tokenOne per environmentYour system. An admin creates it on Studio's Machine access page.The webhook trigger of a secure automation in that environment.Shipped
MCP keyPer MCP configAn outside AI assistant.The MCP server of its config. Studio creates, lists, and deactivates keys.Preview

Authentication · ChecksWhat every request passes before it changes anything, drawn on a sample call.

02User sessions

Every request carries the session token, as Authorization: Bearer or the __session cookie. The token names the member's active organization, their role, and the permissions of that role.

  • Short-lived. A session token lasts about a minute. The SDK in your app refreshes it, so ask for a token before each call instead of storing one.
  • Role based. Admin holds every permission. Builder creates, edits, and runs everyday objects outside PROD. Member reads everything except secrets, machine access, members, and billing.
  • Recorded. Runs and activity record the member as user:{user_id} with their name as the label.
Get a token in your app

// In a browser app that uses Clerk on your instance's sign-in application
const token = await window.Clerk.session.getToken();

await fetch(`${base}/core/api/v1/organizations/${orgId}/me`, {
  headers: { Authorization: `Bearer ${token}` },
});

03Machine tokens

A machine token is how your system calls in, one per environment. Studio's Settings, Machine access page creates it, shows it once, lists it masked afterwards, and revokes it.

  1. Create. An admin picks the environment and creates the token. An environment that already has an active token gets that one back, never a second.
  2. Store. Copy the token into your system's secret store when Studio shows it. It is never shown again.
  3. Attach. In the automation's webhook settings, mark the webhook secure and choose the token for each environment it runs in.
  4. Revoke. Revoking takes effect at once, with one caveat: a verification is cached for up to 60 seconds, so a revoked token can still start a run inside that window.
POST/core/api/v1/organizations/{org_id}/security/m2m

# Studio makes this call for you. It needs an admin session.
curl -s -X POST "$NSTACK_BASE/core/api/v1/organizations/$NSTACK_ORG/security/m2m" \
  -H "Authorization: Bearer $NSTACK_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"env_id":"{env_id}"}'
Response200 · sample values, token withheld

{
  "key_ref": "…",
  "token": "{machine_token}",
  "token_id": "{token_id}",
  "env_id": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
  "created": true,
  "created_at": "2026-10-02T14:30:00.000Z",
  "expires_at": null
}
Handle with care

The token works until you revoke it. Keep it in a secret store, never in source control or a client app, and revoke it in Studio the moment you suspect a leak.

04Secure webhooks

A secure webhook starts an automation in one environment. NSTACK verifies the token, checks that it belongs to your organization and is attached to this webhook for this environment, then queues the run. The run records the token as its actor, machine:{token_id}.

POST/workflow/api/v1/organizations/{org_id}/workflows/{workflow_id}?env={env_id}

curl -s -X POST "$NSTACK_BASE/workflow/api/v1/organizations/$NSTACK_ORG/workflows/$WORKFLOW_ID?env=$ENV_ID" \
  -H "Authorization: Bearer $NSTACK_MACHINE_TOKEN" \
  -H "Content-Type: application/json" \
  -d @payload.json

const url = `${process.env.NSTACK_BASE}/workflow/api/v1/organizations/${process.env.NSTACK_ORG}` +
  `/workflows/${workflowId}?env=${envId}`;

const res = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.NSTACK_MACHINE_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(payload),
});
if (!res.ok) throw new Error(`webhook refused: ${res.status}`);

import os
import requests

url = (f"{os.environ['NSTACK_BASE']}/workflow/api/v1/organizations/{os.environ['NSTACK_ORG']}"
       f"/workflows/{workflow_id}")
res = requests.post(url, params={"env": env_id}, json=payload,
                    headers={"Authorization": f"Bearer {os.environ['NSTACK_MACHINE_TOKEN']}"})
res.raise_for_status()

The body is any JSON object, passed to the run as its input. The call answers {"status": "accepted", "correlation_id", "message"} and the automation runs after the response. Read what it did on the run record.

05MCP keys Preview

An outside AI assistant sends its key as Authorization: Bearer to the MCP server of one config. Studio creates, lists, and disables the keys, and every call shows on the run record with the key as its actor. The MCP page covers the calls.

  • Today. A key belongs to one MCP config and one organization. Studio shows its value once, at creation.
  • Planned. Scopes per key, OAuth 2.1 for clients that support it, and a run record per tool call.

06Organization scoping

Every route carries the organization in its path, /v1/organizations/{org_id}/…, and the path must name the session's active organization. A member of two organizations switches the active one before calling the other. Nobody gets an exception.

Response403 · from core

{ "error": { "code": "org_mismatch", "message": "The organization in the path is not the session's active organization" } }

A request for another organization's records, runs, or activity is refused the same way.

07Environments and PROD

An organization has its environments, usually DEV, UAT, and PROD. Each has an env_id, an alias, and an is_production flag. Connection settings, knowledge indexes, agent pins, automations, and machine tokens are all set per environment.

  • The service enforces it. A change that touches a production environment needs the permission to manage production on top of the route's own. When NSTACK cannot confirm that an environment is not production, it refuses the change.
  • Studio confirms it. Before it sends a PROD change, Studio asks the member to type the environment's alias.
  • Your code does both. Read is_production from the environment and put your own confirmation in front of any change you send to it.

08Errors you will see

StatusCodeMeans
401unauthorizedNo token, or a token that is expired, revoked, or unverifiable. Fetch a fresh session token, or check the machine token in Studio.
403org_mismatchThe path names an organization other than the session's active one.
403forbiddenThe role lacks the route's permission, or the change touches PROD without the permission to manage production.
403machine_token_not_acceptedThe webhook is not marked secure, so it takes a member session instead.
403machine_token_not_attachedThe token is valid but not attached to this webhook for this environment.
409conflictThe instance_id you passed has already run.
503production_check_unavailableThe service could not confirm with core whether the environment is production, so it refused the change. Retry.
503machine_token_unverifiedThe workflow service could not reach Clerk to verify the token. Retry with backoff.

Error bodies take the form {"error": {"code", "message"}}.

09In design

  • In design Machine access to REST. Your system reads datasets and runs with a machine credential, scoped by permission and environment, with no member session.
  • In design Scoped MCP keys. Scopes per key, and OAuth 2.1 for assistants that support it.