raw .md

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

whatvalue
public base URLhttps://auth.gumpworks.com
API basehttps://auth.gumpworks.com/api/v1
management APIhttps://auth.gumpworks.com/api/v1/manage
client auth headerAuthorization: Bearer gtk_...
token typeBearer, prefix cgt_
client token lifetime43200 seconds by default
app token lifetime3600 seconds by default
minimum password length10 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

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_typefieldsresult
password (client)username, passwordclient token, scope client
password (app user)client_id, client_secret, username, passwordapp user token, scope app profile
client_credentialsclient_id, client_secretapp token, no user
authorization_codecode, client_id, client_secret, redirect_uriapp user token
# 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:

{
  "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)

curl -sS "$API/userinfo" -H "Authorization: Bearer $CGT"
{
  "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.

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.

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

POST /accounts

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

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:

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.

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

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

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

Apps

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

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.

# 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

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

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.

{"error": "invalid_credentials", "message": "Username or password is incorrect. Next attempt in 4 seconds.", "retry_after": 4}
errormeaning
invalid_requesta required field is missing or malformed
invalid_clientunknown app, disabled app, or bad app secret
invalid_credentialswrong username or password
invalid_grantaccount disabled, or code expired or used
invalid_tokenmissing, expired or revoked CGT
too_many_attemptsin cooldown; honour retry_after or Retry-After
weak_passwordbelow the minimum length
forbiddenthe token cannot do that
not_foundno such client, app or user in your scope
conflictthe 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.