FR EN

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 les deux actions de cycle de vie de la relation client (fin, art. 23; annulation d'une fin erronée, art. 11 et 28).
  • 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).
POST /v1/customers/{customerRef}/relationship/revert relationship Annuler une fin de relation erronée (correction, art. 11 et 28).

Lister les demandes de consentement

GET /v1/consent-requests liste uniquement les demandes de l'entreprise de la clé, plus récentes d'abord. Quatre paramètres de requête, tous facultatifs :

Paramètre Rôle
status Filtre sur un statut de demande. Un statut inconnu est le seul 400 de l'API.
customerId Votre propre référence client, celle fournie à l'émission. Restreint la liste aux demandes émises pour ce sujet, à l'intérieur de vos propres demandes. Une référence qui n'est pas la vôtre ne correspond à rien.
cursor Le requestId après lequel paginer (pagination par clé, jamais par décalage).
limit Taille de page, bornée à l'intervalle [1, 100], 50 par défaut. Une valeur hors bornes est ramenée dans l'intervalle, ce n'est pas une erreur; une valeur non numérique retombe sur le défaut.

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" }

Branchez toujours sur decision (allow ou deny). Le status dit pourquoi, et le vocabulaire compte huit valeurs :

status decision Ce qu'il signifie
active allow Un enregistrement d'application actif couvre la cellule.
necessity allow Aucun enregistrement par personne, mais la cellule active du catalogue déclare une base légale autre que le consentement (contract, necessary_for_service, security_fraud, legal_obligation, professional_contact). La réponse porte alors basis et ni consentRef ni assurance : il n'y a aucune preuve signée. L'appel ne crée rien.
none deny Aucun enregistrement sur une cellule à base de consentement.
revoked deny Le consentement a été retiré.
expired deny La durée de vie du consentement est écoulée (art. 14 al. 3).
erased deny La cellule a été effacée (destruction de clé).
relationship_ended deny L'entreprise a attesté que la relation est terminée (art. 23). C'est un arrêt prospectif au niveau de la relation; le consentement par cellule reste, en toute vérité, actif (il n'a jamais été retiré).
sensitive_requires_consent deny Aucun enregistrement, et la cellule est déclarée sensible par l'entreprise : elle exige un consentement exprès et ne s'ouvre jamais sur une simple base de nécessité (art. 12 al. 1 in fine et art. 13).

consentRef et assurance sont présents pour chaque statut adossé à un enregistrement, et absents pour none et pour necessity. assurance vaut citizen_signed (signé par le citoyen) ou company_attested (consentement manuel) : c'est le palier du consentement sous-jacent.

Agreely enregistre la base déclarée, il ne la valide pas

Une autorisation necessity repose sur la base légale que votre entreprise a déclarée sur la cellule du catalogue. Agreely consigne cette déclaration et l'applique de façon cohérente; il ne certifie pas qu'elle est juridiquement fondée. À noter : professional_contact (art. 1 al. 5) est une exclusion du champ d'application, non un motif de nécessité au sens de l'art. 12 ou de l'art. 9; il figure ici parce qu'il produit lui aussi une autorisation sans consentement.

Envoyez les category et purpose bruts tels que lus par un humain; le serveur les résout. La résolution est insensible à la casse et aux espaces; un libellé français se résout avec ou sans accents, et un libellé anglais se résout avec ou sans accents uniquement si l'entreprise a divulgué un libellé anglais pour cette cellule. Un libellé ambigu ou non divulgué ne correspond à rien et refuse (none). Ne normalisez pas côté client.

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 ni base déclarée. 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 les statuts none et necessity, le vocabulaire de statuts est identique (y compris necessity avec son basis, relationship_ended et sensitive_requires_consent), et une cellule sans enregistrement ni base déclarée 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" }

Annuler une fin erronée

POST /v1/customers/{customerRef}/relationship/revert annule une fin prématurée ou erronée attestée par l'entreprise. C'est une correction d'une inscription inexacte (art. 11 et 28), jamais une résurrection d'un consentement mort. Portée : relationship. C'est le jumeau /v1 de l'annulation offerte dans l'interface entreprise, derrière exactement la même barrière d'admissibilité.

L'annulation n'est permise que si toutes ces conditions tiennent : la fin était attestée par l'entreprise, rien n'a été détruit (aucun consentement retiré, aucun effacement exécuté), et l'appel arrive dans la fenêtre de correction de 30 jours. Une relation non admissible (une fin déclenchée par le citoyen, hors fenêtre, ou après une destruction) donne un 404 propre sans rien écrire : une personne qui revient est une nouvelle relation, qui exige une nouvelle demande de consentement (art. 14). Un reason absent ou vide donne 422 : la correction doit porter sa justification.

En cas de succès, la relation redevient active et les cellules encore actives, qui n'avaient jamais été retirées, autorisent de nouveau d'elles-mêmes : aucun nouveau consentement à demander, rien n'est périmé.

{ "customerRef": "cust_8812", "status": "active", "reverted": true }

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.
402 billing_inactive L'espace de travail n'a pas d'abonnement actif (essai terminé sans paiement, ou facturation en défaut). Tous les points d'accès /v1 renvoient ce code tant que la situation n'est pas régularisée dans l'application.
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