Référence de l'API
L'API /v1 d'Agreely est le backend que les clients TypeScript,
PHP et CLI encapsulent. Elle est sans état et de serveur à
serveur. Cette page décrit chaque point d'accès : sa portée, son corps, sa réponse
et ses refus. La référence /v1 interactive présente les mêmes
schémas, 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. Chaque clé porte une ou plusieurs portées (scopes), choisies à sa création dans l'application :
check(cochée par défaut) : la vérification (/v1/check,/v1/check/batch), la lecture du catalogue et des documents, un énoncé de conservation.issue(cochée par défaut) : les demandes de consentement, la lecture du catalogue et des documents.attest(cochée par défaut) : les consentements sur papier, les feuilles de signature, les liens de réclamation, le retour de la feuille signée d'un consentement verbal, le retrait et l'effacement d'un consentement consigné par l'entreprise.attest_verbal(jamais cochée par défaut) : les consentements donnés par téléphone et leur historique.relationship(cochée par défaut) : la fin d'une relation client et l'annulation d'une fin erronée.registry(cochée par défaut) : l'identité d'un client au registre, sa posture de conservation, ses déclarations de disposition et ses retenues de conservation.retention(cochée par défaut) : les règles de conservation, les cellules du catalogue, les purges et les passes déclarées.inventory(cochée par défaut) : la déclaration des ensembles de dossiers d'un système hôte.withdraw(jamais cochée par défaut) : le retrait consigné au nom de la personne, sur tout consentement qu'elle a donné.holds(jamais cochée par défaut) : le flux des retenues de conservation en place.
Les trois portées jamais cochées par défaut doivent chacune être un choix délibéré
de la personne qui crée la clé. Toute clé valide, quelle que soit sa portée, peut
appeler GET /v1/whoami.
Une clé absente, invalide ou révoquée donne 401. Une clé valide dépourvue de la
portée requise donne 403. Un espace de travail sans abonnement actif donne 402
sur toutes les routes, sauf la route de retrait (voir
le retrait consigné).
Points d'accès
Identité et vérification
GET /v1/whoami(toute clé) : les portées de la clé et l'organisation qu'elle désigne.POST /v1/check(portéecheck) : décision de consentement pour une cellule.POST /v1/check/batch(portéecheck) : décisions pour plusieurs cellules en un appel (plafond de 500).
Catalogue et documents
GET /v1/catalog(portéecheckouissue) : le catalogue actif, entier ou restreint à un document.GET /v1/catalog/cells(portéeretention) : toutes les cellules et la règle de conservation de chacune.GET /v1/consent-documents(portéecheckouissue) : les documents de consentement publiés.GET /v1/consent-documents/{code}(portéecheckouissue) : un document publié, avec sa divulgation complète.GET /v1/consent-documents/versions/{documentVersionId}/pdf(portéescheck,issue,attestouattest_verbal) : le document d'information d'une version, en PDF.
Demandes de consentement
POST /v1/consent-requests(portéeissue) : émettre une demande de consentement.GET /v1/consent-requests(portéeissue) : lister les demandes, paginées par curseur.GET /v1/consent-requests/{id}(portéeissue) : une demande par sonrequestId.POST /v1/consent-requests/{id}/cancel(portéeissue) : annuler une demande en attente.
Consentements sur papier et par téléphone
POST /v1/manual-consents(portéeattest) : consigner un consentement signé sur papier.POST /v1/manual-consents/claim-links(portéeattest) : émettre un lien de réclamation à usage unique.POST /v1/manual-consents/{consentRef}/revoke(portéeattestouattest_verbal) : retirer un consentement consigné par l'entreprise.POST /v1/manual-consents/{consentRef}/erase(portéeattest) : effacer (destruction cryptographique) un consentement consigné par l'entreprise.POST /v1/verbal-consents(portéeattest_verbal) : consigner un consentement donné par téléphone.GET /v1/verbal-consents/{consentId}(portéeattest_verbalouattest) : l'historique d'un consentement verbal.POST /v1/verbal-consents/{consentId}/paper(portéeattest) : consigner la feuille signée qui confirme un consentement verbal.POST /v1/customers/{customerRef}/consent-sheets(portéeattest) : produire la feuille de signature d'une version, avec son lien de réclamation.
Clients, retrait et relation
PUT /v1/customers/{customerRef}(portéeregistry) : consigner ou modifier l'identité d'un client au registre.GET /v1/customers/{customerRef}(portéeregistry) : ce que le registre détient pour un client, en métadonnées.POST /v1/customers/{customerRef}/consents/{consentRef}/withdrawal(portéewithdraw) : consigner le retrait demandé par la personne.POST /v1/customers/{customerRef}/relationship/end(portéerelationship) : mettre fin à une relation client.POST /v1/customers/{customerRef}/relationship/revert(portéerelationship) : annuler une fin de relation erronée.
Conservation
GET /v1/customers/{customerRef}/retention(portéeregistry) : la posture de conservation d'un client, avec ses retenues.POST /v1/customers/{customerRef}/retention/dispositions(portéeregistry) : déclarer ce que le système hôte a fait des renseignements.POST /v1/customers/{customerRef}/retention/holds(portéeregistry) : placer une retenue de conservation.POST /v1/customers/{customerRef}/retention/holds/{holdId}/release(portéeregistry) : lever une retenue de conservation.GET /v1/retention/holds(portéeholds) : le flux des retenues en place.GET /v1/retention/rules(portéeretention) : les règles de conservation décidées par l'organisation.GET /v1/retention/rules/{ruleKey}(portéeretention) : une règle et les derniers rapports de l'hôte.POST /v1/retention/rules/{ruleKey}/purges(portéeretention) : déclarer une purge.POST /v1/retention/rules/{ruleKey}/sweeps(portéeretention) : déclarer une passe qui n'a rien trouvé à purger.
Inventaire
PUT /v1/inventory/categories(portéeinventory) : déclarer les ensembles de dossiers d'un système hôte.GET /v1/inventory/categories(portéeinventoryouretention) : les ensembles déclarés et ce que votre organisation a décidé pour chacun.GET /v1/inventory/statements/{statementKey}(portéesinventory,retentionoucheck) : un énoncé de conservation figé.
Le client est toujours désigné par votre propre référence : customerId dans le
corps de /v1/check, des consentements et des liens de réclamation, customerRef
dans le chemin des routes /v1/customers/... et dans le lot. Les deux désignent la
même valeur, résolue au sein de l'entreprise de la clé. Une référence peut contenir
une barre oblique (MAGASIN/0042) : dans un chemin, encodez-la en pourcentage
(/v1/customers/MAGASIN%2F0042). Une référence . ou .. est refusée par toute
route qui en crée une (422, raison invalid_customer_id) : aucun chemin ne peut la
porter. Aucune route n'accepte ni ne renvoie un DID de citoyen.
Conventions communes
Le corps de la requête
Envoyez chaque champ dans un corps JSON (Content-Type: application/json). Les
routes qui écrivent (demandes de consentement, consentements sur papier et par
téléphone, liens de réclamation, feuilles de signature, retraits et toutes les
routes /v1/customers/..., de conservation et d'inventaire) ne lisent que le
corps : un champ placé dans la chaîne de requête y est ignoré, pour qu'il
n'aboutisse jamais dans les journaux d'un serveur mandataire. Seuls les filtres
documentés des routes GET (status, cursor, changedSince, pageToken,
locale, documentCode, hostSystem) se passent dans la chaîne de requête. Un
corps qui n'est pas un objet JSON en UTF-8 valide donne 400 avec la raison
invalid_json.
L'enveloppe d'erreur
Chaque erreur (jamais un refus de consentement, qui est un 200) partage une seule
forme :
{
"error": {
"code": "conflict",
"message": "…",
"reason": "predates_withdrawal",
"field": "effectiveDate"
}
}
codeest la classe de l'erreur, liée au statut HTTP.reason, quand elle est présente, est la raison machine stable du refus. Chaque refus des routes de consentement sur papier, de consentement verbal, de liens de réclamation, de feuilles de signature et de retrait en porte une.field, quand il est présent, nomme le champ fautif, dans le vocabulaire de la route. Il accompagne tout refus qui porte sur un seul champ, en422comme en409: par exempleeffectiveDatesurpredates_withdrawal,validUntilsurrenewal_ends_before_current,evidence.pdfSha256surinvalid_pdf_hash, etitemssur un consentement sur papier là où un consentement par téléphone, ou sa feuille signée, nommeanswers. Un refus qui porte sur l'état de l'enregistrement n'en porte pas.messageest une phrase en anglais destinée à un humain. Elle peut être reformulée à tout moment.
Comparez reason, jamais message
Branchez votre code sur code et reason. Le texte de message n'est pas un contrat : un test sur ce texte cassera le jour où il sera reformulé. Une raison nouvelle peut s'ajouter; traitez une raison inconnue d'après son code. Les SDK exposent reason et field sur leur type d'erreur.
Les routes de conservation, de retenue, de registre et d'inventaire nomment leur
refus directement dans code (par exemple already_released, identity_held,
new_set_limit). Quatre raisons sont partagées par les routes qui en portent :
missing_field et invalid_field (422, avec field), invalid_idempotency_key
(422) et invalid_json (400).
400invalid_request: un corps illisible (invalid_json), ou un filtrestatusinconnu sur la liste des demandes.400bad_request: un chemin ou un paramètre de chaîne de requête qui n'est pas en UTF-8 valide, refusé avant qu'aucune route ne s'exécute.401unauthorized: clé absente, invalide ou révoquée.402billing_inactive: l'espace de travail n'a pas d'abonnement actif. Toutes les routes, sauf le retrait.403forbidden: clé valide, portée manquante.404not_found: ressource introuvable. Une référence inconnue, malformée ou appartenant à une autre organisation reçoit la même réponse, octet pour octet.409conflictet codes propres : l'état de l'enregistrement interdit l'acte;reasonoucodedit lequel.413body_too_large: le corps dépasse la taille admise par la route.422invalid_requestet codes propres : entrée invalide;fieldnomme le champ.429rate_limitedet plafonds : fenêtre de débit dépassée (avecRetry-After), ou plafond quotidien atteint (sansRetry-After).
Une erreur interne répond 500 avec le code internal_error. Le code
unavailable est réservé, mais aucune route 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 route /v1 est limitée. Un 429 rate_limited porte un en-tête
Retry-After (secondes, entier). Quelques actions ont en plus un plafond
quotidien par entreprise, sur 24 heures glissantes, qui répond 429 sans
Retry-After : ce n'est pas une file d'attente à patienter, c'est un garde-fou.
- Consentements verbaux consignés : 200 (
verbal_daily_cap, raisondaily_cap). - Retraits consignés par
/withdrawal: 50 (withdrawal_daily_cap, raisondaily_cap). - Retenues placées par l'API : 1 000 (
hold_budget_exhausted). - Levées de retenues que l'API n'a pas placées : 20 (
hold_release_cap_reached).
L'exploitant d'Agreely peut ajuster ces plafonds pour un client dont le volume le
justifie.
Une seconde passe sous la même règle, depuis le même système hôte, en moins de
15 minutes donne 429 sweep_too_frequent, avec Retry-After.
Idempotence
Un en-tête Idempotency-Key (1 à 255 caractères ASCII imprimables, sinon 422
invalid_idempotency_key) rend une nouvelle tentative sûre :
Idempotency-Key: order-4471
POST /v1/consent-requests: une nouvelle tentative avec la même clé rejoue le201d'origine octet pour octet : ni double émission, ni double courriel.POST /v1/manual-consents,POST /v1/verbal-consents,POST /v1/verbal-consents/{consentId}/paper,POST .../withdrawal,POST .../retention/holds,POST .../holds/{holdId}/release: la clé est liée à la route et au corps. Même clé et même corps : la première réponse est rejouée et rien n'est écrit. Même clé avec un autre corps : c'est une nouvelle requête. Les requêtes concurrentes portant la même clé sont traitées une à la fois.POST .../consent-sheets: la clé est un verrou, pas un rejeu : la feuille et son lien ne sont jamais conservés, donc une nouvelle tentative répond409already_mintedet ne produit rien.POST /v1/retention/rules/{ruleKey}/purgeset/sweeps: la clé est obligatoire (422idempotency_key_required) et liée à la clé d'API qui déclare. Même clé et même déclaration :200avec la déclaration conservée. Même clé et autre déclaration :422idempotency_key_reused.
Les SDK attachent une clé unique par appel; passez la vôtre pour qu'une nouvelle tentative après une coupure réseau soit reconnue.
Clé révoquée et mise en cache
Une clé révoquée répond 401 dès l'appel suivant. Pour que votre système le
remarque vite sans multiplier les appels :
- Appelez
GET /v1/whoamiune fois par minute, depuis un seul planificateur, pas depuis chaque processus. C'est le signal le moins coûteux qu'une clé a été révoquée ou que l'abonnement est en défaut. - Sur un
401ou un402, videz tout cache tiré d'Agreely et refusez par défaut jusqu'au rétablissement. Seule exception : continuez de consigner les retraits, que la route de retrait accepte même pendant un défaut de paiement. - Ne mettez jamais en cache une réponse de
/v1/check. Un retrait refuse dès l'appel suivant et n'atteint qu'un système qui vérifie de nouveau.validUntilest une borne supérieure, jamais une durée de cache. - Ne décidez jamais d'un consentement à partir d'un catalogue en cache. Le catalogue et les documents servent à construire un formulaire; seule la vérification autorise un usage.
L'identité de la clé (whoami)
GET /v1/whoami · toute clé valide
Confirme, côté serveur, les portées que porte réellement la clé présentée, et le minimum qu'il faut pour découvrir l'organisation plutôt que de la coder en dur : son nom, la loi qui la régit et l'adresse absolue de sa page publique de confidentialité.
{
"scopes": ["check", "issue"],
"company": {
"name": "Acme inc.",
"sector": "private",
"statute": "P-39.1",
"publicPolicyUrl": "https://app.agreely.ca/p/acme/privacy"
}
}
sector vaut private ou public, statute vaut P-39.1 ou A-2.1.
publicPolicyUrl est null quand cette page ne se résoudrait pas (aucune politique
publiée et aucun responsable désigné). La réponse ne porte volontairement aucun
identifiant d'entreprise, aucun DID, aucun nom ni préfixe de clé, ni rien sur la
taille, le forfait, l'usage ou la facturation de l'organisation : une clé d'API est
un justificatif au porteur, et tout ce qu'elle obtient fuit avec elle.
La vérification : un refus est un 200
POST /v1/check · portée check
{
"customerId": "cust_8812",
"category": "Numéro de téléphone",
"purpose": "Facturation"
}
Une décision de consentement, autorisation ou refus, est toujours un 200. Le
refus n'est pas une erreur.
{
"decision": "allow",
"status": "active",
"consentRef": "0x3103…c354",
"assurance": "company_documented",
"tier": "verbal",
"validUntil": "2027-10-09T03:59:59Z",
"checkedAt": "2026-10-09T14:05:12Z"
}
{
"decision": "deny",
"status": "revoked",
"consentRef": "0x0fc2…337c",
"assurance": "company_attested",
"tier": "manual",
"validUntil": "2027-10-09T03:59:59Z",
"revokedAt": "2026-10-08T20:08:52Z",
"checkedAt": "2026-10-09T14:05:12Z"
}
{
"decision": "allow",
"status": "necessity",
"basis": "necessary_for_service",
"checkedAt": "2026-10-09T14:05:12Z"
}
Branchez toujours sur decision (allow ou deny). Le status dit pourquoi :
active(allow) : un consentement en vigueur couvre la cellule.necessity(allow) : aucun consentement consigné, mais la cellule active du catalogue déclare un motif autre que le consentement que la loi de l'organisation permet. La réponse porte alorsbasis, et niconsentRef, niassurance, ni dates : il n'y a aucune preuve signée. L'appel ne crée rien.none(deny) : aucun consentement sur une cellule fondée sur le consentement, ou aucune cellule active ne correspond aux libellés.revoked(deny) : le consentement a été retiré.expired(deny) : la durée de vie du consentement est écoulée (P-39.1, art. 14 al. 3; A-2.1, art. 53.1 al. 3).relationship_ended(deny) : l'entreprise a attesté que la relation est terminée (P-39.1, art. 23; A-2.1, art. 73). C'est un arrêt prospectif de l'usage; son enregistrement reste au statut actif (il n'a jamais été retiré).requires_depersonalization(deny) : la cellule déclare un motif que la loi de l'organisation ne permet que pour des renseignements dépersonnalisés. La vérification porte sur une référence client nommée, donc la condition ne peut pas être remplie ici. Ce n'est pas « aucun consentement » : n'y répondez pas en recueillant un consentement.basis_not_in_regime(deny) : la cellule déclare un motif que la loi de l'organisation ne connaît pas, typiquement une cellule restée dans le vocabulaire de l'autre secteur. Le correctif est une nouvelle déclaration au catalogue.
Deux valeurs anciennes méritent une note. sensitive_requires_consent n'est plus
émis depuis le 28 septembre 2026 : une cellule déclarée sensible répond d'après sa
base comme toute autre (sur consent, elle refuse none tant qu'il n'y a pas de
consentement, qui doit être exprès; sur un motif autre que le consentement, elle
autorise necessity). Et erased reste dans l'énumération de la spécification,
mais aucune réponse ne le porte : l'effacement détruit l'enregistrement que lit la
vérification, et la cellule répond ensuite comme s'il n'y en avait jamais eu
(none sur une cellule fondée sur le consentement).
Les champs d'une réponse adossée à un consentement :
consentRef: la référence0xde la cellule, qui sert au retrait et à l'effacement.assurance: la force de la preuve,citizen_signed(signé par la personne avec sa clé d'accès),company_attested(signé sur papier, attesté par l'entreprise) oucompany_documented(donné par téléphone, documenté par l'entreprise). Un client qui ne connaît que les deux premières valeurs doit traiter une valeur inconnue comme non acceptable.tier: le même palier sous son nom de stockage,full,manualouverbal. C'est votre système qui décide de ce que chaque palier débloque; par exemple, accepterverbalpour un usage et exigermanualpour un autre est votre choix de preuve, pas un seuil légal.validUntil: la fin du consentement (instant ISO 8601 en UTC), présente sur chaque statut adossé à un consentement, y comprisrevoked,expiredetrelationship_ended. Elle bouge dès qu'un consentement est renouvelé : relisez-la à chaque vérification.nullseulement pour un consentement ancien consigné sans fin.revokedAt: présent seulement quandstatusvautrevoked, c'est l'instant à partir duquel Agreely tient le consentement pour retiré. Pour une case laissée vide sur une feuille signée, c'est la date de la signature; pour un retrait consigné par/withdrawalou/revoke, c'est l'instant de la consignation, pas nécessairement celui où la personne l'a exprimé.
consentRef, assurance, tier, validUntil et revokedAt sont absents (pas
null) quand aucun consentement n'est en cause : none, necessity,
requires_depersonalization et basis_not_in_regime. Une ligne qu'un document
donne à titre d'information, et que la personne a seulement reconnue, n'est pas un
consentement : la vérification y répond d'après la base actuelle du catalogue,
comme s'il n'y avait aucun enregistrement.
Les valeurs de basis dépendent du secteur. Une entreprise privée (P-39.1)
reçoit contract, necessary_for_service, security_fraud, legal_obligation ou
professional_contact. Un organisme public (A-2.1) reçoit attributions,
programme, entente_collecte, compatible_use, manifest_benefit,
law_application ou public_character. Lisez regime dans GET /v1/catalog avant
de brancher sur basis.
Agreely consigne la base déclarée, il ne la valide pas
Une autorisation necessity repose sur la base légale que votre organisation a déclarée sur la cellule du catalogue. Agreely consigne cette déclaration et l'applique de façon cohérente; il ne se prononce pas sur son bien-fondé juridique. À noter : professional_contact est une exclusion, non un motif de nécessité : les renseignements liés à l'exercice d'une fonction au sein d'une entreprise échappent aux règles de collecte, d'utilisation et de communication (P-39.1, art. 1 al. 5). Il figure ici parce qu'il produit lui aussi une autorisation sans consentement. public_character joue le même rôle pour un organisme public (A-2.1, art. 55 al. 1). Les motifs de nécessité sont ceux de P-39.1 (art. 9 et 12) ou de A-2.1 (art. 64 et 65.1).
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'un retrait refuse dès l'appel suivant. Elle fonctionne en refus par défaut : il n'y a pas d'autorisation sans consentement 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.
Refus : 401, 402, 403, 422 invalid_request (customerId, category ou
purpose manquant, avec field; ou l'un d'eux envoyé comme une liste, un objet ou
un booléen, avec la raison invalid_field), 429.
La vérification par lot
POST /v1/check/batch · portée check
Évalue plusieurs cellules en un seul aller-retour, avec le même moteur de décision, la même règle de refus par défaut et le même journal d'accès par décision. Deux formes de corps sont acceptées. La forme plate :
{
"items": [
{
"customerRef": "cust_8812",
"category": "Numéro de téléphone",
"purpose": "Facturation"
}
]
}
Et la forme groupée, développée côté serveur en un produit client x champs :
{
"checks": [
{
"customerRef": "cust_8812",
"fields": [
{
"category": "Numéro de téléphone",
"purpose": "Facturation"
},
{
"category": "Courriel",
"purpose": "Infolettre"
}
]
}
]
}
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, y compris tier,
validUntil et revokedAt, plus les customerRef, category et purpose
renvoyés en écho :
{
"decisions": [
{
"customerRef": "cust_8812",
"category": "Numéro de téléphone",
"purpose": "Facturation",
"decision": "allow",
"status": "active",
"consentRef": "0x9a2b…7081",
"assurance": "citizen_signed",
"tier": "full",
"validUntil": "2031-01-01T04:59:59Z",
"checkedAt": "2026-10-09T14:05:12Z"
}
]
}
Un lot vide ou 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. Une référence ou un libellé envoyé comme une liste, un objet ou un
booléen donne 422 avec la raison invalid_field. Le lot nomme le sujet
customerRef, l'appel unique customerId; les SDK masquent cette différence.
Cas d'usage : protéger l'affichage d'une liste de clients
Afficher un tableau de N clients x M champs, c'est N x M utilisations de renseignements personnels. La loi limite l'utilisation aux fins de la collecte (P-39.1, art. 12; A-2.1, art. 65.1) et l'accès aux membres du personnel pour qui le renseignement est nécessaire à leurs fonctions (P-39.1, art. 20; A-2.1, art. 62). Agreely encadre chaque champ affiché d'une vérification et en consigne la décision, pour documenter cette limitation. 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.
Le catalogue et les documents
Lire le catalogue
GET /v1/catalog · portée check ou issue · filtre facultatif documentCode
Le catalogue (catégorie, finalité) actif de l'entreprise, en lecture seule.
Passez documentCode pour restreindre la réponse aux cellules d'un document publié :
la réponse ajoute alors document, dont le documentVersionId est celui qu'exigent
les consentements sur papier et par téléphone.
{
"regime": {
"sector": "private",
"statute": "P-39.1"
},
"document": {
"code": "formulaire-accueil",
"documentVersionId": "e4d41dc3-c8a5-44b1-96c5-be740c45cc6d"
},
"catalog": [
{
"id": "5b1e…",
"category": "Photo",
"purpose": "Publication",
"description": null,
"legalBasis": "consent",
"sensitive": false
}
]
}
legalBasis est le motif que l'entreprise déclare pour la cellule; consent
signifie qu'elle exige un consentement. Un documentCode inconnu ou étranger donne
le même 404.
Lire les cellules et leurs règles
GET /v1/catalog/cells · portée retention
Toutes les cellules, archivées comprises (avec leur status), chacune avec ses
libellés anglais (categoryEn, purposeEn) et retentionRuleKey, la règle de
conservation qui la gouverne. Un retentionRuleKey nul est une lacune du registre,
signalée et jamais comblée : le système hôte s'abstient.
Lister les documents publiés
GET /v1/consent-documents · portée check ou issue
Chaque document publié, avec ses cellules :
{
"documents": [
{
"code": "formulaire-accueil",
"documentVersionId": "e4d41dc3-c8a5-44b1-96c5-be740c45cc6d",
"version": "3",
"name": "Formulaire d'accueil",
"nameEn": "Intake form",
"effectiveDate": "2026-09-01",
"publishedAt": "2026-08-28T15:00:00Z",
"items": [
{
"id": "5b1e…",
"category": "Photo",
"categoryEn": "Photo",
"purpose": "Publication",
"purposeEn": "Publication",
"sensitive": false
}
]
}
]
}
Résolvez un document par son code, stable d'une version à l'autre; le
documentVersionId désigne une seule version et change à chaque publication.
Lire un document publié
GET /v1/consent-documents/{code} · portée check ou issue
Le document publié et sa divulgation complète en français et en anglais
(disclosure, responsable, automatedDecision, profiling, integrity), pour que
votre formulaire affiche le texte publié plutôt qu'une paraphrase. Aucune langue ne
se rabat sur l'autre. Un code inconnu ou étranger donne le même 404.
Télécharger le document d'information d'une version
GET /v1/consent-documents/versions/{documentVersionId}/pdf?locale=fr · portée
check, issue, attest ou attest_verbal
Le document d'information d'une version, en PDF (application/pdf), à imprimer
et à remettre à la personne, avec la feuille de signature ou seul. Il se nomme
lui-même document d'information, reproduit le texte de cette version et son
responsable, et précise qu'il ne contient aucune demande de consentement.
- La version est désignée par son identifiant, jamais par son code : c'est le texte de la version consignée que la personne doit recevoir. Une version remplacée depuis est encore servie et dit qu'elle a été remplacée.
localevautfr(par défaut) ouen. L'anglais est refusé (422english_text_missing) quand une section n'a pas de texte anglais, plutôt que d'imprimer un document incomplet.- Une version brouillon, inconnue, étrangère ou un identifiant malformé reçoivent le
même
404. - La réponse est
Cache-Control: no-store. Télécharger le PDF ne consigne rien : ce n'est pas une preuve que quelqu'un a été informé.
Les demandes de consentement (issue)
Émettre une demande
POST /v1/consent-requests · portée issue · honore Idempotency-Key
{
"customerId": "cust_8812",
"recipientEmail": "[email protected]",
"validUntil": "2027-10-09",
"documentCode": "formulaire-accueil"
}
Exactement un de consentDocumentId (l'identifiant de la version publiée) ou
documentCode désigne le document sous lequel la demande est émise. Les cellules
demandées découlent du document; un champ items est refusé (422). L'offre est
signée côté serveur et le courriel est envoyé une fois la demande enregistrée. Réponse 201 :
{
"requestId": "0x4b08…1234",
"status": "pending",
"deepLink": "https://…",
"emailDelivered": true,
"items": [
{
"category": "Photo",
"purpose": "Publication"
}
],
"document": {
"code": "formulaire-accueil",
"name": "Formulaire d'accueil",
"version": "3"
}
}
Lister les demandes
GET /v1/consent-requests · portée issue
Liste uniquement les demandes de l'entreprise de la clé, plus récentes d'abord,
avec nextCursor (null à la fin). Quatre paramètres, tous facultatifs :
| Paramètre | Rôle |
|---|---|
status |
pending, approved, asks_declined, refused, expired ou revoked_before_action. Un statut inconnu donne 400. |
customerId |
Votre propre référence client. 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, ramenée dans [1, 100], 50 par défaut. Hors bornes, la valeur est ramenée dans l'intervalle; non numérique, elle retombe sur le défaut. |
asks_declined désigne une demande à laquelle la personne a répondu en refusant
chaque demande de consentement, en confirmant seulement avoir reçu les lignes
données à titre d'information : aucun consentement n'a été obtenu, et cette demande
n'apparaît jamais comme approved.
Lire une demande
GET /v1/consent-requests/{id} · portée issue
Une demande par son requestId, avec son statut effectif (une demande en attente
dont l'échéance est passée se lit expired). Ses instants, validUntil,
expiresAt, createdAt et settledAt (null tant que la demande n'est pas
réglée), sont en RFC 3339, en UTC, comme dans la liste. Un identifiant inconnu ou
étranger donne 404.
Annuler une demande
POST /v1/consent-requests/{id}/cancel · portée issue
Annule une demande en attente : elle passe à revoked_before_action, et le lien
du citoyen ne mène plus à une approbation. L'appel est idempotent : une demande
déjà terminale renvoie son statut courant avec cancelled: false.
{
"requestId": "0x4b08…1234",
"status": "revoked_before_action",
"cancelled": true
}
Un requestId inconnu, étranger ou malformé donne 404 (jamais une écriture entre
locataires).
Les consentements sur papier (attest)
Un consentement signé à la main sur une feuille, puis consigné par l'organisation.
Sa réponse et sa vérification disent toujours assurance: "company_attested" et
tier: "manual" : Agreely consigne que l'organisation atteste détenir une feuille
signée dont l'empreinte correspond, il ne prouve pas qui l'a signée. Le guide
Enregistrer un consentement hors ligne en décrit le
parcours complet.
Consigner un consentement sur papier
POST /v1/manual-consents · portée attest · honore Idempotency-Key
{
"customerId": "cust_8812",
"documentVersionId": "e4d41dc3-c8a5-44b1-96c5-be740c45cc6d",
"effectiveDate": "2026-10-08",
"validUntil": "2027-10-31",
"items": [ { "category": "Photo", "purpose": "Publication" } ],
"evidence": { "pdfSha256": "0x9f2c…0d2e" },
"sensitiveExpressAttested": false,
"versionAttested": false
}
itemsnomme les demandes de consentement cochées sur la feuille (paires{category, purpose}ou identifiants de catalogue). Il peut être vide si chaque demande a reçu un « non ». Le serveur ajoute lui-même chaque ligne que le document donne à titre d'information, comme une reconnaissance et jamais comme un consentement.effectiveDateest la date de la signature : une dateAAAA-MM-JJou un instant RFC 3339 avec décalage horaire. Un instant dans le futur, même d'une seconde, est refusé (effective_date_in_future).evidence.pdfSha256est l'empreinte SHA-256 (0x+ 64 hex) de la feuille signée, calculée chez vous.evidence.pdf(base64) est facultatif, pour une mise sous séquestre; il doit correspondre à l'empreinte.sensitiveExpressAttesteddoit valoir le booléen JSONtruequand une finalité cochée est déclarée sensible et repose sur le consentement : l'organisation atteste que le consentement a été donné expressément. Agreely consigne cette attestation et la clé qui l'a envoyée; il ne la vérifie pas.versionAttesteddoit valoirtruequand la feuille a été signée avant la publication de la version choisie : l'organisation atteste que la feuille signée demandait le consentement pour les mêmes finalités, avec la même information.
Réponse 201 :
{
"consentId": "6f96…64ff",
"merkleRoot": "0x…",
"consentRefs": [
"0x…",
"0x…"
],
"assurance": "company_attested",
"anchored": false,
"acknowledged": [
{
"category": "Adresse",
"purpose": "Livraison",
"consentRef": "0x…"
}
],
"asksDeclined": false
}
anchored est faux au moment de l'enregistrement; l'ancrage suit de façon
asynchrone. asksDeclined vaut true quand aucune demande n'a été acceptée.
Une nouvelle feuille signée sur un consentement papier ou verbal encore en vigueur pour la même finalité est acceptée, et la vérification passe à la nouvelle; la précédente reste consignée telle quelle. Les refus d'état :
409stronger_consent_active: un consentement signé par clé d'accès est en vigueur pour une de ces finalités; il n'est jamais remplacé par un consentement sur papier.409renewal_ends_before_current: la nouvelle feuille prendrait fin avant le consentement en vigueur qu'elle remplace. Agreely traite un consentement plus court comme un retrait : consignez plutôt un retrait.409predates_withdrawal: la signature est antérieure à un retrait consigné pour la même finalité; elle ne le rouvre jamais (field:effectiveDate).409relationship_ended: la relation avec ce client est terminée : elle n'accepte aucun nouveau consentement.409verbal_awaits_paper: un consentement verbal de ce document attend sa feuille signée pour ce client : consignez cette feuille comme sa confirmation (/v1/verbal-consents/{consentId}/paper).
Refus d'entrée (422) : missing_field, invalid_field, invalid_customer_id,
invalid_document, document_placeholder, notice_only (le document ne demande
aucun consentement), no_items, invalid_items, invalid_pdf_hash,
pdf_hash_mismatch, pdf_not_pdf, pdf_empty, invalid_effective_date,
effective_date_in_future, invalid_valid_until, valid_until_in_past,
valid_until_too_far (au-delà de 10 ans, une règle de produit d'Agreely),
version_attestation_required, sensitive_express_required,
capacity_declaration_required, capacity_verification_invalid,
no_verified_domain.
Produire une feuille de signature
POST /v1/customers/{customerRef}/consent-sheets · portée attest
La feuille de signature d'une version publiée, pour un client que l'organisation connaît, en PDF à imprimer, avec le lien de réclamation que la personne pourra utiliser plus tard. C'est la même feuille que celle du parcours papier de l'application : une paire Oui/Non par demande de consentement, les finalités d'information sans case, la signature, et une référence imprimée sous la signature. Elle ne nomme jamais la personne.
{
"documentVersionId": "e4d41dc3-c8a5-44b1-96c5-be740c45cc6d",
"locale": "fr"
}
Réponse 201, Cache-Control: no-store :
{
"signatureSheet": {
"documentVersionId": "e4d41dc3-…",
"locale": "fr",
"contentType": "application/pdf",
"filename": "agreely-formulaire-consentement-….pdf",
"pdf": "<base64>"
},
"printedReference": "ABCD-EFGH",
"claim": {
"claimUrl": "https://…",
"token": "…",
"expiresAt": "2026-12-08T14:05:12Z"
}
}
Imprimer, c'est émettre. La référence imprimée est le second facteur du lien de
réclamation émis dans le même appel, et seule son empreinte est conservée : elle et
le token sont renvoyés une seule fois, dans cette réponse. Une nouvelle feuille
retire tout lien encore actif pour ce client, si bien que la référence d'une feuille
précédente cesse de fonctionner. Une nouvelle tentative avec la même
Idempotency-Key répond 409 already_minted et ne produit rien.
Deux règles à respecter avec une feuille de signature
N'envoyez jamais le lien de réclamation dans la même enveloppe que la feuille, ni dans le même message. La référence imprimée ne protège qu'un lien qui voyage séparément : réunis sur un même papier, ou dans un même envoi, les deux facteurs n'en font plus qu'un. Le PDF de la feuille porte la référence, jamais le lien.
N'envoyez jamais l'empreinte de la feuille vierge comme evidence.pdfSha256. La preuve est la feuille signée, numérisée au retour; c'est son empreinte que demande POST /v1/manual-consents. C'est pourquoi cette réponse ne porte volontairement aucune empreinte.
404unknown_customer(codenot_found) : l'organisation ne détient aucun dossier pour ce client (la même réponse que pour les liens de réclamation). Rien n'est écrit.409relationship_ended(codeconflict) : la relation est terminée : aucun lien n'est émis.409already_minted(code et raison) : cetteIdempotency-Keya déjà produit une feuille; envoyez une nouvelle clé pour en imprimer une autre.413body_too_large(code et raison) : le corps dépasse 4 096 octets.422unknown_field(code et raison) : un membre que la requête ne définit pas.422invalid_field(codeinvalid_request) :localen'est nifrnien.422invalid_document(codeinvalid_request) : la version n'est pas la version publiée d'un document de l'organisation.422notice_only(codeno_consent_ask) : aucune finalité de cette version n'est une demande de consentement : il n'y a rien à signer.422claim_link_already_live(codeinvalid_request) : un lien a été émis au même instant pour le même client.
Émettre un lien de réclamation
POST /v1/manual-consents/claim-links · portée attest
Émet un lien à usage unique que la personne utilise pour rattacher ses
consentements hors ligne à son identifiant décentralisé de citoyen. Corps
{ "customerId": "cust_8812", "reference": "order-4471" } (reference facultatif);
réponse 201 { "claimUrl", "token", "expiresAt" }. Émettre un lien retire tout
lien encore actif pour ce client.
Refus : 404 unknown_customer (aucun dossier pour ce client, rien n'est écrit),
409 relationship_ended, 422 missing_field (sans customerId) ou
claim_link_already_live.
Retirer un consentement consigné par l'entreprise
POST /v1/manual-consents/{consentRef}/revoke · portée attest ou attest_verbal
Le retrait d'un consentement que l'organisation a consigné, sur papier ou par
téléphone. Une clé qui ne porte que attest_verbal n'atteint que les consentements
verbaux. Corps facultatif { "reason": "…" } (1 000 caractères au plus, sans
caractère de contrôle). Réponse 200 :
{
"consentRef": "0x0fc2…337c",
"revoked": true,
"alreadyRevoked": false,
"gate": "denied"
}
gate dit ce que /v1/check répond désormais pour cette finalité : denied (ce
consentement portait la décision, qui refuse maintenant), superseded (un
consentement plus récent pour la même finalité portait déjà la décision et n'est pas
touché) ou unchanged (répétition idempotente). Un retrait s'attache à la
finalité, jamais à un seul enregistrement : il emporte au même instant chaque
autre consentement de la même finalité encore en cours pour cette personne et lié à
celui-ci (un renouvellement et la feuille qu'il renouvelle, un consentement verbal et
la feuille qui l'a confirmé).
404unknown_consent: référence absente, malformée, étrangère, ou hors de la portée de la clé : la même réponse.409superseded_by_paper: ce consentement verbal a été confirmé par sa feuille signée : retirez laconsentRefdu consentement sur papier qui l'a confirmé (GET /v1/verbal-consents/{consentId}la donne).409citizen_consent_at_gate: la personne a signé la même finalité en ligne, avec sa clé d'accès, après ce consentement : cette route ne met jamais fin à un consentement que la personne a signé. Consignez son retrait par la route de retrait, qui retire les deux au même instant. Rien n'est écrit.422invalid_field:reasontrop long ou invalide.
Effacer un consentement consigné par l'entreprise
POST /v1/manual-consents/{consentRef}/erase · portée attest seulement
Procède à l'effacement (destruction cryptographique) d'une cellule consignée par
l'entreprise : le sel de la cellule, l'enregistrement d'application, le reçu et tout
PDF sous séquestre sont détruits; l'engagement est conservé dans l'arbre de Merkle. Un
effacement ne se défait pas, d'où la portée attest seule. Corps facultatif
{ "reason": "…" }. Réponse 200 :
{
"consentRef": "0x0fc2…337c",
"erased": true,
"alreadyErased": false,
"gate": "denied"
}
Effacer la cellule papier qui a confirmé un consentement verbal efface aussi la
cellule verbale. Effacer un consentement encore en vigueur retire (sans les
effacer) les autres consentements de la même finalité encore en cours, qui gardent
leur preuve. Les refus sont ceux du retrait : 404 unknown_consent, 409
superseded_by_paper et 409 citizen_consent_at_gate (consignez d'abord le retrait
de la personne, puis effacez). L'effacement ne consulte pas les retenues de
conservation : vérifiez qu'aucune demande d'accès ou de rectification n'est en cours.
Les consentements par téléphone (attest_verbal)
Un consentement donné par téléphone et documenté par l'organisation. C'est le
plus faible des trois paliers (full > manual > verbal) : il n'y a aucun
document, la preuve est la parole de l'organisation. La réponse et la vérification
le nomment tier: "verbal" et assurance: "company_documented", jamais « signé ».
La portée attest_verbal est distincte et n'est jamais cochée par défaut.
Consigner un consentement verbal
POST /v1/verbal-consents · portée attest_verbal · honore Idempotency-Key
{
"customerId": "client-7",
"documentVersionId": "e4d41dc3-c8a5-44b1-96c5-be740c45cc6d",
"answers": [
{ "category": "Photo", "purpose": "Publication", "answer": "yes" },
{ "category": "Adresse", "purpose": "Livraison de repas", "answer": "no" }
],
"obtainedAt": "2026-10-08T14:05:00-04:00",
"obtainedBy": "Luc Gagnon",
"scriptVersion": "accueil-v3",
"respondent": { "consentedBy": "self" },
"validUntil": "2027-10-31",
"paperExpected": true
}
- Chaque demande posée reçoit une réponse explicite,
"yes"ou"no". Seuls les « oui » deviennent des consentements; chaque « non » est conservé dans le reçu signé. Une réponse sur une ligne donnée à titre d'information est refusée : le serveur consigne ces lignes comme reconnues à la lecture du script. documentVersionIdlie le consentement à la version exacte du document lue au téléphone.scriptVersionest le libellé que votre organisation donne au script lu (lettres, chiffres,.,_,-, 64 caractères au plus). Le script à lire est offert dans l'application Web d'Agreely, à la page Consentement verbal de la fiche du client.obtainedAtest l'instant de l'appel (la date du consentement), RFC 3339 avec décalage horaire, au plus 7 jours dans le passé et jamais dans le futur : un appel daté une seconde après l'horloge d'Agreely est refusé (obtained_at_in_future). Le moment de la consignation est conservé à côté.obtainedBynomme la personne qui a pris l'appel; il est chiffré et reste hors du reçu signé.respondent.consentedByvautself,self_with_assistantourepresentative. Pour un représentant,representativeCapacityest requis. Pour un mineur de moins de 14 ans (isMinor: true), le représentant doit être le titulaire de l'autorité parentale ou le tuteur, nommé dansrespondent.name.sensitiveExpressAttested: trueest requis quand un « oui » porte sur une finalité sensible fondée sur le consentement.paperExpecteddit que l'organisation attend la feuille signée en retour. C'est un attribut, pas un palier : si la feuille ne revient jamais, le consentement resteverbal, et Agreely ne le fait pas expirer pour autant.
Réponse 201 :
{
"consentId": "0a4f…",
"merkleRoot": "0x…",
"consentRefs": [
"0x…"
],
"tier": "verbal",
"assurance": "company_documented",
"anchored": false,
"paperExpected": true,
"acknowledged": [],
"asksDeclined": false
}
Une finalité déjà couverte par un consentement plus fort en vigueur (sur papier ou par clé d'accès) n'est jamais remplacée par un consentement verbal, sauf dans la fenêtre de renouvellement décrite ci-dessous.
409stronger_consent_active(codeconflict) : un consentement plus fort est en vigueur pour une de ces finalités; laissez-les de côté. Rien n'est consigné.409all_covered(codeconflict) : chaque demande du document est déjà couverte par un consentement plus fort.409renewal_ends_before_current(codeconflict) : le nouveau consentement prendrait fin avant celui qu'il prend en relais.409renewal_predates_current(codeconflict) : le renouvellement est daté avant la signature de la feuille qu'il renouvelle.409predates_withdrawal(codeconflict) : l'appel est daté avant un retrait consigné pour la même finalité (à la même seconde, le retrait l'emporte).409relationship_ended(codeconflict) : la relation avec ce client est terminée.429daily_cap(codeverbal_daily_cap) : plafond quotidien de consentements verbaux atteint.
Refus d'entrée (422) : missing_field, invalid_answers, duplicate_answer,
informed_line_answered, nothing_consented, invalid_obtained_at,
obtained_at_in_future, invalid_obtained_by, invalid_script_version,
invalid_respondent, minor_needs_guardian, invalid_valid_until,
valid_until_too_far, sensitive_express_required, et les raisons communes au
papier (invalid_customer_id, invalid_document, document_placeholder,
notice_only, no_items, invalid_items, no_verified_domain).
Renouveler par téléphone un consentement sur papier
Un consentement signé sur papier dont la fin tombe dans les 30 jours civils (au
calendrier de l'organisation) peut être renouvelé par téléphone. Ces 30 jours sont
une règle de produit d'Agreely, pas un délai prévu par la loi. La requête est un
POST /v1/verbal-consents ordinaire; un « oui » pour cette finalité est accepté :
- c'est un nouveau consentement, qui renouvelle la feuille sans jamais la réécrire;
/v1/checkrépond aussitôttier: "verbal"avec le nouveauvalidUntil, jusqu'au retour de la nouvelle feuille signée (POST .../paper), qui le fait passer àmanualavec ce mêmevalidUntil. Un système qui exigemanualvoit doncverbalentre l'appel et la feuille;- son
validUntilne peut pas être antérieur à celui de la feuille (renewal_ends_before_current), et l'appel ne peut pas être daté avant la signature de la feuille (renewal_predates_current); - un « non » au renouvellement ne retire rien : la feuille reste en vigueur jusqu'à sa propre fin;
- un retrait fait par n'importe quelle référence de la chaîne retire aussi le consentement en cours : retirer le renouvellement retire la feuille, et inversement.
Hors de la fenêtre, la requête est refusée comme d'habitude (stronger_consent_active
ou all_covered). Une feuille retirée ou échue ne bloque rien. Un consentement
signé par clé d'accès n'est jamais renouvelé par téléphone, même dans ses derniers
jours : renouvelez-le par une nouvelle demande en ligne.
Consigner la feuille signée d'un consentement verbal
POST /v1/verbal-consents/{consentId}/paper · portée attest · honore
Idempotency-Key
La feuille signée est revenue. Il faut attest (et non attest_verbal), parce que
la feuille produit un consentement attesté par l'entreprise.
{
"signedAt": "2026-10-20T10:30:00-04:00",
"answers": [
{
"category": "Photo",
"purpose": "Publication",
"answer": "yes"
}
],
"evidence": {
"pdfSha256": "0x…"
}
}
Le consentement verbal n'est jamais réécrit : chaque finalité cochée sur la feuille
passe dans un nouveau consentement manual, et la vérification le suit. Les deux
dates restent distinctes : le consentement date de l'appel, la signature est sa
date de preuve. Une finalité laissée sans « oui » sur la feuille est consignée comme
un retrait daté de la signature. La feuille doit répondre exactement aux finalités
encore consenties par téléphone; une finalité consentie pour la première fois sur
papier est un nouveau consentement (POST /v1/manual-consents). Réponse 201 :
{
"verbalConsentId": "0a4f…",
"manualConsentId": "7c21…",
"merkleRoot": "0x…",
"tier": "manual",
"confirmed": [
{
"category": "Photo",
"purpose": "Publication",
"verbalConsentRef": "0x…",
"consentRef": "0x…"
}
],
"withdrawn": []
}
Refus : 404 not_found; 409 already_confirmed (une seule feuille par
consentement verbal), lapsed (le consentement verbal a atteint sa fin) ou
nothing_to_confirm; 422 invalid_signed_at, signed_at_in_future,
paper_answer_missing, paper_purpose_not_consented_by_phone, ainsi que
invalid_pdf_hash et pdf_hash_mismatch.
Lire l'historique d'un consentement verbal
GET /v1/verbal-consents/{consentId} · portée attest_verbal ou attest
Ce qui a été dit au téléphone, ce que la feuille a dit, et ce qui est en vigueur
aujourd'hui. Le consentement verbal garde tier: "verbal" pour toujours; state
vaut awaiting_paper, verbal_only ou paper_received, et chaque élément de
purposes donne la réponse (yes, no ou informed), son statut et, le cas
échéant, la consentRef papier qui le confirme. Un identifiant absent, étranger,
malformé ou non verbal reçoit le même 404 (raison not_found).
Le retrait consigné au nom de la personne (withdraw)
POST /v1/customers/{customerRef}/consents/{consentRef}/withdrawal · portée
withdraw · honore Idempotency-Key
L'organisation consigne, au nom de la personne, le retrait qu'elle lui a demandé
par n'importe quel canal, sur tout consentement qu'elle détient pour elle : sur
papier, par téléphone, ou signé en ligne avec sa clé d'accès. C'est le jumeau /v1
de l'action « Consigner un retrait » de la fiche client : /v1/check refuse aussitôt,
la fin du consentement est ancrée, et l'acte est consigné comme une attestation de
l'organisation, jamais comme la signature de la personne. La demande peut venir de la
personne ou de son représentant (par exemple le titulaire de l'autorité parentale ou
le tuteur d'un mineur de moins de 14 ans).
{
"channel": "phone",
"operator": "intervenant-0042",
"requestedAt": "2026-10-08T09:15:00-04:00",
"reason": "Appel du 8 octobre : ne souhaite plus recevoir l'infolettre."
}
channel(requis) :phone,email,mail,in_personouother.operator(requis) : l'identifiant opaque du membre de votre personnel qui a reçu la demande (1 à 64 lettres, chiffres,.,_,:ou-). Jamais un nom, jamais une adresse courriel. Il est consigné dans l'attestation chiffrée et n'est jamais renvoyé.requestedAt(facultatif) : quand la personne l'a demandé, tel que déclaré, RFC 3339 avec décalage horaire, pas avant le consentement retiré ni dans le futur. Il est consigné à côté du retrait sans déplacer l'instant où Agreely ferme la vérification. Recommandation : cessez l'usage dès la demande de la personne et consignez le retrait sans délai.reason(facultatif) : 1 000 caractères au plus, chiffré, détruit à l'effacement, jamais sur la chaîne, jamais renvoyé.
Réponse 200 :
{
"consentRef": "0x9f2c…0d2e",
"withdrawn": true,
"alreadyWithdrawn": false,
"recordedOnBehalf": true,
"assurance": "company_attested",
"gate": "denied",
"alsoWithdrawn": [
"0x…"
]
}
recordedOnBehalf: trueetassurance: "company_attested"toujours : Agreely consigne que l'organisation dit avoir reçu et traité une demande; il n'en a pas été témoin. La réponse est la même quel que soit le palier du consentement, si bien qu'elle ne dit jamais si la personne détient un compte citoyen.gatevautdenied(plus rien ne porte la décision),superseded(un autre consentement de la même finalité, signé en ligne après lerequestedAtdéclaré, la porte encore et n'est pas touché) ouunchanged(répétition).alsoWithdrawnliste les autres consentements de la même finalité retirés au même instant. Un retrait s'attache à la finalité : retirer par n'importe quelle référence de la chaîne (un renouvellement et la feuille qu'il renouvelle, un consentement verbal et la feuille qui l'a confirmé) retire le consentement en cours. Quand la personne a signé la même finalité en ligne au plus tard aurequestedAtdéclaré, ce consentement est retiré lui aussi.
Le retrait est accepté après la fin de la relation (le droit de retirer survit à
la relation) et pendant un défaut de paiement : c'est la seule route /v1 qui
ne répond jamais 402, parce que donner suite à un retrait est une obligation, pas
un service payant. Une répétition répond 200 avec alreadyWithdrawn: true,
gate: "unchanged" et alsoWithdrawn: [], et n'écrit rien.
404unknown_consent(codenot_found) : référence de consentement absente, malformée ou étrangère, référence client inconnue, ou consentement d'un autre client de la même organisation : la même réponse, octet pour octet.409consent_lapsed(codeconflict) : le consentement a atteint sa propre fin : il n'y a plus rien à retirer. Cela ne veut jamais dire que rien n'est en vigueur pour la finalité; relisez/v1/check.422not_revocable(codeinvalid_request) : la cellule n'a jamais été une demande de consentement (elle repose sur un autre motif). Le message cite la disposition de la loi qui régit votre organisation.422invalid_channel,invalid_operator,requested_at_invalid,requested_at_in_future,requested_at_before_grant,invalid_reason(codeinvalid_request) : champ invalide.429daily_cap(codewithdrawal_daily_cap) : plafond de 50 retraits par 24 heures glissantes atteint, sansRetry-After. Consignez les retraits suivants depuis la fiche client dans Agreely.
Une répétition n'est jamais refusée par le plafond, et la route
/v1/manual-consents/{consentRef}/revoke n'entame pas ce budget. Au premier refus de
la journée, le propriétaire de l'espace de travail reçoit un courriel (des comptes et
des noms de clés seulement), et il reçoit chaque jour un relevé, par clé, de ce que
les clés de l'organisation ont consigné.
Le registre des clients (registry)
La portée registry n'offre aucune route de liste : chaque route est adressée
par une seule référence client que vous connaissez déjà. Aucune lecture en masse
n'est donc possible, par construction.
Consigner l'identité d'un client
PUT /v1/customers/{customerRef} · portée registry
Consigne ou modifie l'identité que le registre détient pour un client tenu sans
consentement, sur un motif documenté. C'est une fusion, jamais un remplacement :
un champ absent reste tel quel, un champ à null ou "" est effacé, un champ avec
une valeur est écrit.
{
"displayName": "Léa Tremblay",
"email": "[email protected]",
"legalBasis": "contract",
"basisNote": "Contrat de service no 4471",
"noticeLocale": "fr"
}
legalBasis doit appartenir au vocabulaire du secteur de l'organisation, et ne peut
pas valoir consent (un client tenu sur consentement relève du parcours de
consentement). Réponse 201 si l'identité n'existait pas, 200 si elle a été
fusionnée; le corps est celui du GET ci-dessous.
Refus : 422 invalid_request avec field (référence de 1 à 200 caractères,
displayName de 200 caractères au plus, email valide, basisNote de 2 000
caractères au plus, legalBasis hors vocabulaire, noticeLocale autre que fr ou
en); 409 identity_erased (une déclaration de destruction ou d'anonymisation a
retiré l'identité : un nom, un courriel, une note ou une langue sont refusés, mais
un champ peut encore être effacé); 409 identity_held (une retenue sur tous les
renseignements conserve l'identité : aucun changement à ces quatre champs, effacement
compris).
Lire ce que le registre détient
GET /v1/customers/{customerRef} · portée registry
Ce qu'Agreely détient pour cette référence, en métadonnées, jamais en clair :
{ "customerRef": "cust_8812", "registered": true, "source": "api",
"hasDisplayName": true, "hasEmail": true, "hasBasisNote": true,
"legalBasis": "contract", "noticeLocale": "fr",
"createdAt": "2026-10-01T14:05:12Z", "updatedAt": "2026-10-08T13:15:00Z",
"relationship": { "status": "active", "endedAt": null } }
hasEmail: true, jamais l'adresse. Agreely n'est pas un endroit où relire vos
clients : c'est là que vit le registre de responsabilité. Une référence que
l'organisation n'a jamais utilisée donne 404.
La relation client (relationship)
Mettre fin à une relation
POST /v1/customers/{customerRef}/relationship/end · portée relationship
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 P-39.1 (article 73 de A-2.1
pour un organisme public) : « les fins [...] sont accomplies ». C'est la même écriture que l'action de l'interface : elle ne révoque,
n'efface ni ne masque aucun consentement par cellule. Corps { "reason": "…" }
requis (2 000 caractères au plus). L'appel est idempotent : le endedAt et
l'origine endedBy d'origine sont préservés.
{
"customerRef": "cust_8812",
"status": "ended",
"endedAt": "2026-06-26T15:04:05Z",
"endedBy": "company"
}
Refus : 404 (référence inconnue ou étrangère, rien n'est écrit), 422 (reason
absent ou vide; le message cite la loi de l'organisation).
Annuler une fin erronée
POST /v1/customers/{customerRef}/relationship/revert · portée relationship
Annule une fin prématurée ou erronée attestée par l'entreprise. C'est une
correction d'une inscription inexacte (P-39.1, art. 11; A-2.1, art. 72), jamais
une résurrection d'un consentement mort. Permise seulement si la fin était
attestée par l'entreprise, si rien n'a été détruit (aucun consentement retiré,
aucun effacement) et si l'appel arrive dans les 30 jours (une règle de produit
d'Agreely, pas un délai prévu par la loi). Sinon : 404 propre, rien n'est écrit;
une personne qui revient est une nouvelle relation : le consentement antérieur ne
valait que pour la durée nécessaire à ses fins (P-39.1, art. 14 al. 3; A-2.1,
art. 53.1 al. 3).
Corps { "reason": "…" } requis.
{ "customerRef": "cust_8812", "status": "active", "reverted": true }
La conservation (registry, retention, holds)
Votre organisation décide, Agreely consigne et rappelle, le système hôte
exécute et rend compte. Rien ici
ne vérifie ce que fait le système hôte : chaque écriture consigne une
déclaration ("status": "declared"), jamais une vérification.
Lire la posture de conservation d'un client
GET /v1/customers/{customerRef}/retention · portée registry
{
"customerRef": "cust_8812",
"relationship": {
"status": "ended",
"endedAt": "2026-03-31T20:00:00Z"
},
"clock": {
"trigger": "purpose_achieved",
"recipe": "dueAt = the tenant-local calendar date of endedAt, plus periodMonths calendar months",
"dueAt": null,
"reason": "hold_active",
"rules": [
{
"ruleId": "b7e0…",
"label": "Dossiers clients",
"labelEn": "Customer files",
"periodMonths": 24,
"action": "destroy",
"dueAt": "2028-03-31",
"held": true
}
]
},
"disposition": null,
"holds": [
{
"id": "3c9d…",
"status": "active",
"scope": "all",
"placedBy": "api",
"startedOn": "2026-10-08",
"placedAt": "2026-10-08T13:00:00Z",
"releasedAt": null,
"ground": "rights_request",
"hasProvision": false,
"reviewOn": null,
"hasReleaseReason": false
}
],
"releasedTruncated": false
}
clock.rules[].dueAtest l'échéance arithmétique de chaque règle;held: truedit qu'une retenue active la suspend, et que cette date n'est pas une date à laquelle agir.clock.dueAtest l'échéance la plus proche d'une règle qu'aucune retenue ne suspend. Elle estnullavecreason:no_declared_rule(aucune règle déclarée, et aucune durée n'est inventée),relationship_activeouhold_active(chaque règle est retenue).holdsliste chaque retenue active et les 20 dernières levées (releasedTruncatedquand il y en a davantage).placedByvautapiouorganization;ground,hasProvision,reviewOnethasReleaseReasonne sont relus que pour une retenue que l'API a placée. Jamais le texte d'une disposition légale ni d'un motif de levée.dispositionest la déclaration en vigueur, avec sonagreelyIdentity.
Une référence inconnue, étrangère ou malformée donne le même 404.
Déclarer ce qui a été fait des renseignements
POST /v1/customers/{customerRef}/retention/dispositions · portée registry
Le système hôte déclare destroyed, anonymized ou legal_hold pour un client
dont la relation est terminée.
{ "disposition": "destroyed", "scheduleRef": "CC-2026-04" }
reason est requis pour legal_hold (il nomme la loi, pas la personne), et
retentionUntil (AAAA-MM-JJ) n'est permis qu'avec legal_hold. Le registre est
en ajout seulement : une correction est un nouvel ajout qui remplace la déclaration
en vigueur. Réponse 201 (ajout) ou 200 (déclaration identique déjà en vigueur,
rejouée avec appended: false) :
{
"customerRef": "cust_8812",
"recorded": true,
"status": "declared",
"appended": true,
"id": "d1f0…",
"disposition": "destroyed",
"retentionUntil": null,
"hasReason": false,
"endedAtSnapshot": "2026-03-31T20:00:00Z",
"scheduleRef": "CC-2026-04",
"basis": "contract",
"hasBasisNote": true,
"declaredBy": "api:purge-nocturne",
"declaredAt": "2026-10-09T03:00:00Z",
"agreelyIdentity": "erased",
"warnings": []
}
Une déclaration de destruction retire l'identité détenue par Agreely
Une déclaration destroyed ou anonymized ajoutée (pas rejouée) retire, dans la même transaction, le nom, le courriel, la note de base et la langue d'avis que le registre d'Agreely détient pour ce client. La référence, le motif déclaré, les déclarations, la preuve de consentement et le registre des demandes de droits restent. Rien n'est retiré sur un legal_hold, ni tant qu'une retenue sur tous les renseignements du client est en place (la levée de cette retenue les retire alors). Les sauvegardes existantes ne sont pas modifiées.
agreelyIdentity dit ce que la déclaration a fait : erased (l'identité a été
retirée), none_held (rien n'était détenu), retained_hold (une retenue la
conserve) ou retained (un legal_hold); null pour une déclaration antérieure à
ce champ. Une déclaration faite pendant qu'une retenue est active est consignée,
jamais refusée, avec un avertissement { "code": "hold_active", "holdIds": […] }
dans warnings.
Refus : 404; 409 relationship_active (la relation n'est pas terminée :
mettez-y fin d'abord, ou placez une retenue pour conserver des renseignements
pendant qu'elle se poursuit); 422 invalid_request.
Placer une retenue de conservation
POST /v1/customers/{customerRef}/retention/holds · portée registry · honore
Idempotency-Key
Consigne que les renseignements d'un client doivent être conservés malgré les règles de conservation, sur une relation en cours ou terminée.
{
"ground": "other_law",
"provision": "Une obligation de conservation prévue par une autre loi, citée par l'organisation",
"scope": {
"rules": [
"b7e0…"
],
"cells": []
},
"startedOn": "2026-10-08",
"reviewOn": "2027-04-08"
}
ground(requis) :rights_request(les renseignements font l'objet d'une demande d'accès ou de rectification : P-39.1, art. 36; A-2.1, art. 102.1) ouother_law.provision: requis pourother_law, refusé sinon. La disposition est consignée telle qu'écrite, jamais vérifiée (2 000 caractères au plus). Ce que la loi réserve diffère selon le régime : P-39.1 (art. 23) réserve un délai de conservation prévu par une loi; A-2.1 (art. 73) ne réserve que la Loi sur les archives et le Code des professions.scope:"all"(par défaut), ou{ "rules": [...], "cells": [...] }avec les clés deGET /v1/retention/ruleset deGET /v1/catalog/cells(200 de chaque au plus). Seule une retenue surallconserve aussi l'identité au registre après une déclaration de destruction.startedOn(AAAA-MM-JJ, aujourd'hui ou avant au calendrier de l'organisation, aujourd'hui par défaut) etreviewOn(un rappel, jamais une levée automatique).
Rien ne lève une retenue de lui-même : la levée est un acte explicite, avec un
motif. Réponse 201 avec la retenue (customerRef, recorded: true,
replayed: false), ou 200 quand la même Idempotency-Key et le même corps rejouent
la première réponse. Un système hôte ne lie jamais une demande de droits : le
registre des demandes place lui-même ses retenues liées.
Refus : 404 (référence inconnue, étrangère, malformée ou trop longue); 422
invalid_request (clé de règle ou de cellule inconnue, étrangère ou malformée, champ
invalide), unknown_field (tout champ non défini, dont rightsRequestId) ou
too_many_holds (50 retenues actives par client); 429 hold_budget_exhausted
(1 000 retenues placées par l'API par 24 heures).
Lever une retenue de conservation
POST /v1/customers/{customerRef}/retention/holds/{holdId}/release · portée
registry · honore Idempotency-Key
Lève n'importe quelle retenue active de l'organisation sur ce client, y compris
celles que le registre des demandes de droits ou un membre de l'organisation ont
placées. Corps { "reason": "…" } requis (2 000 caractères au plus, chiffré, jamais
renvoyé); tout autre champ donne 422 unknown_field. La levée est consignée et
attribuée à la clé.
{
"customerRef": "cust_8812",
"recorded": true,
"id": "3c9d…",
"status": "released",
"scope": "all",
"placedBy": "organization",
"startedOn": "2026-10-01",
"placedAt": "2026-10-01T15:00:00Z",
"releasedAt": "2026-10-09T14:05:12Z",
"agreelyIdentity": null
}
Lever une retenue que le registre des demandes a placée est consigné et jamais refusé; toute obligation de conserver les renseignements visés jusqu'à l'épuisement des recours de la personne reste celle de l'organisation, et Agreely ne la vérifie pas. Chaque levée d'une retenue que votre système n'a pas placée est signalée par courriel au propriétaire de l'organisation, et plafonnée à 20 par 24 heures glissantes; lever les retenues que votre système a placées n'est pas plafonné.
Quand c'est la dernière retenue active sur tous les renseignements et qu'une
déclaration de destruction ou d'anonymisation est en vigueur, la levée retire
l'identité du registre dans la même transaction : agreelyIdentity vaut alors
erased. Sinon retained_hold, none_held ou null (aucune telle déclaration).
Refus : 404 (identifiant de retenue inconnu, étranger, malformé ou d'un autre
client); 409 already_released; 422; 429 hold_release_cap_reached.
Lire le flux des retenues
GET /v1/retention/holds · portée holds · filtres changedSince et pageToken
Ce qu'une tâche de purge lit avant de purger : les références à épargner et la
portée de chaque retenue, jamais le motif, l'origine, la disposition légale ni
une date autre que changedAt. La portée holds est accordée séparément et n'est
jamais cochée par défaut : la clé d'une tâche de purge porte délibérément
retention et holds.
{
"holds": [
{
"id": "3c9d…",
"customerRef": "cust_8812",
"status": "active",
"scope": "all",
"changedAt": "2026-10-08T13:00:00Z"
}
],
"nextPageToken": null,
"cursor": "2026-10-09T13:58:00Z"
}
- Sans
changedSince: chaque retenue active, un instantané qui remplace l'ensemble actif de votre système. - Avec
changedSince: chaque retenue placée ou levée depuis, avec sonstatus. - Pages de 500. Renvoyez
nextPageTokentel quel commepageTokenjusqu'à une page qui répondcursor, lechangedSincede la prochaine synchronisation. Le jeton est opaque mais non signé : renvoyez-le sans le modifier. - Le curseur traîne derrière la lecture, si bien qu'une retenue en cours de
validation est renvoyée plutôt que manquée : la livraison est au moins une
fois, mettez à jour par
id. - Un
changedSinceou unpageTokenmalformé ou dans le futur donne422.
En cas de doute, ne purgez rien
Traitez un 401, un 403 ou une erreur 5xx sur ce flux comme « ne rien purger ». Une purge faite sans connaître les retenues en place ne peut pas se défaire.
Lire les règles de conservation
GET /v1/retention/rules · portée retention · filtre facultatif changedSince
Chaque règle de l'entreprise, archivées comprises, avec sa durée décidée :
{
"cursor": "2026-10-09T13:55:00Z",
"rules": [
{
"key": "b7e0…",
"label": "Dossiers clients",
"labelEn": "Customer files",
"duration": {
"value": 24,
"unit": "months"
},
"trigger": "purpose_achieved",
"action": "destroy",
"anonymizationProcessKey": null,
"status": "active",
"archivedAt": null,
"updatedAt": "2026-09-01T12:00:00Z"
}
],
"ruleKeys": [
"b7e0…"
]
}
Passez le cursor précédent comme changedSince pour ne recevoir que les règles
changées depuis; ruleKeys porte toujours l'ensemble complet des clés, si bien
qu'une règle supprimée se voit comme une clé disparue. trigger vaut collection,
purpose_achieved, last_activity ou decision; action vaut destroy ou
anonymize.
GET /v1/retention/rules/{ruleKey} renvoie une règle et lastReports, les derniers
instants de purge et de passe déclarés par l'hôte, chacun à côté de leur
réception par Agreely. Ce ne sont ni un indicateur de retard ni une prochaine
échéance. Une clé inconnue, étrangère ou malformée donne le même 404.
Déclarer une purge
POST /v1/retention/rules/{ruleKey}/purges · portée retention ·
Idempotency-Key obligatoire
{
"ranAt": "2026-10-09T03:00:00-04:00",
"recordsAffected": 42,
"method": "destroyed",
"coveredFrom": "2024-01-01",
"coveredUntil": "2024-09-30",
"hostSystem": "crm",
"hostCategory": "dossiers-clients"
}
method vaut destroyed ou anonymized (anonymizationProcessKey requis avec
anonymized, refusé avec destroyed). hostSystem et hostCategory sont des
identifiants courts (minuscules, chiffres, ., _, -) qui nomment un système ou
un type de dossiers, jamais un dossier. references, facultatif, liste au plus
1 000 identifiants de vos propres dossiers (pas plus que recordsAffected); Agreely
n'en garde jamais le texte, seulement une recherche à clé, un sel chiffré et un
engagement salé. Réponse 201 avec la déclaration (status: "declared",
referencesRecorded, warnings), ou 200 pour un rejeu. Une méthode différente de
l'action de la règle est consignée telle que déclarée et signalée dans warnings
(method_differs_from_rule). Une passe qui n'a rien trouvé est une passe, pas une
purge de zéro dossier.
Refus : 404; 409 retry (une nouvelle tentative concurrente n'a pas pu être
réglée : réessayez avec la même clé); 422 idempotency_key_required,
idempotency_key_reused, unknown_field, invalid_method,
anonymization_process_required, invalid_anonymization_process,
invalid_records_affected, ran_at_in_future, invalid_covered_period,
invalid_host_token, host_token_limit.
Déclarer une passe sans purge
POST /v1/retention/rules/{ruleKey}/sweeps · portée retention ·
Idempotency-Key obligatoire
Le battement de cœur : le système hôte déclare avoir passé sous cette règle sans rien
trouver à purger. Sans lui, un hôte qui a cessé de purger et un hôte qui n'a rien à
purger se ressemblent. Corps { "sweptAt": "…", "hostSystem": "crm" }; sweptAt
doit tomber dans les 24 dernières heures. Réponse 201 avec
declaredNothingDue: true : ce que l'hôte a dit, jamais un fait établi par Agreely.
Refus : 404; 422 swept_at_in_future et les refus d'entrée de la purge; 429
sweep_too_frequent.
L'inventaire (inventory)
Déclarer les ensembles de dossiers d'un système
PUT /v1/inventory/categories · portée inventory
Un système hôte déclare la liste complète de ses ensembles de dossiers et le nom de leurs champs. Jamais une valeur : on déclare qu'un ensemble a un champ « Date de naissance », on n'envoie jamais une date de naissance.
{
"hostSystem": "crm",
"categories": [
{
"key": "dossiers-clients",
"label": "Dossiers clients",
"labelEn": "Customer files",
"fields": [
{
"key": "date-naissance",
"label": "Date de naissance"
}
]
}
]
}
Un ensemble qui n'est plus déclaré est retiré (il reste visible, marqué withdrawn).
Une liste vide est refusée plutôt que lue comme « tout retirer ». Réponse 200 :
{ "hostSystem", "withdrawn", "categories" }, chaque ensemble avec ce que votre
organisation a décidé pour lui (decision.state : decided, no_cell ou no_rule) et son
retentionStatement.
Limites : 200 ensembles par déclaration, 60 champs par ensemble, 120 caractères par
libellé d'ensemble, 200 par libellé de champ, un corps de 256 Kio. Au plus 1 000
nouvelles clés d'ensemble par 30 jours glissants (422 new_set_limit; une clé
déjà déclarée est toujours acceptée), et 10 nouveaux systèmes hôtes par 30 jours
(422 host_system_limit). Un libellé qui a la forme d'une valeur (une adresse
courriel, une suite de cinq chiffres, une date) est refusé.
Refus : 413 body_too_large; 422 invalid_request, unknown_field,
value_shaped_label, invalid_key, duplicate_key, limit_exceeded,
empty_declaration, new_set_limit, host_system_limit. Un refus nomme un
chemin (categories[3].fields[0].label), jamais ce qui y a été envoyé.
Ce que ces filtres ne peuvent pas faire
« Inscriptions de Jean-Paul » est un libellé bien formé : aucun filtre ne distingue le nom d'une personne de celui d'une catégorie. Les libellés et les clés ne s'affichent que sur les surfaces de votre propre organisation, où le responsable les lit au registre; ils n'atteignent jamais une autre entreprise, ni une ancre sur la chaîne.
Lire l'inventaire
GET /v1/inventory/categories · portée inventory ou retention · filtre
facultatif hostSystem
Les ensembles déclarés, chacun avec la cellule et la règle auxquelles Agreely l'a rattaché, et son énoncé de conservation.
Lire un énoncé de conservation figé
GET /v1/inventory/statements/{statementKey} · portée inventory, retention ou
check
L'énoncé qu'un système hôte a apposé sur un dossier à la collecte. Il est figé : un dossier recueilli sous « 3 mois » garde une clé qui se résout à « 3 mois » après que la règle soit passée à 2.
{
"categoryKey": "dossiers-clients",
"hostSystem": "crm",
"current": true,
"supersededAt": null,
"key": "8a7b…",
"revision": 2,
"frozenAt": "2026-09-01T12:00:00Z",
"decided": true,
"duration": {
"value": 24,
"unit": "months"
},
"trigger": "purpose_achieved",
"action": "destroy",
"text": {
"fr": "…",
"en": "…"
}
}
Un ensemble sans durée décidée a un énoncé decided: false, avec duration et
text à null : le système hôte s'abstient, il n'invente jamais de chiffre. Une clé inconnue
donne 404.
Ensuite
- La référence
/v1interactive pour les schémas complets. - Enregistrer un consentement hors ligne : le papier, le téléphone, le renouvellement et la feuille signée, pas à pas.
- Les SDK TypeScript, SDK PHP et CLI encapsulent tout cela.