Skip to content

Agent Self-Serve Quickstart

Ask about this page: Claude ChatGPT Grok

Cold agents can provision a sandbox workspace, create an application, and obtain human-delegated authority without a human first creating the account in a dashboard. This page is the single mechanical path.

Hosted end-user authorize: sandbox bootstrap returns fixture_end_user – one ordinary human at @test.sigid.dev + once-only password for the new environment. Use those credentials on the real hosted login for your app’s authorize leg (mail to that domain is never delivered). Mint more (up to 5 active): POST /api/v1/sandbox/fixture-users. Rotate: POST /api/v1/sandbox/fixture-users/{user_id}/rotate-password (users:manage). Agent identities are not stand-ins for human OIDC. Beyond the fixture cap use signup, invite, or AAL2 admin create. Fixtures are retired on sandbox → production activation.

For the required provision → fixture-test → ownership-transfer → human preview and activation → branded retest sequence, including how to preserve application-owned sample data for the developer, follow Provision, Test, And Hand Off.

Human-managed agent CRUD (portal or POST /api/v1/agents with an existing bearer) is documented separately in Agent Registration.

Time: 10–20 minutes after the IDP is reachable. Step 4 always requires a human browser session (AAL2 + fresh authentication).

For Node.js or Bun environments without native CLI execution, use the JavaScript Agent SDK. It implements the same bootstrap, signing, proof-of-work, and human-delegation flow through npm imports.

What You Will Build

Ask:
  1. An agent identity on the control-plane tenant (PoW-gated).
  2. A sandbox organization, environment, and customer OAuth application.
  3. A device-flow delegation request with verification_uri_complete.
  4. Human approval on the hosted consent page.
  5. A delegated access token whose act chain carries the human’s AAL2 evidence.

Before You Start

Ask:
Value Production default Local Docker Compose
IDP base / issuer host https://auth.sigid.org http://auth.sigid.localhost:3000
Control-plane tenant sigid sigid
App redirect URI your app callback http://localhost:3000/
Delegation audience tenant issuer or API audience often sigid or the tenant issuer
Delegation scopes scopes the human holds e.g. applications:manage

Set:

export SIGID_IDP="${SIGID_IDP:-https://auth.sigid.org}"
# Local stack example:
# export SIGID_IDP="http://auth.sigid.localhost:3000"

Security rules for this entire page:

  • Never log or paste into tickets: access tokens, refresh tokens, client_secret, device_code, private keys, keystore passphrases, or raw user_code values beyond what the human must type once.
  • Device-delegation routes require a direct (non-delegated) agent bearer. Do not overwrite the agent token cache with the delegated token.
  • Prefer sigid for key generation, PoW, and key-ownership proofs. Raw HTTP is shown for wire transparency; crypto fields are non-trivial by hand.

Optional one-shot check (CLI track) after install:

# From a docs checkout, or copy the script next to your agent:
bash docs/developers/agent-quickstart.sh --help

Step 1 – Self-Register And Bootstrap Workspace

Ask:

The preferred path is one PoW-gated composite that creates the agent, sandbox organization, environment, and application.

Ask:
sigid setup \
  --idp "$SIGID_IDP" \
  --name my-agent \
  --redirect-uri http://localhost:3000/

# Optional re-issue of co-owner invite:
# sigid invite --organization-id <org_id> --idp "$SIGID_IDP"

sigid verify-setup --idp "$SIGID_IDP"

What you get:

Field Use
agent_id Agent identity transferred by operator handoff
application_id Application resource included in operator handoff
client_id / optional client_secret Customer app OAuth client (client_secret printed once)
tenant_issuer Tenant OIDC issuer
organization_id Control-plane org for invites / activation
Cached tenant agent token Direct bearer for later steps (tokens.enc)
env_block / start_snippet Use the snippet for a drop-in page; map generic environment values with Integration Settings for framework apps

curl track

Ask:

PoW mining and key_ownership_proof require a local signing key. Prefer the CLI unless you already implement the ownership-proof format from Agent Authentication / core agent identity types.

# 0) Discover the control-plane UUID and canonical proof audience anonymously.
curl -sS "$SIGID_IDP/api/v1/agents/workspace/bootstrap"
# Sign key_ownership_proof with the returned tenant_id UUID and issuer.
# The routing slug "sigid" is not a valid substitute in the signature payload.

