# Agent skills

This is the agent-facing manual for GAUTH. It is written to be copied whole
into an agent prompt.

The live, version-stamped copy is generated by the running service at
`/skills.md`. It fills in this deployment's exact base URLs, token
lifetimes and password minimum. Use this page when you need a stable copy.

## Connection facts

| what | value |
|---|---|
| public base URL | `https://auth.gumpworks.com` |
| API base | `https://auth.gumpworks.com/api/v1` |
| management API | `https://auth.gumpworks.com/api/v1/manage` |
| client auth header | `Authorization: Bearer gtk_...` |
| token type | `Bearer`, prefix `cgt_` |
| client token lifetime | 43200 seconds by default |
| app token lifetime | 3600 seconds by default |
| minimum password length | 10 characters |

## Two kinds of account

A client account owns applications. It signs in at `/login`, registers
apps, and holds a management key (`gtk_...`). Client sign-ins are strictly
rate-limited: the wait doubles after each failure. Read `retry_after` and back
off.

An app user belongs to one application. App users sign in through that app with
the app's `app_key` and `app_secret` plus their username and password. App user
sign-ins are lenient by default: the first few are free, then the wait doubles
up to the app's configured cap. A client can tune this per app.

## Concepts

- client - a `cid` like `c_4m2x9p`. Owns apps. Has a management key.
- app - an `app_key` (public) and an `app_secret` (private, shown once).
- sub-app - an app with `parent_app_id` set. It shares its parent's user pool,
  so one account works across the family. Opt in per app.
- app user - a `uid` like `a_9k2m4x`. Scoped to one app or app family. Has a
  password and an optional long-lived key (`gak_...`).
- CGT - the ephemeral token. No refresh tokens: re-run the token request.

The `uid` is the stable join key for your own database. It is not a secret and
it never changes. The token is ephemeral; the uid is not.

## Bootstrap in 30 seconds

```bash
export GAUTH="https://auth.gumpworks.com"
export API="$GAUTH/api/v1"
export MANAGE="$API/manage"

# 1. sign up a client account at /signup, or ask your administrator
#    the response includes a management key: gtk_...

# 2. as that client, create an app
curl -sS -X POST "$MANAGE/apps" \
  -H "Authorization: Bearer gtk_..." -H 'Content-Type: application/json' \
  -d '{"name":"acme-app","redirect_uris":["https://acme.example/cb"]}'
# -> {"id":1,"app_key":"app_...","app_secret":"gsk_...", ...}

# 3. create an app user
curl -sS -X POST "$MANAGE/apps/1/users" \
  -H "Authorization: Bearer gtk_..." -H 'Content-Type: application/json' \
  -d '{"app_username":"alice","password":"at-least-10-chars"}'
# -> {"uid":"a_9k2m4x","api_key":"gak_...", ...}

# 4. trade credentials for a CGT
curl -sS -X POST "$API/token" \
  -H 'Content-Type: application/json' \
  -d '{"grant_type":"password","client_id":"app_...","client_secret":"gsk_...","username":"alice","password":"..."}'
# -> {"access_token":"cgt_...","uid":"a_9k2m4x","app":"app_...", ...}

# 5. use it
curl -sS "$API/userinfo" -H "Authorization: Bearer cgt_..."
```

## Public API

Base: `https://auth.gumpworks.com/api/v1`

### POST /token

| grant_type | fields | result |
|---|---|---|
| `password` (client) | `username`, `password` | client token, scope `client` |
| `password` (app user) | `client_id`, `client_secret`, `username`, `password` | app user token, scope `app profile` |
| `client_credentials` | `client_id`, `client_secret` | app token, no user |
| `authorization_code` | `code`, `client_id`, `client_secret`, `redirect_uri` | app user token |

```bash
# client
curl -sS -X POST "$API/token" -H 'Content-Type: application/json' \
  -d '{"grant_type":"password","username":"acme","password":"..."}'

# app user
curl -sS -X POST "$API/token" -H 'Content-Type: application/json' \
  -d '{"grant_type":"password","client_id":"app_...","client_secret":"gsk_...","username":"alice","password":"..."}'

# server to server
curl -sS -X POST "$API/token" -H 'Content-Type: application/json' \
  -d '{"grant_type":"client_credentials","client_id":"app_...","client_secret":"gsk_..."}'
```

