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¶
- 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
sigid-clifor 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-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.
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¶
- 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-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.
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_secretcaptured once or rotated viasigid-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¶
- 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
sigid-clireference - Public shortest path: www quickstart
- Self-test script: agent-quickstart.sh