Skip to content

Agent Self-Serve Quickstart

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

What You Will Build

  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

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

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

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

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

sigid-cli 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 Paste into your app

curl track

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.

# 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 (CLI does this; custom miners must meet difficulty + duration).

# 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"'",
    "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-cli 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)

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

# CLI
sigid-cli 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.

Step 2 – Create An Application (If Needed)

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

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

curl track

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

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

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

curl track

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

  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

CLI track

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

curl track

# 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)

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

Symptom Cause What to do
PoW start/complete fails Bad ownership proof, expired challenge, insufficient difficulty/duration, registration disabled Regenerate proof; restart PoW; check tenant registration policy
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-cli 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

  • No tokens, secrets, device_code, or private keys in logs, tickets, or git
  • client_secret captured once or rotated via sigid-cli app rotate-secret
  • Unused delegations revoked via sigid-cli 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