Response:

```json
{
  "access_token": "cgt_...",
  "certified_gump_token": "cgt_...",
  "token_type": "Bearer",
  "expires_in": 43200,
  "scope": "app profile",
  "issued_at": 1761200000,
  "sub": "a_9k2m4x",
  "uid": "a_9k2m4x",
  "app": "app_1a2b3c4d5e6f7a8b",
  "app_username": "alice",
  "client": "c_4m2x9p"
}
```

### GET /userinfo (alias: GET /me)

```bash
curl -sS "$API/userinfo" -H "Authorization: Bearer $CGT"
```

```json
{
  "active": true,
  "sub": "a_9k2m4x",
  "uid": "a_9k2m4x",
  "app_username": "alice",
  "email": "alice@example.com",
  "display_name": "Alice",
  "status": "active",
  "app": "app_1a2b3c4d5e6f7a8b",
  "app_name": "acme-app",
  "client": "c_4m2x9p",
  "scope": "app profile",
  "expires_at": 1761243200,
  "expires_in": 43199,
  "token_kind": "api"
}
```

A client token returns `cid` instead of `uid` and `app`.

### PATCH /me, POST /me/password, POST /me/delete-request

Client tokens only.

```bash
curl -sS -X PATCH "$API/me" -H "Authorization: Bearer $CGT" \
  -H 'Content-Type: application/json' -d '{"email":"new@example.com"}'

curl -sS -X POST "$API/me/password" -H "Authorization: Bearer $CGT" \
  -H 'Content-Type: application/json' \
  -d '{"current_password":"old","new_password":"new-at-least-10-chars"}'
```

### GET /apps

The client's own apps.

```bash
curl -sS "$API/apps" -H "Authorization: Bearer $CGT"
```

### POST /accounts

Create an app user. A tenant key creates users under its own apps.

```bash
curl -sS -X POST "$API/accounts" -H "Authorization: Bearer gtk_..." \
  -H 'Content-Type: application/json' \
  -d '{"app":"app_...","username":"alice","password":"at-least-10-chars"}'
```

URI style:

```bash
curl -sS -X POST "$API/tenants/c_4m2x9p/apps/app_.../accounts" \
  -H "Authorization: Bearer gtk_..." -H 'Content-Type: application/json' \
  -d '{"username":"alice","password":"at-least-10-chars"}'
```

### POST /apps/{app_key}/password

An app user changing their own password.

```bash
curl -sS -X POST "$API/apps/app_1a2b3c4d5e6f7a8b/password" \
  -H "Authorization: Bearer $CGT" -H 'Content-Type: application/json' \
  -d '{"current_password":"old","new_password":"new-at-least-10-chars"}'
```

### POST /introspect, POST /revoke

```bash
curl -sS -X POST "$API/introspect" -H 'Content-Type: application/json' \
  -d '{"token":"cgt_...","client_id":"app_...","client_secret":"gsk_..."}'

curl -sS -X POST "$API/revoke" -H 'Content-Type: application/json' \
  -d '{"token":"cgt_..."}'
```

### GET /health

Unauthenticated liveness probe.

## Management API

Base: `https://auth.gumpworks.com/api/v1/manage`

Authenticate with `Authorization: Bearer gtk_...`. A client key can only see
and change that client's own apps, users, keys and sessions.

### Meta

```bash
curl -sS "$MANAGE/health" -H "$AUTH"
curl -sS "$MANAGE/stats" -H "$AUTH"
curl -sS "$MANAGE/settings" -H "$AUTH"
```

### Apps

```bash
curl -sS "$MANAGE/apps" -H "$AUTH"

curl -sS -X POST "$MANAGE/apps" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"name":"acme-app","redirect_uris":["https://acme.example/cb"]}'

# a sub-app shares its parent's user pool
curl -sS -X POST "$MANAGE/apps" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"name":"acme-app-mobile","parent_app_id":1}'

curl -sS -X PATCH "$MANAGE/apps/1" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"status":"disabled"}'
curl -sS -X POST "$MANAGE/apps/1/rotate-secret" -H "$AUTH"
curl -sS -X DELETE "$MANAGE/apps/1" -H "$AUTH"
```

### App users

