# StackMachine agent and CLI sign-in

GraphQL endpoint: https://api.stackmachine.dev/graphql
Browser approval page: https://dashboard.stackmachine.dev/auth/claim

Use this flow to sign an agent or CLI in and choose a workspace for its work.
A workspace in StackMachine is a namespace in the GraphQL API.

- STACKMACHINE_USER_ACCESS_TOKEN is a personal token with the user's permissions.
  Login claim redemption returns this token.
- STACKMACHINE_API_TOKEN is an API token for one specific workspace (namespace).
  Prefer this credential for integrations and ongoing agent work.

Reuse an existing valid workspace API token if it covers the requested workspace
and task. Otherwise reuse a user access token, or sign in below to obtain one.
Verify a user access token with the viewer query in step 4. Verify a workspace
API token with an authorized workspace operation; viewer is a user-token check.
The human signs in and explicitly approves in their browser. The agent does not
need a localhost callback, the user's password, or access to browser cookies.
All authentication operations below call GraphQL directly; no frontend API
endpoints are required. Keep credentials and requests on the same API environment.

## 1. Create a request

Send this anonymous POST to https://api.stackmachine.dev/graphql with Content-Type: application/json:

```graphql
mutation CreateLoginClaim($input: CreateLoginClaimInput!) {
  createLoginClaim(input: $input) {
    claimToken
    verificationUri
    expiresIn
    interval
    claim { userCode clientName apiKeyName createdFrom status expiresAt }
  }
}
```

Example variables:

```json
{"input":{"clientName":"My coding agent","apiKeyName":"My coding agent on this computer"}}
```

Choose a recognizable client and token name. Check that createdFrom is
`stackmachine`. Keep claimToken private in the requesting process; it is the
bearer secret for observation and one-time token redemption. Never put it in a
URL, user-facing message, telemetry, or public log.

## 2. Ask the human to authorize

Give the human this StackMachine link, replacing USER_CODE with claim.userCode:

https://dashboard.stackmachine.dev/auth/claim?code=USER_CODE

Display the client name and public user code alongside the link. Only the public
code is shared. Deliver the complete URL and code to the human before polling.
Tell them you will check automatically and continue once they authorize. Do not
ask them to reply once authorized or wait for a chat confirmation.
The human signs in or creates an account, checks the code and
requested access, then chooses Authorize access or Deny. Opening a link or
signing in alone does not authorize the request. Agents must not approve it on
the human's behalf.

Use the StackMachine approval page above against the same GraphQL API.

## 3. Wait and redeem once

After sharing the approval link, wait 15 seconds before the first poll. Send
this anonymous mutation with the private claimToken, then poll sequentially
every 5 seconds while PENDING, up to expiresIn (ten minutes). If the API returns
an interval longer than 5 seconds, use that longer interval between polls.
Keep polling in the same session while the human authorizes; do not end the
flow waiting for them to reply. Once a token is returned, continue immediately
with steps 4 and 5 to save and verify it and ask which workspace to use:

```graphql
mutation RedeemLoginClaim($claimToken: String!) {
  redeemLoginClaim(claimToken: $claimToken) { status token }
}
```

Variables: `{"claimToken":"<private claimToken>"}`.

- PENDING with token null: wait 5 seconds (or the longer returned interval),
  then poll again.
- CONSUMED with a token: sign-in succeeded. Stop polling, store the token
  securely, and continue automatically with account verification and workspace
  selection.
- DENIED or EXPIRED: stop and explain the outcome. A new attempt needs a new claim.
- CONSUMED with token null: another redemption already consumed this request.
  Stop and start a fresh claim if you do not have the credential.
- GraphQL errors or HTTP 429: respect Retry-After when present, back off, and
  never retry faster than 5 seconds or the returned interval, whichever is
  longer. Check the deadline before retrying.

The token is returned only once. If the successful redemption response is lost,
it cannot be retrieved again. Do not automatically retry a credential-issuing
request after an ambiguous network failure; start a fresh claim instead.

As an alternative to polling, connect to wss://api.stackmachine.dev/graphql using
the `graphql-transport-ws` protocol, initialize the connection, then subscribe:

```graphql
subscription LoginClaimStatus($claimToken: String!) {
  loginClaimStatus(claimToken: $claimToken)
}
```

Subscription events contain only PENDING, APPROVED, DENIED, EXPIRED, or CONSUMED;
they never contain the credential. On APPROVED, call redeemLoginClaim once.
Reconnect with the same private secret to observe current status. Stop watching
on a terminal status or expiry. Keep subscription variables out of logs.

## 4. Save and verify the user access token

The returned token is a personal user access token, not a workspace API token.
Save it as `STACKMACHINE_USER_ACCESS_TOKEN` in your private credential store,
secret manager, or an ignored local environment file. If saving a file, restrict
it to the current user (mode 0600). Never commit it or show it in chat or logs.

Use this user access token for the authenticated GraphQL operations in steps 4-6:

```text
Authorization: Bearer <STACKMACHINE_USER_ACCESS_TOKEN>
Content-Type: application/json
```

