← Retour à humanlayer.systems
FR | EN

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.

En-tête
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.
💡 Langue des messages d’erreur Ajoutez 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_typeNiveauSLAPrix
compliance (alias conformité)1 · Conformité30 min20 € / décision
judgment (alias jugement)2 · Jugement contextuel2 h100 € / décision
signature3 · Autorité décisionnelle4 h, ou sur mesure dès 30 min (Enterprise)250 € / décision
ForfaitNiveaux accessiblesQuota
Conformité1100 demandes / jour
Jugement expert1, 250 demandes / jour
Autorité habilitée1, 2, 320 demandes / jour
Enterprise1, 2, 3Sur 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

🤖
Agent IA
Soumet le dossier
→
⚡
Routage
Sentinel qualifié
→
👤
Sentinel
Tranche et justifie
→
✓
Décision signée
Webhook + API
  1. L’agent soumet la demande : POST /api/v1/decisions.
  2. 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é.
  3. Le Sentinel tranche (approved ou rejected, justification obligatoire) ou escalade pour un second avis : la demande passe alors à un autre Sentinel.
  4. La décision est signée (Ed25519) et vous est livrée par webhook ; elle reste consultable via GET /api/v1/decisions?id=….
  5. Si le SLA est dépassé, vous recevez sla.breached et la demande est réassignée automatiquement (trois escalades au plus). Le contrôle tourne toutes les 5 minutes.
  6. 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

POST /api/v1/decisions

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ètreTypeRequisDescription
agent_idstringrequisIdentifiant de votre agent (120 caractères max.)
agent_namestringoptionnelNom lisible de l’agent
request_typestringrequiscompliance | judgment | signature (voir les niveaux)
domainstringrequislegal, finance, compliance, medical, accounting, hr, insurance, quality, security, other
jurisdictionstringoptionnelJuridiction exigée (ex. « Québec », « France »)
summarystringrequisLa question posée au Sentinel (5 000 caractères max.)
context_jsonobjectoptionnelDossier structuré préparé par l’IA (50 Ko max.)
prioritystringoptionnellow | normal (défaut) | high | urgent
sla_minutesintegeroptionnelDélai plus long que l’engagement du niveau (10 080 max.)
callback_urlstringoptionnelURL de webhook : HTTPS obligatoire, hôte public (les adresses privées et internes sont refusées)

Réponse (200)

json
{
  "ok": true,
  "request_id": "8b1f2c4e-…",
  "status": "assigned",
  "level": 1,
  "sla_minutes": 30,
  "expires_at": "2026-03-12T15:02:07.000Z",
  "sentinel_ref": "S-4F2A9C"
}
⚠️ Statut « pending » Si aucun Sentinel qualifié n’est disponible, la demande reste 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

GET /api/v1/decisions?id={request_id}

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

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

GET /api/v1/decisions?agent_id={agent_id}

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

POST /api/v1/decisions

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ètreTypeRequisDescription
second_opinion_ofstringrequisrequest_id de la demande tranchée, créée avec la même clé
prioritystringoptionnelPar défaut, celle de la demande d’origine
callback_urlstringoptionnelPar défaut, celle de la demande d’origine
sla_minutesintegeroptionnelComme pour une demande classique

Réponse (200)

json
{
  "ok": true,
  "request_id": "c7d05e91-…",
  "status": "assigned",
  "level": 2,
  "sla_minutes": 120,
  "sentinel_ref": "S-9B31D0",
  "second_opinion_of": "8b1f2c4e-…"
}
Un seul second avis par demande Il n’est possible que sur une demande tranchée (sinon 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

GET /api/v1/decision-key

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énementQuand
decision.completedLe Sentinel a approuvé
decision.rejectedLe Sentinel a refusé
decision.escalatedSecond avis demandé : la demande passe à un autre Sentinel
sla.breachedLe 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).

python
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.

javascript (Node.js)
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

statusSignification
pendingEn attente d’un Sentinel disponible
assignedAssignée à un Sentinel, dans les délais
sla_breachedDélai dépassé et aucun autre Sentinel disponible pour l’instant
decidedTranché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": "…" }.

HTTPcodeCause
400missing_fields, invalid_request_type, invalid_domain, invalid_priority, invalid_callback_url, context_too_large, invalid_second_opinionRequête invalide
401missing_api_key, invalid_api_keyClé absente ou inconnue
403api_key_inactive, api_key_expired, plan_restrictionClé suspendue, révoquée, expirée, ou niveau hors forfait
404not_foundDemande inexistante ou créée avec une autre clé
409not_decided, second_opinion_existsSecond avis sur une demande pas encore tranchée, ou déjà demandé
413payload_too_largeCorps supérieur à 64 Ko
429rate_limited, daily_limit_reachedPlus de 60 demandes par minute et par clé, ou quota du forfait atteint. Respectez l’en-tête Retry-After.
500server_errorErreur interne : réessayez avec un délai croissant

Exemple complet

python
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
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 ?"}'