Référence de l'API

L'API /v1 d'Agreely est le backend que les clients TypeScript, PHP et CLI encapsulent. Elle est petite, sans état et de serveur à serveur. Cette page est l'orientation; la référence /v1 interactive présente les schémas complets de requête/réponse, rendus à partir de la spécification OpenAPI.

Authentification

Chaque appel utilise une clé d'API Bearer :

Authorization: Bearer agr_live_...

La clé seule identifie le locataire. Il n'y a ni session ni CSRF. Les clés portent des portées (scopes) :

  • check autorise POST /v1/check.
  • issue autorise les points d'accès de demande de consentement.
  • attest autorise les points d'accès de consentement manuel.
  • relationship autorise la fin d'une relation client (art. 23).
  • La portée check ou issue peut lire GET /v1/catalog.
  • Toute clé valide (n'importe quelle portée) peut appeler GET /v1/whoami.

Une clé absente ou invalide donne 401; une clé valide dépourvue de la portée requise donne 403.

Points d'accès

Méthode Chemin Portée Finalité
GET /v1/whoami n'importe laquelle Les portées que porte la clé présentée (divulgation minimale).
POST /v1/check check Décision de consentement synchrone pour une cellule.
POST /v1/check/batch check Décisions synchrones pour plusieurs cellules en un seul appel (plafond de 500).
GET /v1/catalog check ou issue Le catalogue (catégorie, finalité) actif de l'entreprise.
POST /v1/consent-requests issue Émettre une demande de consentement.
GET /v1/consent-requests issue Lister les demandes, plus récentes d'abord, paginées par curseur.
GET /v1/consent-requests/{id} issue Une demande par son requestId de protocole.
POST /v1/consent-requests/{id}/cancel issue Annuler une demande en attente (idempotent).
POST /v1/manual-consents attest Enregistrer un consentement hors ligne, attesté par l'entreprise.
POST /v1/manual-consents/claim-links attest Émettre un lien de réclamation à usage unique pour le citoyen.
POST /v1/manual-consents/{consentRef}/revoke attest Retrait d'un consentement manuel.
POST /v1/manual-consents/{consentRef}/erase attest Effacer (destruction cryptographique) un consentement manuel.
POST /v1/customers/{customerRef}/relationship/end relationship Mettre fin à une relation client (art. 23, idempotent).

L'identité de la clé (whoami)

GET /v1/whoami confirme, côté serveur, les portées que porte réellement la clé présentée. Toute clé valide y accède (n'importe quelle portée). La réponse est volontairement minimale : uniquement les portées de la clé, rien d'autre, pas d'identifiant d'entreprise, pas de nom de clé, pas de compteur.

{ "scopes": ["check", "issue"] }

Une clé absente ou invalide donne 401. Comme les autres points d'accès, il est soumis aux limites de débit (429).

L'annulation d'une demande de consentement

POST /v1/consent-requests/{id}/cancel annule une demande en attente (la voie « révocation avant action » côté entreprise) : elle passe à l'état terminal revoked_before_action, si bien que le lien profond du citoyen ne mène plus à une approbation. Portée : issue. La demande est identifiée par son requestId de protocole et rattachée à l'entreprise de la clé (jamais à une valeur du corps).

L'appel est idempotent : une demande déjà terminale (approved, refused, expired ou revoked_before_action) n'est pas une erreur; elle renvoie son statut courant avec cancelled: false. Seule la transition en attente vers annulée renvoie cancelled: true.

{ "requestId": "0x...", "status": "revoked_before_action", "cancelled": true }

Un requestId inconnu, étranger à l'entreprise, ou malformé donne 404 (jamais une écriture inter-locataires).

La vérification : un refus est un 200

Une décision de consentement, autorisation ou refus, est toujours un 200 réussi. Le refus n'est pas une erreur.

{ "decision": "deny", "status": "revoked", "consentRef": "0x...", "assurance": "citizen_signed", "checkedAt": "2026-06-26T15:04:05Z" }

status est l'un de active (autorisation), none, revoked, expired ou erased. consentRef est présent pour chaque statut sauf none. Le corps porte aussi assurance, citizen_signed (signé par le citoyen) ou company_attested (consentement manuel), présent pour chaque statut sauf none : c'est le palier du consentement sous-jacent. Envoyez les category et purpose bruts tels que lus par un humain; le serveur les normalise. La vérification ne lit que le statut d'un seul enregistrement d'application indexé et ne touche jamais la chaîne ni l'indexeur, de sorte qu'une révocation refuse dès l'appel suivant. Elle fonctionne en refus par défaut : il n'y a pas d'autorisation sans enregistrement positif. Une panne d'Agreely doit elle aussi être traitée comme un refus; c'est ce que font les SDK.

La vérification par lot

POST /v1/check/batch évalue plusieurs cellules en un seul aller-retour au lieu de N appels à /v1/check. Même portée (check), même moteur de décision, même règle « refus par défaut » et même journal d'accès par décision (Loi 25 G3) : une seule écriture groupée, granulaire par décision. Il est rattaché à l'entreprise de la clé. Les category / purpose sont envoyés bruts; le serveur les normalise.

Deux formes de corps sont acceptées. La forme plate :

{ "items": [ { "customerRef": "cust_8812", "category": "Phone number", "purpose": "Billing" } ] }

Et la forme groupée (développée côté serveur en un produit client x champs) :

{ "checks": [ { "customerRef": "cust_8812", "fields": [ { "category": "Phone number", "purpose": "Billing" }, { "category": "Email address", "purpose": "Newsletter" } ] } ] }

Le lot utilise customerRef, l'appel unique utilise customerId

Détail de compatibilité à connaître : POST /v1/check nomme le champ du sujet customerId, alors que POST /v1/check/batch le nomme customerRef. Les deux désignent votre propre référence client, résolue au sein de l'entreprise de la clé. Les SDK masquent cette différence.

La réponse est un tableau decisions aligné sur les cellules soumises. Chaque décision porte les mêmes champs qu'un /v1/check unique, plus les customerRef, category et purpose renvoyés en écho :

{ "decisions": [
  { "customerRef": "cust_8812", "category": "Phone number", "purpose": "Billing",
    "decision": "allow", "status": "active", "consentRef": "0x...",
    "assurance": "citizen_signed", "checkedAt": "2026-06-26T15:04:05Z" }
] }

Comme pour l'appel unique, consentRef et assurance sont absents pour le statut none, et une cellule sans enregistrement actif renvoie deny / none (refus par défaut). Un lot vide donne 422, et un lot de plus de 500 cellules donne 422 (invalid_request : « Batch exceeds the 500-item cap. Split into smaller batches. ») : découpez en lots plus petits.

Cas d'usage : protéger l'affichage d'une liste de clients

Afficher un tableau de N clients x M champs est N x M usages de renseignements personnels. Sous la Loi 25, chaque champ que vous affichez est un usage encadré par une vérification (limitation de l'usage, art. 12; responsabilité, art. 3.1). Le lot regroupe la grille entière en un seul aller-retour tout en conservant une décision et une entrée de journal d'accès par cellule. Le regroupement est une optimisation de la même responsabilité par décision, jamais un affaiblissement : chaque cellule reste décidée et journalisée individuellement.

Les consentements manuels (attest)

La portée attest couvre les consentements recueillis hors ligne et attestés par l'entreprise. Quatre points d'accès :

  • POST /v1/manual-consents enregistre un consentement attesté par l'entreprise. Le corps porte customerId, documentVersionId, effectiveDate, validUntil, des items (identifiants de catalogue ou paires {category, purpose}) et une evidence dont le pdfSha256 (empreinte SHA-256, 0x + 64 hex) est requis; le pdf en base64 est facultatif, pour la mise sous séquestre. La réponse 201 porte consentId, merkleRoot, un consentRefs (un 0x-hex par cellule), assurance: "company_attested" et anchored (faux au moment de l'enregistrement).
  • POST /v1/manual-consents/claim-links émet un lien de réclamation à usage unique pour le citoyen : corps { customerId } (requis) et reference facultatif; réponse 201 { claimUrl, token, expiresAt }.
  • POST /v1/manual-consents/{consentRef}/revoke porte le retrait du consentement : corps { reason? } facultatif; réponse 200 { consentRef, revoked: true, alreadyRevoked }. Une référence inconnue donne 404.
  • POST /v1/manual-consents/{consentRef}/erase procède à l'effacement (destruction cryptographique) : corps { reason? } facultatif; réponse 200 { consentRef, erased: true, alreadyErased }. Une référence inconnue donne 404.

Un consentement manuel et un consentement signé par le citoyen autorisent de façon identique; seul le palier d'assurance diffère (company_attested contre citizen_signed). Les consentements manuels n'appliquent pas l'idempotence.

La fin d'une relation client (relationship)

POST /v1/customers/{customerRef}/relationship/end atteste, depuis votre propre code (p. ex. un flux de désinscription), qu'une relation client est terminée, au sens de l'article 23 de la Loi 25 (« les fins sont accomplies »). Portée : relationship. C'est l'exposition /v1 de l'action de l'interface entreprise : elle effectue la même écriture de cycle de vie terminale, si bien que la justification requise, l'origine endedBy, la transition active/ending vers ended et les obligations en aval se comportent de façon identique. C'est une surcouche de cycle de vie : elle ne révoque, n'efface ni ne masque aucun consentement par cellule.

Le client est identifié par votre propre customerRef (le même que pour /v1/check), résolu au sein de l'entreprise de la clé. Une référence inconnue ou étrangère donne un 404 propre, sans rien écrire. Un reason absent ou vide donne 422 (jamais une fin silencieuse). L'appel est idempotent : re-finir une relation déjà terminée est un succès; le endedAt et l'origine endedBy d'origine sont préservés. Le point d'accès n'accepte ni ne renvoie jamais un DID de citoyen.

{ "customerRef": "cust_8812", "status": "ended", "endedAt": "2026-06-26T15:04:05Z", "endedBy": "company" }

L'enveloppe d'erreur

Chaque erreur (jamais un refus) partage une seule forme :

{ "error": { "code": "invalid_request", "message": "…", "field": "category" } }
HTTP code Quand
401 unauthorized Clé absente, invalide ou révoquée.
403 forbidden Clé valide, portée manquante.
422 invalid_request Entrée malformée (field nomme le fautif). Le seul 400 est le filtre status inconnu sur la liste des demandes.
404 not_found Ressource introuvable.
429 rate_limited Fenêtre par entreprise dépassée (voir Retry-After).

Le code unavailable est réservé dans l'énumération, mais aucun point d'accès ne l'émet pour l'instant.

Limites de débit

Les limites sont par entreprise, en fenêtre fixe (120 requêtes par minute par défaut). Chaque point d'accès /v1 est limité. Un 429 porte le code rate_limited et un en-tête Retry-After (secondes, entier, jusqu'à la réinitialisation de la fenêtre). Il n'y a aucun autre en-tête de limite de débit. Les SDK l'exposent comme une erreur typée avec retryAfter.

Idempotence

Seul POST /v1/consent-requests honore un en-tête Idempotency-Key (255 caractères au plus). Une nouvelle tentative avec la même clé pour la même entreprise rejoue le 201 d'origine octet pour octet et ne crée rien de neuf, de sorte qu'il n'y a ni double émission ni double courriel. Les SDK attachent automatiquement une clé unique par appel; passez la vôtre pour rendre une nouvelle tentative sûre. Les points d'accès de consentement manuel n'appliquent pas l'idempotence.

Idempotency-Key: order-4471

Ensuite