# 1) Start PoW (control-plane host; body is the same registration shape as
#    POST /api/v1/agents/auth/register/pow – name, anchor_type, public_key,
#    key_algorithm, key_ownership_proof).
curl -sS "$SIGID_IDP/api/v1/agents/workspace/bootstrap/pow" \
  -X POST \
  -H "content-type: application/json" \
  -d @bootstrap-pow-start.json
# → challenge_id, challenge_token, difficulty_bits, min_duration_seconds, …

# 2) Mine the challenge and store its decimal nonce as POW_NONCE.
# min_duration_seconds is a wall-clock floor, separate from difficulty_bits.
# Wait until that floor has elapsed before completing; waiting alone is not a proof.

# 3) Complete bootstrap
curl -sS "$SIGID_IDP/api/v1/agents/workspace/bootstrap/pow/complete" \
  -X POST \
  -H "content-type: application/json" \
  -d '{
    "challenge_id": "'"$CHALLENGE_ID"'",
    "nonce": "'"$POW_NONCE"'",
    "organization_name": "My Agent Workspace",
    "redirect_uris": ["http://localhost:3000/"],
    "issue_operator_invite": true,
    "framework": "generic"
  }'
# → agent, organization_*, tenant_*, application_id, client_id, client_secret?,
#   fixture_end_user, control_plane_access_token, tenant_access_token,
#   start_snippet, env_block, …

Store tenant_access_token as AGENT_ACCESS_TOKEN for later steps. Do not log it.

The bootstrap invite makes a human co-owner but leaves the agent identity under its previous ownership. After integration testing, use sigid handoff with the printed agent, organization, and application IDs to transfer the agent and grant the developer the complete resource package.

Alternate: register only (no workspace)

Ask:

If you only need an agent principal on a tenant that already admits agents:

# CLI
sigid init --idp "$SIGID_IDP" --tenant-id "$SIGID_TENANT_ID" --name my-agent --anchor-type did_key --algo ed25519

# HTTP (paths – not the stale /auth/agent/* aliases)
# POST $SIGID_IDP/api/v1/agents/auth/register
# POST $SIGID_IDP/api/v1/agents/auth/register/pow
# POST $SIGID_IDP/api/v1/agents/auth/register/pow/complete

init / bare register does not create an org or application. Use Step 2 only when you already hold a tenant token with applications:manage.

Recover a failed setup or continue from autonomous registration

Ask:

On servers before the reserved organization-name fix, names beginning with sigid (for example sigid-lab-try5) cause a generic completion 400 at stage=create_organization: PoW and agent registration have already succeeded. Until the server fix is deployed, use a name such as lab-try6 for a fresh workspace. Fixed servers preserve the requested display name and generate an organization slug outside the reserved namespace, such as org-sigid-lab-try5-workspace-<suffix>.

The npm CLI 0.3.1 (embedded binary version 2.0.1) saves its new key only after successful setup. A failed attempt on that version may therefore leave a registered agent without a saved key; the recovery below requires that the key actually exists. A server update cannot recover a discarded private key. Do not replay a consumed challenge. Preserve the failed request/agent IDs for operator inspection, and use a fresh setup after the server fix when no key was saved. CLI builds containing the key-retention fix behave as described below.

setup saves its signing key under --name before requesting PoW. If completion fails or the response is lost, keep that key. Registration and subsequent provisioning steps commit separately: an error does not prove that nothing was created. A duplicate key label is rejected before another PoW challenge starts.

Authenticate with the saved key (or use the control-plane token from autonomous authentication), then inspect existing resources before creating replacements:

sigid auth --mode agent --key <setup-name> --idp "$SIGID_IDP" \
  --tenant-id <discovered-control-plane-uuid> \
  --scope 'openid profile org:read org:create'

Follow the workspace workflow using these control-plane APIs in order:

  1. List GET /api/v1/control-plane/organizations. Reuse the intended existing organization; create one with POST to the same collection only if absent.
  2. POST /api/v1/control-plane/organizations/{organization_id}/token returns an organization-bound token. Use that token for subsequent organization calls.
  3. List GET /api/v1/control-plane/organizations/{organization_id}/tenants. Reuse an existing environment; create one with POST only if absent.
  4. POST /api/v1/control-plane/organizations/{organization_id}/tenants/{tenant_id}/switch returns a tenant agent token with the owner's application grants.
  5. List /api/v1/applications with that tenant token; create the missing app only if absent. A partial composite setup can recover its fixture password using POST /api/v1/sandbox/fixture-user/rotate-password with users:manage.

