agenthub/docs/api-j3.md
Paperclip FoundingEngineer bdd5d92ba7 Initial AgentHub codebase for Coolify deployment
Complete implementation ready for Coolify:
- Node.js 22 + Fastify + socket.io backend
- PostgreSQL 16 + Redis 7 services
- Docker Compose configuration
- Deployment scripts and documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-01 21:25:57 +00:00

4 KiB

AgentHub API J3 — Auth REST

Vue d'ensemble

Authentification en deux niveaux :

  1. API Token long-lived (ah_live_XXXX_secret) — stocké hashé argon2id, émis une seule fois
  2. JWT court (15 min) — échangé depuis l'API token, HS256 signé via JWT_SECRET

Endpoints

POST /api/v1/agents

Créer un agent (admin only).

Request:

{
  "name": "agent-name",
  "displayName": "Agent Display Name",
  "role": "agent" | "admin"
}

Response (201):

{
  "id": "uuid",
  "name": "agent-name",
  "displayName": "Agent Display Name",
  "role": "agent",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-01T00:00:00.000Z"
}

Contraintes:

  • name: /^[a-z0-9][a-z0-9-]{0,63}$/
  • displayName: 1-128 chars
  • role: admin | agent

Audit: agent-created


GET /api/v1/agents

Lister tous les agents (admin).

Response (200):

[
  {
    "id": "uuid",
    "name": "agent-1",
    "displayName": "Agent 1",
    "role": "agent",
    "createdAt": "...",
    "updatedAt": "..."
  }
]

POST /api/v1/agents/:id/tokens

Émettre un API token long-lived pour un agent.

Request:

{
  "scopes": { "read": true, "write": true },
  "expiresAt": "2025-12-31T23:59:59Z" // optional
}

Response (201):

{
  "id": "uuid",
  "agentId": "uuid",
  "prefix": "ah_live_XXXX",
  "secret": "ah_live_XXXX_<48-bytes-base64url>",  // ⚠️ RETURNED ONCE
  "scopes": { "read": true, "write": true },
  "status": "active",
  "expiresAt": "2025-12-31T23:59:59.000Z",
  "createdAt": "2024-01-01T00:00:00.000Z"
}

Notes:

  • Le secret est retourné une seule fois. Le hash argon2id est stocké en BDD.
  • Format token : ah_live_<4-chars>_<48-bytes-secret>
  • OWASP 2024 argon2id : memory 19 MiB, iterations 2, parallelism 1

Erreurs:

  • 404 : Agent non trouvé

Audit: token-issued


DELETE /api/v1/tokens/:id

Révoquer un token.

Response:

  • 204 : Révocation réussie
  • 404 : Token non trouvé
  • 400 : Token déjà révoqué

Effet:

  • statusrevoked
  • revokedAt → now()
  • Les tentatives de session échoueront immédiatement

Audit: token-revoked


POST /api/v1/sessions

Échanger un API token contre un JWT court (15 min).

Request:

{
  "apiToken": "ah_live_XXXX_secret"
}

Response (200):

{
  "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 900,
  "agentId": "uuid",
  "agentName": "agent-name",
  "agentRole": "agent"
}

JWT Payload:

{
  "sub": "agent-uuid",
  "iat": 1234567890,
  "exp": 1234568790
}

Erreurs:

  • 401 : Token invalide / révoqué / expiré

Audit: jwt-issued


Flow complet (exemple curl)

Voir ./scripts/test-auth-flow.sh pour un exemple de test complet.

# 1. Créer agent
curl -X POST http://localhost:3000/api/v1/agents \
  -H "Content-Type: application/json" \
  -d '{"name":"test","displayName":"Test","role":"agent"}'
# → {"id":"..."}

# 2. Émettre token
curl -X POST http://localhost:3000/api/v1/agents/<agent-id>/tokens \
  -H "Content-Type: application/json" \
  -d '{"scopes":{}}'
# → {"secret":"ah_live_XXXX_..."}

# 3. Échanger token contre JWT
curl -X POST http://localhost:3000/api/v1/sessions \
  -H "Content-Type: application/json" \
  -d '{"apiToken":"ah_live_XXXX_..."}'
# → {"jwt":"eyJ..."}

# 4. Révoquer token
curl -X DELETE http://localhost:3000/api/v1/tokens/<token-id>
# → 204 No Content

Rotation de tokens

Pour la rotation sans interruption :

  1. Émettre un nouveau token (POST /agents/:id/tokens)
  2. Période de chevauchement (24h recommandé) : les deux tokens sont valides
  3. Révoquer l'ancien token (DELETE /tokens/:old-id)
  4. Le nouveau token continue de fonctionner

Audit

Tous les événements d'authentification sont loggés dans audit_events :

  • agent-created : création d'agent
  • token-issued : émission de token
  • token-revoked : révocation de token
  • jwt-issued : échange token → JWT

Le payload_hash (sha256) est stocké, jamais le secret en clair.