```bash
curl -sS "$MANAGE/apps/1/users" -H "$AUTH"

curl -sS -X POST "$MANAGE/apps/1/users" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"app_username":"alice","password":"at-least-10-chars"}'
# -> {"uid":"a_9k2m4x","api_key":"gak_...", ...}

curl -sS -X PATCH "$MANAGE/apps/1/users/a_9k2m4x" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"status":"disabled"}'
curl -sS -X POST "$MANAGE/apps/1/users/a_9k2m4x/password" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"password":"new-password-here"}'
curl -sS -X POST "$MANAGE/apps/1/users/a_9k2m4x/rotate-key" -H "$AUTH"
curl -sS -X DELETE "$MANAGE/apps/1/users/a_9k2m4x" -H "$AUTH"
```

### Keys

A principal may hold any number of labelled keys. Every key is returned in
full exactly once, at creation; afterwards only its hint is visible. Revoke
one key without touching the others, or rotate to revoke them all at once.

```bash
# client management keys
curl -sS "$MANAGE/clients/1/keys" -H "$AUTH"
curl -sS -X POST "$MANAGE/clients/1/keys" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"label":"ci"}'
# -> {"id":7,"label":"ci","api_key":"gtk_...","warning":"..."}
curl -sS -X DELETE "$MANAGE/clients/1/keys/7" -H "$AUTH"

# app secrets
curl -sS "$MANAGE/apps/1/keys" -H "$AUTH"
curl -sS -X POST "$MANAGE/apps/1/keys" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"label":"staging"}'
curl -sS -X DELETE "$MANAGE/apps/1/keys/8" -H "$AUTH"

# app user keys
curl -sS "$MANAGE/apps/1/users/a_9k2m4x/keys" -H "$AUTH"
curl -sS -X POST "$MANAGE/apps/1/users/a_9k2m4x/keys" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"label":"phone"}'
curl -sS -X DELETE "$MANAGE/apps/1/users/a_9k2m4x/keys/9" -H "$AUTH"
```

`rotate-key` and `rotate-secret` are shortcuts for "revoke every key, issue
one". Self-service equivalents live at `GET|POST /me/keys` and
`DELETE /me/keys/{id}` on the public API, authenticated with the caller's own
token.

### Sessions, audit and lockouts

```bash
curl -sS "$MANAGE/sessions" -H "$AUTH"
curl -sS -X DELETE "$MANAGE/sessions/42" -H "$AUTH"
curl -sS "$MANAGE/audit?limit=100" -H "$AUTH"
curl -sS "$MANAGE/security/lockouts" -H "$AUTH"
curl -sS -X POST "$MANAGE/security/unblock" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"username":"alice"}'
```

### Sign-in policy

```bash
curl -sS -X PATCH "$MANAGE/settings" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"app_user_policy":{"start_after":5,"cap":60,"burst_gap":300}}'
```

Bounds: `start_after` 0-20, `cap` 5-300 seconds, `burst_gap` 60-3600 seconds.
The server rejects values outside them.

## Error format

Every error is a machine code plus one plain sentence.

```json
{"error": "invalid_credentials", "message": "Username or password is incorrect. Next attempt in 4 seconds.", "retry_after": 4}
```

| error | meaning |
|---|---|
| `invalid_request` | a required field is missing or malformed |
| `invalid_client` | unknown app, disabled app, or bad app secret |
| `invalid_credentials` | wrong username or password |
| `invalid_grant` | account disabled, or code expired or used |
| `invalid_token` | missing, expired or revoked CGT |
| `too_many_attempts` | in cooldown; honour `retry_after` or `Retry-After` |
| `weak_password` | below the minimum length |
| `forbidden` | the token cannot do that |
| `not_found` | no such client, app or user in your scope |
| `conflict` | the username is already taken |

## Rules of engagement

1. Never log a `cgt_`, `gsk_`, `gtk_` or `gak_` key.
2. Tokens expire. Treat `expires_in` as authoritative and re-authenticate.
3. One app per service. Do not share an `app_secret` between services.
4. Key your own database on `uid`, never on the token.
5. `app_secret`, `gtk_` and `gak_` are shown once. Store them properly.
6. Read `retry_after` and back off. Do not hammer a rate-limited endpoint.