Direct agent switching retains the agent subject and issues no human browser cookies. Requesting applications:manage directly from challenge authentication is not a substitute for organization elevation and tenant switching. DCR's initial access token is a separate credential, not the autonomous agent token.

If key authentication fails because registration never completed, preserve the failure's request ID and check the server's completion-stage log before starting another setup with a new name. A missing nonce is rejected before challenge consumption and can be resubmitted with the mined nonce while still valid; post-registration failures require inspecting existing resources, not replaying an already-consumed completion request.

Step 2 – Create An Application (If Needed)

Ask:

Bootstrap already created one application. Create another only when you need a second client.

Requires a tenant-scoped direct agent token with applications:manage (bootstrap’s tenant_access_token / CLI cache after setup).

CLI track

Ask:
sigid app create \
  --idp "$SIGID_IDP" \
  --name "Web App" \
  --redirect-uri http://localhost:3000/

curl track

Ask:
curl -sS "$SIGID_IDP/api/v1/applications" \
  -X POST \
  -H "authorization: Bearer $AGENT_ACCESS_TOKEN" \
  -H "content-type: application/json" \
  -H "idempotency-key: $(uuidgen)" \
  -d '{
    "name": "Web App",
    "redirect_uris": ["http://localhost:3000/"]
  }'
# 201 → id, client_id, client_secret (once), audience, …
Update an application (app update) `app update` is a **read-modify-write**: it fetches the current application, overlays your flags, and PUTs the merged state. This matters because the server's `PUT` is full-replacement – omitting fields would clear them.
# Rename and add a redirect URI; scopes / grant types / web origins are preserved.
sigid app update "$APP_ID" \
  --idp "$SIGID_IDP" \
  --name "Web App (renamed)" \
  --redirect-uri https://app.example.com/cb \
  --auth-profile strict
Repeatable replace-only flags: `--redirect-uri`, `--scope`, `--grant-type`, `--web-origin`. Pass `--description ""` to clear the description.

Step 3 – Initiate Device Delegation

Ask:

Request human-delegated authority through an RFC 8628-shaped ceremony.

Use a direct agent bearer for the managed tenant (not a delegated token).

CLI track

Ask:
sigid delegation create \
  --idp "$SIGID_IDP" \
  --audience sigid \
  --scope applications:manage
# → absolute_link, user_code_display, device_code, expires_in, interval

curl track

Ask:
curl -sS "$SIGID_IDP/api/v1/agents/delegations/device" \
  -X POST \
  -H "authorization: Bearer $AGENT_ACCESS_TOKEN" \
  -H "content-type: application/json" \
  -d '{
    "audience": "sigid",
    "scope": "applications:manage"
  }'

Response shape

{
  "device_code": "<secret>",
  "user_code": "ABCDEFGH",
  "user_code_display": "ABCD-EFGH",
  "verification_uri": "/device/delegation/ABCDEFGH",
  "verification_uri_complete": "/device/delegation/ABCDEFGH?complete=1",
  "expires_in": 900,
  "interval": 5
}
Field Semantics
device_code Agent-only secret for poll; hashed at rest
user_code / user_code_display Human-facing code
verification_uri_complete Relative path; prefix with $SIGID_IDP for the absolute link
interval Minimum poll spacing (seconds)
export DEVICE_CODE='…'   # from response; keep private
export DELEGATION_LINK="${SIGID_IDP}/device/delegation/${USER_CODE}?complete=1"

Step 4 – Human Approves

Ask:
  1. Open verification_uri_complete (absolute URL) in a browser, or type the user code on the verification page.
  2. Sign in or sign up on the hosted identity surface.
  3. Complete MFA / step-up until the session is AAL2 and fresh.
  4. Review agent identity, audience, and scopes; Approve or Deny.

Hosted routes (human browser, not agent JSON):

Method + path Role
GET /device/delegation/{user_code} Consent page
POST /device/delegation/{user_code}/approve Approve (AAL2 + fresh)
POST /device/delegation/{user_code}/deny Deny

Scopes granted are the intersection of what you requested and what the human actually holds at approval time. Empty intersection fails at approve.

Step 5 – Poll For The Delegated Token

Ask:

CLI track

Ask:
sigid delegation poll "$DEVICE_CODE" --idp "$SIGID_IDP"
# Optional single check: sigid delegation poll "$DEVICE_CODE" --once --idp "$SIGID_IDP"

curl track

