API HumanLayer v1
Intégrez des décisions humaines qualifiées dans vos agents IA via une API REST. Votre agent automatise par défaut ; quand une demande franchit le seuil Arbitrium, il la soumet à HumanLayer, qui la route vers un Sentinel habilité et renvoie une décision justifiée, horodatée et signée.
URL de base : https://humanlayer.systems/api/v1
Authentification
Chaque requête inclut votre clé d’API dans l’en-tête x-api-key.
x-api-key: hl_live_…
- Créez et révoquez vos clés dans l’espace client (jusqu’à 5 clés actives).
- La clé complète n’est affichée qu’une fois : HumanLayer n’en conserve que l’empreinte SHA-256.
- Chaque clé reçoit un secret de signature des webhooks (
whsec_…), lui aussi affiché à la création.
X-HL-Lang: en pour recevoir les messages en anglais. Le champ code est toujours stable.
Niveaux, SLA et tarifs
Le champ request_type fixe le niveau de décision, donc le SLA et le prix. Votre forfait détermine les niveaux accessibles et le quota quotidien.
| request_type | Niveau | SLA | Prix |
|---|---|---|---|
compliance (alias conformité) | 1 · Conformité | 30 min | 20 € / décision |
judgment (alias jugement) | 2 · Jugement contextuel | 2 h | 100 € / décision |
| signature | 3 · Autorité décisionnelle | 4 h, ou sur mesure dès 30 min (Enterprise) | 250 € / décision |
| Forfait | Niveaux accessibles | Quota |
|---|---|---|
| Conformité | 1 | 100 demandes / jour |
| Jugement expert | 1, 2 | 50 demandes / jour |
| Autorité habilitée | 1, 2, 3 | 20 demandes / jour |
| Enterprise | 1, 2, 3 | Sur contrat (SLA premium, pools dédiés) |
Le délai court dès la soumission. Vous pouvez demander un délai plus long avec sla_minutes, jamais plus court que l’engagement du niveau (7 jours au plus).
Cycle d’une demande
- L’agent soumet la demande :
POST /api/v1/decisions. - HumanLayer la route vers un Sentinel actif du bon domaine (et de la bonne juridiction), dont la double authentification est configurée, en privilégiant le moins chargé.
- Le Sentinel tranche (
approvedourejected, justification obligatoire) ou escalade pour un second avis : la demande passe alors à un autre Sentinel. - La décision est signée (Ed25519) et vous est livrée par webhook ; elle reste consultable via
GET /api/v1/decisions?id=…. - Si le SLA est dépassé, vous recevez
sla.breachedet la demande est réassignée automatiquement (trois escalades au plus). Le contrôle tourne toutes les 5 minutes. - Une fois la demande tranchée, vous pouvez demander un second avis : un autre Sentinel reprend le dossier sans connaître le premier verdict.
Soumettre une demande
Crée une demande de décision et l’assigne à un Sentinel qualifié dès qu’il est disponible.
Corps de la requête (JSON, 64 Ko max.)
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| agent_id | string | requis | Identifiant de votre agent (120 caractères max.) |
| agent_name | string | optionnel | Nom lisible de l’agent |
| request_type | string | requis | compliance | judgment | signature (voir les niveaux) |
| domain | string | requis | legal, finance, compliance, medical, accounting, hr, insurance, quality, security, other |
| jurisdiction | string | optionnel | Juridiction exigée (ex. « Québec », « France ») |
| summary | string | requis | La question posée au Sentinel (5 000 caractères max.) |
| context_json | object | optionnel | Dossier structuré préparé par l’IA (50 Ko max.) |
| priority | string | optionnel | low | normal (défaut) | high | urgent |
| sla_minutes | integer | optionnel | Délai plus long que l’engagement du niveau (10 080 max.) |
| callback_url | string | optionnel | URL de webhook : HTTPS obligatoire, hôte public (les adresses privées et internes sont refusées) |
Réponse (200)
{
"ok": true,
"request_id": "8b1f2c4e-…",
"status": "assigned",
"level": 1,
"sla_minutes": 30,
"expires_at": "2026-03-12T15:02:07.000Z",
"sentinel_ref": "S-4F2A9C"
}
pending ; le routage est retenté toutes les 5 minutes. L’identité du Sentinel n’est jamais exposée : sentinel_ref est un pseudonyme stable.
Suivre une demande
Renvoie l’état de la demande, son SLA et, une fois tranchée, la décision signée. Préférez les webhooks au sondage ; si vous sondez, espacez les appels d’au moins 15 secondes.
Réponse (200) : demande tranchée
{
"request": {
"id": "8b1f2c4e-…",
"status": "decided",
"level": 1,
"priority": "normal",
"sentinel_ref": "S-4F2A9C",
"expires_at": "2026-03-12T15:02:07.000Z",
"escalation_count": 0,
"verdict": "approved",
"reasoning": "Conforme à la politique interne du client.",
"decision": {
"id": "d41c…",
"verdict": "approved",
"signed_at": "2026-03-12T14:48:31.000Z",
"signature": "base64…",
"signed_payload": "{\"decision_id\":…}",
"key_id": "3f9a…"
}
}
}
Historique d’un agent
Les 100 dernières demandes de cet agent créées avec la même clé, du plus récent au plus ancien, au même format que ci-dessus.
Demander un second avis
Confie une demande déjà tranchée à un autre Sentinel, qui n’a jamais traité le dossier et ne voit pas le premier verdict. Le dossier (niveau, domaine, juridiction, question, contexte) est repris tel quel. Le second avis est facturé comme une nouvelle décision du même niveau et compte dans votre quota quotidien.
Corps de la requête
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| second_opinion_of | string | requis | request_id de la demande tranchée, créée avec la même clé |
| priority | string | optionnel | Par défaut, celle de la demande d’origine |
| callback_url | string | optionnel | Par défaut, celle de la demande d’origine |
| sla_minutes | integer | optionnel | Comme pour une demande classique |
Réponse (200)
{
"ok": true,
"request_id": "c7d05e91-…",
"status": "assigned",
"level": 2,
"sla_minutes": 120,
"sentinel_ref": "S-9B31D0",
"second_opinion_of": "8b1f2c4e-…"
}
409 not_decided) et une seule fois (409 second_opinion_exists). La demande d’origine expose ensuite second_opinion_id, le second avis second_opinion_of. Chaque verdict est signé séparément ; s’ils divergent, la suite vous appartient.
Clé de vérification
Clé publique Ed25519 (PEM) et identifiant key_id, pour vérifier les décisions sans faire confiance au transport. Aucune authentification requise.
Webhooks signés
Si vous fournissez callback_url, HumanLayer y envoie un POST JSON à chaque événement :
| Événement | Quand |
|---|---|
| decision.completed | Le Sentinel a approuvé |
| decision.rejected | Le Sentinel a refusé |
| decision.escalated | Second avis demandé : la demande passe à un autre Sentinel |
| sla.breached | Le délai est dépassé ; réassignation automatique |
En-têtes : X-HumanLayer-Event, X-HumanLayer-Delivery et X-HumanLayer-Signature: t=<horodatage>,v1=<hmac>, où v1 est le HMAC-SHA256 hexadécimal de t + "." + corps brut avec le secret whsec_… de votre clé. Chaque événement est livré une fois (délai de 5 s) ; en cas d’échec, l’état reste consultable par l’API (webhook_status).
import hmac, hashlib, time def verify_webhook(headers, raw_body: bytes, secret: str) -> bool: parts = dict(p.split("=", 1) for p in headers["X-HumanLayer-Signature"].split(",")) if abs(time.time() - int(parts["t"])) > 300: return False # trop ancien : rejeu possible signed = parts["t"].encode() + b"." + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts["v1"])
Vérifier une décision
Chaque décision est signée en Ed25519 sur le champ signed_payload (JSON canonique : identifiants, verdict, empreinte SHA-256 de la justification, pseudonyme du Sentinel, horodatage, key_id). Toute modification ultérieure invalide la signature.
import crypto from 'node:crypto'; const key = await (await fetch('https://humanlayer.systems/api/v1/decision-key')).json(); const ok = crypto.verify( null, Buffer.from(decision.signed_payload), key.public_key_pem, Buffer.from(decision.signature, 'base64') ); // Vérifiez aussi que sha256(reasoning) === JSON.parse(signed_payload).reasoning_sha256
Statuts
| status | Signification |
|---|---|
| pending | En attente d’un Sentinel disponible |
| assigned | Assignée à un Sentinel, dans les délais |
| sla_breached | Délai dépassé et aucun autre Sentinel disponible pour l’instant |
| decided | Tranchée : verdict vaut approved ou rejected |
Une escalade (escalated) n’est pas un statut final : la demande repasse à assigned auprès d’un autre Sentinel, et escalation_count augmente.
Erreurs et limites
Les erreurs renvoient { "error": "message", "code": "…" }.
| HTTP | code | Cause |
|---|---|---|
| 400 | missing_fields, invalid_request_type, invalid_domain, invalid_priority, invalid_callback_url, context_too_large, invalid_second_opinion | Requête invalide |
| 401 | missing_api_key, invalid_api_key | Clé absente ou inconnue |
| 403 | api_key_inactive, api_key_expired, plan_restriction | Clé suspendue, révoquée, expirée, ou niveau hors forfait |
| 404 | not_found | Demande inexistante ou créée avec une autre clé |
| 409 | not_decided, second_opinion_exists | Second avis sur une demande pas encore tranchée, ou déjà demandé |
| 413 | payload_too_large | Corps supérieur à 64 Ko |
| 429 | rate_limited, daily_limit_reached | Plus de 60 demandes par minute et par clé, ou quota du forfait atteint. Respectez l’en-tête Retry-After. |
| 500 | server_error | Erreur interne : réessayez avec un délai croissant |
Exemple complet
import time, requests API = "https://humanlayer.systems/api/v1/decisions" HEADERS = {"x-api-key": "hl_live_…"} # 1. Seuil Arbitrium franchi : on soumet le dossier created = requests.post(API, headers=HEADERS, json={ "agent_id": "agent-credit", "request_type": "compliance", "domain": "finance", "summary": "KYC : correspondance partielle sur une liste de sanctions. Valider ?", "context_json": {"customer_id": "c_4521", "match_score": 0.71}, "callback_url": "https://votre-app.example/webhooks/humanlayer", }).json() # 2. Sans webhook : sondage espacé jusqu'à la décision while True: req = requests.get(API, headers=HEADERS, params={"id": created["request_id"]}).json()["request"] if req["status"] == "decided": break time.sleep(15) # 3. L'agent reprend son workflow, décision signée à l'appui if req["verdict"] == "approved": continue_workflow(req["decision"])
curl -X POST https://humanlayer.systems/api/v1/decisions \ -H "x-api-key: hl_live_…" \ -H "content-type: application/json" \ -d '{"agent_id":"agent-legal","request_type":"judgment","domain":"legal","summary":"La clause §4.2.b est-elle acceptable ?"}'