agentic-hosting is open source. If this is useful, star the repo — it helps other agents find it.

★ Star on GitHub

Docs

Registration & API keys

Access is a tenant plus one or more API keys. Keys are keyID.secret. The secret is returned once. The database stores an HMAC-SHA256 of the full key using the server master key (internal/crypto.HashAPIKey). Verification is constant-time (hmac.Equal).

bcrypt is used for passwords (kanban / DB credential hashing helpers), not for API keys.

Bootstrap token

Production will not start without AH_BOOTSTRAP_TOKEN unless you pass --dev --open-registration.

  • Minimum length 32 characters per token
  • Comma-separated list is allowed: AH_BOOTSTRAP_TOKEN=new,old — both work during rotation
  • Compared with HMAC (no length leak) in validateBootstrapToken
  • Sent as header X-Bootstrap-Token (not Authorization)

Validate without registering:

curl -s -X POST "$AH_URL/v1/system/bootstrap-token/validate" \
  -H "Content-Type: application/json" \
  -d "{\"token\":\"$AH_BOOTSTRAP_TOKEN\"}"
# → 200 {"valid":true} or 200 {"valid":false} — never 401 for a bad token

The token is in the JSON body, not X-Bootstrap-Token (that header is used by register). Recover / reactivate / quota patch take X-Admin-Token when AH_ADMIN_TOKEN is set.

Admin token

AH_ADMIN_TOKEN (comma-separated, 32+ chars) authorizes operator-only routes: POST /v1/auth/recover, POST /v1/tenants/{id}/reactivate, PATCH /v1/tenants/{id}/quotas. Send X-Admin-Token (or the same value in X-Bootstrap-Token). Once configured, a valid bootstrap token on those routes returns 403. If unset, bootstrap tokens still work (deprecated; startup warning).

Register

POST /v1/tenants/register
X-Bootstrap-Token: <token>
{"name":"my-agent","email":"agent@example.com"}

Rules from handleTenantRegister:

  • Name 2–128 characters
  • Email must match ^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$, max 256
  • Max 1000 active tenants
  • Duplicate name/email → 422 (registration failed, please check your details and try again)
  • Rate limit: 5 / IP / hour and 20 / hour global (same limiter as recover, reactivate, quota patch, bootstrap validate)

Response:

{"tenant_id":"<hex>","api_key":"<keyID>.<secret>"}

--dev --open-registration skips the bootstrap header. Anyone who can reach the port can create tenants.

Authenticated requests

Authorization: Bearer <keyID>.<secret>

Auth middleware (internal/middleware/auth.go):

  • Looks up the key by ID, checks not revoked / not expired / tenant active
  • Verifies HMAC
  • Caches the lookup 30 seconds (max 5000 entries) so revocation can lag up to 30s
  • Samples last_used_at at most every 5 minutes

Suspended tenants cannot authenticate.

Create, list, revoke

POST   /v1/auth/keys     {"name":"ci","expires_in":2592000}
GET    /v1/auth/keys
DELETE /v1/auth/keys/{keyID}
  • name defaults to unnamed
  • expires_in is seconds, must be > 0, max ~10 years
  • Response field is api_key (not key)
  • List never returns the secret; it returns id, name, prefix, created_at, last_used_at, expires_at
  • Max 20 non-revoked keys per tenant → 409 quota exceeded: maximum API keys reached...

Creating a key invalidates the auth cache for that tenant.

Recover lost keys

When every key is gone:

POST /v1/auth/recover
X-Admin-Token: <admin-token>
{"email":"agent@example.com"}

Email must match the tenant. Issues a new key. Same registration rate limiter.

Reactivate a suspended tenant

DELETE /v1/tenant soft-deletes: status becomes suspended and services/DBs/kanbans/builds are stopped.

POST /v1/tenants/{tenantID}/reactivate
X-Admin-Token: <admin-token>

Returns {"tenant_id","api_key","status"}. Already-active tenants return 409. Rate-limited like registration.

Tenant self-service

MethodPathNotes
GET/v1/tenantid, name, email, status, timestamps
PATCH/v1/tenantname only (2–128 chars). Email is not updatable here.
DELETE/v1/tenantSuspend + stop resources
GET/v1/tenant/usageUsed/max for services, databases, API keys; memory/cpu/disk/rate_limit quotas
GET/v1/tenant/usage/metricsMetering rollups (period=hourly|daily, since/until RFC3339)

Operator quota changes:

PATCH /v1/tenants/{tenantID}/quotas
X-Admin-Token: <admin-token>

Same bootstrap rate limiter.

ahc equivalents

ahc register --name my-agent --email agent@example.com --save
ahc key create --name ci
ahc key list
ahc key revoke <keyID>
ahc recover --email agent@example.com

Config file expected by ahclient.LoadConfig is ~/.ah/config.json with url and key.