Ask:
# Respect interval from initiate (default often 5s). On slow_down, increase wait.
while true; do
  resp="$(curl -sS -w '\n%{http_code}' "$SIGID_IDP/api/v1/agents/delegations/device/token" \
    -X POST \
    -H "authorization: Bearer $AGENT_ACCESS_TOKEN" \
    -H "content-type: application/json" \
    -d "{\"device_code\":\"$DEVICE_CODE\"}")" || true
  body="$(printf '%s' "$resp" | sed '$d')"
  code="$(printf '%s' "$resp" | tail -n1)"
  case "$code" in
    200)
      # Success: JSON token bundle. Capture to a secret store; do not echo.
      printf '%s\n' "$body" > /dev/null
      break
      ;;
    400)
      err="$(printf '%s' "$body" | sed -n 's/.*"error"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)"
      case "$err" in
        authorization_pending) sleep 5 ;;
        slow_down) sleep 10 ;;
        access_denied|expired_token) echo "terminal: $err" >&2; exit 1 ;;
        *) echo "unexpected error body" >&2; exit 1 ;;
      esac
      ;;
    *) echo "unexpected HTTP $code" >&2; exit 1 ;;
  esac
done

Success body (shape)

{
  "access_token": "<delegated>",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "applications:manage",
  "refresh_token": "<optional>"
}

Validate before use: signature, iss, aud, expiry, tenant, scopes, subject_type, and delegated act (human AAL evidence on the chain). See Verify Access Tokens.

Step 6 – Revoke A Delegation (Optional)

Ask:

Revoke an issued delegation by its delegation id (not a device code). This hits POST /api/v1/delegations/{id}/revoke – a different router from the device flow – and the server enforces fresh + highest-AAL step-up. If the cached agent token is rejected (401/403), pass a freshly minted stepped-up token via --access-token.

sigid delegation revoke "$DELEGATION_ID" --idp "$SIGID_IDP"

Revocation is immediate and reflected on the backend; subsequent use of the delegated token is rejected at token-exchange time.

Failure Modes

Ask:
Symptom Cause What to do
PoW start/complete fails Bad ownership proof, expired challenge, insufficient difficulty/duration, registration disabled Discover the issuer and control-plane UUID with GET /api/v1/agents/workspace/bootstrap; sign that binding, not the sigid slug; regenerate proof and check tenant registration policy
Bootstrap completion 400 Missing/invalid nonce or a later provisioning rejection; elapsed minimum duration does not establish success Send the decimal nonce as a JSON string, record the full problem type and request ID, and inspect the server workspace bootstrap failed stage. Keep the saved key and follow the recovery steps above
Bootstrap 401 Proof/issuer/tenant mismatch or an ingress refusal; this does not imply a dashboard-session requirement Record the failing phase, sanitized RFC 9457 type/detail, request ID, CLI version (sigid --version), and server release. Never include keys, tokens, fixture passwords, or invitation links. Deploy the matching server/CLI and retry with a fresh proof
Bootstrap / app create 403 Missing applications:manage or wrong tenant token Use bootstrap tenant token; re-run setup or switch tenant
Device initiate 403 Not a direct agent principal, inactive agent, or delegated bearer Re-auth with sigid auth / setup; never poll with delegated token
Empty scope / approve fails Requested scopes not held by human or not delegatable for audience Narrow --scope; human must hold each scope
Poll authorization_pending Human has not finished Keep polling at interval
Poll slow_down Polling too fast Increase wait (Retry-Interval header or +5s)
Poll access_denied Human denied Stop; start a new device request if still needed
Poll expired_token Device request timed out Start a new delegation create
Approve blocked / step-up loop Human not AAL2 or session not fresh (~5 min) Complete MFA; re-authenticate freshly
AAL2 routes still reject after poll Freshness window elapsed or missing act validation Re-run device flow; validate act / auth_time on the token
delegation revoke 401/403 Cached token not fresh or not highest-AAL (step-up required) Re-auth freshly; pass stepped-up token via --access-token

Security Checklist

Ask:
  • No tokens, secrets, device_code, or private keys in logs, tickets, or git
  • client_secret captured once or rotated via sigid app rotate-secret
  • Unused delegations revoked via sigid delegation revoke
  • Direct agent token kept separate from delegated token
  • Least-privilege scopes on device initiate
  • Operator invite / co-owner path used before treating sandbox as long-lived
  • Hosted authorize tested with fixture humans and application-owned sample data recorded
  • Operator handoff accepted by the developer (invite alone does not transfer the agent)

See Also

Ask: