> **Tardis.dev Account API**
>
> Resource server: https://api.tardis.dev
> Protected resource metadata: https://api.tardis.dev/.well-known/oauth-protected-resource
>
> The token exchange below uses the default audience for the REST Account API. For MCP, add `--data-urlencode "resource=https://api.tardis.dev/mcp"` to the token exchange request.
>
> Register with `type: service_auth` and set `login_hint` to the user's email address. New users can sign up from the verification link. After exchanging the identity assertion for an access token, call `POST https://api.tardis.dev/v1/account` (`setup_account`); it creates the user's account or returns the existing one.
>
> An agent can use a permission (OAuth scope) only when its token was granted it and the authorizing user's role has it. The Permissions table in the OpenAPI document lists the roles of each permission. This also applies over MCP.
>
> Remove hyphens from the displayed user code before submitting it to WorkOS.
>
> If the verification page reports a different signed-in account, sign out and sign in with the `login_hint` email, then reopen the verification link.
>
> If WorkOS returns `invalid_login_hint`, restart registration with the email address the user will use to sign in.

# auth.md — agent registration

You are an agent. This service allows you to register for an identity and exchange it for credentials.

Run these commands in order. **Don't print secret values and don't write your own scripts** — the commands below persist secrets and surface only non-secret fields. Commands are macOS (`curl`, `security`, `jq`); adapt as needed for Linux (`secret-tool`, reads stdin) or Windows (PowerShell `Set-Secret`/`Get-Secret`). Only `security` takes the secret on argv — `secret-tool`/`Set-Secret` don't, so don't carry its "rotate if exposed" caveat to them.

Set once:

```bash
ISSUER="https://auth.tardis.dev"
SVC="${ISSUER#https://}"        # keychain service label (issuer host)
EMAIL="<user-email>"           # login_hint, and keychain account for email-based methods
```

## 1. Check for an existing registration — reuse before creating a new one

```bash
if   security find-generic-password -s "$SVC" -a "$EMAIL"  -w >/dev/null 2>&1; then ACCT="$EMAIL"
fi
# ACCT set -> skip to step 4 (Exchange). ACCT unset -> continue to step 2.
```

## 2. Register — pick the method that fits; capture into `$REG`, never print it

```bash
# service_auth — you have the user's email (claim ceremony required):
REG="$(curl -sS "$ISSUER/agent/identity" -H 'Content-Type: application/json' \
  -d "{\"type\":\"service_auth\",\"login_hint\":\"$EMAIL\"}")"
ACCT="$EMAIL"
# errors: invalid_request -> fix the body; invalid_login_hint -> fix EMAIL; service_auth_registration_disabled -> method not enabled for this environment.
```

**Don't persist `$REG` yet** — the service_auth response holds only a short-lived `claim.token`. Keep it in memory; persist after the claim verifies (step 3).

## 3. Claim ceremony — service_auth (required)

Mint an attempt and give the user its link. They sign in there and the page shows them a code; once they read it back to you, complete the claim with it.

```bash
CLAIM_TOKEN="$(printf '%s' "$REG" | jq -r .claim.token)"
ATT="$(curl -sS "$ISSUER/agent/identity/claim" -H 'Content-Type: application/json' \
  -d "{\"type\":\"service_auth\",\"claim_token\":\"$CLAIM_TOKEN\",\"login_hint\":\"$EMAIL\"}")"
# errors: invalid_claim_token -> restart at step 2; invalid_login_hint -> fix EMAIL; claim_expired | claim_revoked | already_claimed -> restart at step 2; auth_method_disabled -> method was disabled; too_many_attempts -> wait for a pending attempt to expire.

# give the user this link (non-secret); the code is shown to them on the page, not here:
printf '%s' "$ATT" | jq '{verification_uri:.attempt.verification_uri}'

# the user reads the code off that page and gives it to you; submit it with the claim token:
USER_CODE="<code the user read off the claim page>"
VER="$(curl -sS "$ISSUER/agent/identity/claim/complete" -H 'Content-Type: application/json' \
  -d "{\"claim_token\":\"$CLAIM_TOKEN\",\"user_code\":\"$USER_CODE\"}")"

# success returns the verified identity once — persist it; an error returns {code,message}:
printf '%s' "$VER" | jq -e .identity.assertion >/dev/null && security add-generic-password -U -s "$SVC" -a "$ACCT" -w "$VER"
# errors: claim_not_confirmed -> user hasn't finished on the page yet, wait and retry; invalid_user_code -> wrong code, ask the user again; user_code_expired -> re-run this step for a fresh link; claim_expired | already_claimed -> restart at step 2; claim_denied -> user denied the claim, restart at step 2; auth_method_disabled -> method was disabled; organization_selection_required | stale_organization_selection -> user must re-select an organization on the claim page.
```

## 4. Exchange the assertion for an access token

```bash
ASSERTION="$(security find-generic-password -s "$SVC" -a "$ACCT" -w | jq -r .identity.assertion)"
CRED="$(curl -sS "$ISSUER/oauth2/token" -H 'Content-Type: application/x-www-form-urlencoded' \
  -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
  --data-urlencode "assertion=$ASSERTION")"
ACCESS_TOKEN="$(printf '%s' "$CRED" | jq -r .access_token)"
# errors: invalid_request -> assertion could not be decoded; invalid_grant -> assertion expired/revoked, restart at step 2; invalid_target -> resource URI not recognized; unsupported_grant_type -> use the grant above.
```

Send it as `Authorization: Bearer $ACCESS_TOKEN`. **Don't persist the access token** — it's short-lived (~10 minutes). When it expires, re-run this step from the stored assertion; when the assertion itself expires, refresh (step 5).

## 5. Refresh — when the stored assertion nears expiry

```bash
RT="$(security find-generic-password -s "$SVC" -a "$ACCT" -w | jq -r .identity.refresh_token.value)"
REG="$(curl -sS "$ISSUER/agent/identity" -H 'Content-Type: application/json' \
  -d "{\"type\":\"refresh\",\"refresh_token\":\"$RT\"}")"
security add-generic-password -U -s "$SVC" -a "$ACCT" -w "$REG"   # rotates the refresh token; overwrite
# errors: invalid_refresh_token -> restart at step 2.
```

Then re-run step 4 with the fresh assertion.

## Any request

`5xx` -> back off and retry the same request. `rate_limit_exceeded` -> wait `retry_after` seconds. A `4xx` not listed above -> fix per the response body; don't replay.


After authorization, the [Account API OpenAPI document](https://api.tardis.dev/v1/account/openapi.json) describes the operations.