Confirm the signed-in account before proceeding:

```graphql
query LoginViewer { viewer { id username } }
```

If viewer is null or GraphQL returns an authentication error, stop and resolve
sign-in. The token belongs to this API environment; do not try it on another API.

## 5. Ask which workspace to use

List the user's workspaces (namespaces) with their IDs and names:

```graphql
query LoginWorkspaces($after: String) {
  viewer {
    namespaces(first: 100, after: $after) {
      edges { node { id name displayName } }
      pageInfo { hasNextPage endCursor }
    }
  }
}
```

Start with `{"after":null}`. While hasNextPage is true, request the next page
with after set to endCursor. A query error or null viewer is not an empty list.

- If there are one or more workspaces, show the names and ask: "Which workspace
  should this integration use? I recommend a workspace API token for it."
  Ask even if there is only one workspace; do not choose automatically.
- If there are no workspaces, ask: "Would you like to create a workspace for
  this integration?" If yes, ask for its name and optional display name.
  Only after the human agrees and confirms the name, call:

```graphql
mutation CreateLoginWorkspace($input: CreateNamespaceInput!) {
  createNamespace(input: $input) {
    namespace { id name displayName }
  }
}
```

Example variables, using the human's chosen name:

```json
{"input":{"name":"my-workspace","displayName":"My workspace"}}
```

Persist both the chosen workspace's ID and its name alongside the credential:

- `STACKMACHINE_WORKSPACE_ID`: the namespace ID for GraphQL API calls.
- `STACKMACHINE_WORKSPACE_NAME`: the namespace name for SDK owner fields.
  Save the returned name, not displayName, so it is available in future sessions.

Use the same private credential store or ignored local environment file used
for the token. These variables are saved metadata; the SDK does not read them
automatically.

If the human declines workspace access or creation, stop workspace setup.
Do not fall back to the broader personal user token for integration work.
The Tokens page linked below explains that alternative if the human requests it.

## 6. Get the recommended workspace API token

For the selected or newly created workspace, reuse a valid API token already in
the private credential store if available. Otherwise ask the human to confirm
creating a token for this integration in that workspace, then call with the
user access token:

```graphql
mutation CreateLoginWorkspaceToken($input: CreateNamespaceAPIKeyInput!) {
  createNamespaceApiKey(input: $input) {
    apiKey { id name }
    keyRaw
  }
}
```

Example variables (namespaceId must be the selected workspace's returned ID):

```json
{"input":{"namespaceId":"<chosen workspace ID>","name":"My coding agent","note":"Integration on this computer"}}
```

Store keyRaw privately as `STACKMACHINE_API_TOKEN`. It is scoped to the selected
workspace. Keep apiKey.id for revocation and retain the saved workspace ID,
workspace name, and API endpoint alongside the credential. Do not put the raw
token in chat or logs.
Keep any saved user access token under `STACKMACHINE_USER_ACCESS_TOKEN` separately.

If the mutation returns errors, null, or no keyRaw, no usable credential has been
received. If permission is denied, ask a workspace administrator to provide an
API token or ask the human to choose another workspace. Do not switch to the
broader user token automatically. Do not retry token creation automatically
after an ambiguous network failure; the raw key may already have been issued.

## 7. Use the credential and revoke it when needed

Send GraphQL requests to https://api.stackmachine.dev/graphql with Authorization: Bearer followed by the chosen
token. Prefer `STACKMACHINE_API_TOKEN` for ordinary integration work.
Pass the workspace token explicitly to the SDK constructor; it does not read
environment variables automatically. Keep SDK clients in server-side code:

```javascript
import StackMachine from "stackmachine";

const token = process.env.STACKMACHINE_API_TOKEN;
if (!token) throw new Error("STACKMACHINE_API_TOKEN is required");
const client = new StackMachine(token, { apiUrl: "https://api.stackmachine.dev/graphql" });
```

Use the selected workspace's name as owner in deployment calls. Verify the token
with a read-only operation permitted in that workspace before making changes.
The SDK also accepts a personal user access token when the human chooses that
access; see https://docs.stackmachine.dev/getting-started/tokens for that alternative.

Workspace API tokens can be revoked from the workspace's API Keys page, or with
this mutation authenticated as a user allowed to manage the workspace's keys:

```graphql
mutation RevokeLoginWorkspaceToken($input: DeleteNamespaceAPIKeyInput!) {
  deleteNamespaceApiKey(input: $input) { success }
}
```

Variables: `{"input":{"apiKeyId":"<saved workspace API key ID>"}}`.

The user access token has the approving account's full permissions and can be
revoked from https://dashboard.stackmachine.dev/<username>/access-tokens. Keep it only if needed for future user-level operations;
revoking it does not revoke the separately created workspace API token.
An agent can revoke its user access token with this authenticated mutation:

```graphql
mutation RevokeLoginUserToken($input: RevokeAPITokenInput!) {
  revokeApiToken(input: $input) { success }
}
```

Variables: `{"input":{"token":"<STACKMACHINE_USER_ACCESS_TOKEN>"}}`.
