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).
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¶
- An agent identity on the control-plane tenant (PoW-gated).
- A sandbox organization, environment, and customer OAuth application.
- A device-flow delegation request with
verification_uri_complete. - Human approval on the hosted consent page.
- A delegated access token whose
actchain 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 rawuser_codevalues 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
sigidfor 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.
CLI track (recommended)¶
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¶
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)¶
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¶
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:
- List
GET /api/v1/control-plane/organizations. Reuse the intended existing organization; create one withPOSTto the same collection only if absent. POST /api/v1/control-plane/organizations/{organization_id}/tokenreturns an organization-bound token. Use that token for subsequent organization calls.- List
GET /api/v1/control-plane/organizations/{organization_id}/tenants. Reuse an existing environment; create one withPOSTonly if absent. POST /api/v1/control-plane/organizations/{organization_id}/tenants/{tenant_id}/switchreturns a tenant agent token with the owner's application grants.- List
/api/v1/applicationswith that tenant token; create the missing app only if absent. A partial composite setup can recover its fixture password usingPOST /api/v1/sandbox/fixture-user/rotate-passwordwithusers: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)¶
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¶
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.
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 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¶
- Open
verification_uri_complete(absolute URL) in a browser, or type the user code on the verification page. - Sign in or sign up on the hosted identity surface.
- Complete MFA / step-up until the session is AAL2 and fresh.
- 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 delegation poll "$DEVICE_CODE" --idp "$SIGID_IDP"
# Optional single check: sigid 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.
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 | 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¶
- No tokens, secrets,
device_code, or private keys in logs, tickets, or git -
client_secretcaptured once or rotated viasigid 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¶
- Agent And MCP Auth – integration overview
- Agent Registration – tenant-scoped agent CRUD
- Agent Authentication – challenge-response runtime auth
- Delegation And Token Exchange – RFC 8693 exchange (after a grant exists)
- Use Vault And Egress – discover vault credentials, call external APIs through egress, sign SSH certificates
- Agent CLI – full
sigidreference - Public shortest path: www quickstart
- Self-test script: agent-quickstart.sh