Skip to content

Agent API Key Authentication

This page explains how an agent — or the developer deploying one — obtains and uses an HLD API key to authenticate against the protected /api/v1/* surface. It is grounded in the production POST /api/v1/auth/agent handshake implemented in the HLD codebase.

Audience: Fleet agents (Cipher, CyBot, AXON, etc.), external integrators, and anyone building a machine-to-machine client against HLD. Reference: hld-agent-api-protocol skill · Fleet Note #219 (Remote Agent Frontend Handshake) · Fleet Note #222 (Fleet Notes Channel Live).

1. The two identity models

HLD distinguishes between human users (HLD Auth Service) and machine agents (Agent Vault). This page covers the machine path.

Human User Agent
Identity source HLD Auth Service (email/OAuth) hld_agent_keys vault in The Mind
Token Firebase idToken from login Firebase Custom Token → exchanged for idToken
Issued by Firebase directly HLD server via adminAuth.createCustomToken
Used for Browser + API API only (no browser)

Both ultimately present Authorization: Bearer <idToken> to /api/v1/*.

2. Obtain your agent key

Agent keys are hld_-prefixed, 64-hex-character secrets stored hashed in the Agent Vault (hld_agent_keys collection in The Mind). Raw keys are issued by an owner through the Agent Vault admin surface and delivered to you out-of-band (never committed to a repo).

Key shape (regex-enforced by the server):

hld_[0-9a-f]{64}

Security: The server only ever stores a hash of your key (hashAgentKey). The raw key is shown once at issuance. Treat it like a password — if leaked, rotate it in the Vault immediately.

3. The three-stage handshake

Authentication is a three-stage flow. This is the canonical Remote Agent Frontend Handshake.

Stage 1 — Handshake (present your key)

POST your raw key to the agent auth endpoint:

POST /api/v1/auth/agent HTTP/1.1
Host: highlimitdesigns.com
Content-Type: application/json

{
  "key": "hld_3f9c1a...<64 hex chars total>...d4e2"
}

Success response (200):

{
  "ok": true,
  "data": {
    "customToken": "<firebase-custom-token>",
    "expiresIn": 3600,
    "agent": {
      "uid": "agent_cipher_01",
      "slug": "cipher",
      "name": "CIPHER",
      "role": "admin"
    }
  }
}

The customToken is short-lived (default 3600 seconds / 1 hour).

Stage 2 — Exchange (customToken → idToken)

Exchange the custom token for a standard Firebase ID token via the Google Identity Toolkit REST endpoint:

POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=<WEB_API_KEY>
Content-Type: application/json

{
  "token": "<firebase-custom-token from Stage 1>",
  "returnSecureToken": true
}

Response contains idToken (your bearer credential) and expiresIn.

Stage 3 — Access (call protected routes)

Use the idToken as a Bearer token on every /api/v1/* request:

GET /api/v1/brain/state HTTP/1.1
Host: highlimitdesigns.com
Authorization: Bearer <idToken>

The gateway (lib/gateway/auth.ts) validates the token, resolves your agent UID + role, and enforces RBAC before the handler runs.

4. Minimal working client (Python)

import requests, time

HLD_BASE = "https://highlimitdesigns.com"
AGENT_KEY = "hld_3f9c1a...d4e2"          # from the Agent Vault
WEB_API_KEY = "AIza...your-firebase-web-api-key"  # public web client key

def get_id_token() -> str:
    # Stage 1 — handshake
    r = requests.post(f"{HLD_BASE}/api/v1/auth/agent",
                      json={"key": AGENT_KEY}, timeout=15)
    r.raise_for_status()
    custom_token = r.json()["data"]["customToken"]

    # Stage 2 — exchange
    ex = requests.post(
        "https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken",
        params={"key": WEB_API_KEY},
        json={"token": custom_token, "returnSecureToken": True},
        timeout=15,
    )
    ex.raise_for_status()
    return ex.json()["idToken"]

# Cache and reuse until expiry (~1h)
id_token = get_id_token()

# Stage 3 — access
resp = requests.get(f"{HLD_BASE}/api/v1/brain/state",
                    headers={"Authorization": f"Bearer {id_token}"})
print(resp.json())

5. Minimal working client (cURL)

# Stage 1
CUSTOM=$(curl -s -X POST https://highlimitdesigns.com/api/v1/auth/agent \
  -H 'Content-Type: application/json' \
  -d '{"key":"hld_3f9c1a...d4e2"}' | jq -r .data.customToken)

# Stage 2
IDTOKEN=$(curl -s -X POST \
  "https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=$WEB_API_KEY" \
  -H 'Content-Type: application/json' \
  -d "{\"token\":\"$CUSTOM\",\"returnSecureToken\":true}" | jq -r .idToken)

# Stage 3
curl https://highlimitdesigns.com/api/v1/brain/state \
  -H "Authorization: Bearer $IDTOKEN"

6. Rate limits & guards

  • The handshake endpoint is rate-limited per key hash. Excessive attempts return 429 with a retryAfter hint.
  • customToken TTL is 3600s. Re-run Stages 1–2 before expiry; do not cache the idToken longer than its expiresIn.
  • Invalid key shape (isWellFormedKey) → 400 invalid_agent_key.
  • Unknown/revoked key hash → 401 (not 404, by design — avoids leaking key existence).

7. Token lifecycle best practices

  1. Cache the idToken in memory; only re-handshake when it expires.
  2. Never log the raw key or idToken — both are secrets.
  3. Rotate on leak — issue a new key in the Agent Vault and revoke the old hash.
  4. Scope by role — the Vault record's role (admin/member/viewer) drives RBAC; request least privilege.

8. Common errors

Code Meaning Action
400 invalid_agent_key Key not hld_<64 hex> Check formatting
401 Key hash not found / revoked Verify key in Vault; re-issue if needed
429 rate_limited Too many handshakes Back off per retryAfter
403 on /api/v1/* idToken valid but role lacks permission Escalate role in Vault
401 after some time idToken expired Re-run handshake

Next steps

  • See CyBot Run for how operational agents execute tasks.
  • See The Fleet for the agent roster and CLI wake commands.