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. Aucune n'est cochée d'avance : chaque portée est un choix délibéré de la personne qui crée la clé, et une portée ajoutée plus tard n'est jamais accordée par défaut.
check: la vérification (/v1/check,/v1/check/batch), la lecture du catalogue et des documents, un énoncé de conservation.issue: les demandes de consentement, la lecture du catalogue et des documents.attest: 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: les consentements donnés par téléphone et leur historique.relationship: la fin d'une relation client et l'annulation d'une fin erronée.registry: l'identité d'un client au registre, sa posture de conservation, ses déclarations de disposition et ses retenues de conservation.retention: les règles de conservation, les cellules du catalogue, les purges et les passes déclarées.inventory: la déclaration des ensembles de dossiers d'un système hôte.withdraw: le retrait consigné au nom de la personne, sur tout consentement qu'elle a donné.holds: le flux des retenues de conservation en place.diffusion: la déclaration de la diffusion d'une politique publiée.
Toute clé valide, quelle que soit sa portée, peut appeler GET /v1/whoami et
GET /v1/policies/current.
Une clé absente, invalide ou révoquée donne 401. Une clé valide dépourvue de la
portée requise donne 403 forbidden. Un espace de travail sans abonnement actif
donne 402 sur toutes les routes, sauf la route de retrait (voir
le retrait consigné). Un
espace de travail suspendu par l'exploitant d'Agreely donne 403
workspace_suspended (code et raison) sur toutes les routes sauf le retrait. Un
espace de travail fermé pour son départ d'Agreely donne 403 forbidden avec la
raison workspace_sealed sur toutes les routes, lectures comprises (le retrait reste
ouvert jusqu'à la destruction), puis la raison workspace_shredding dès que sa
destruction a commencé, retrait compris.
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é.
Politique de confidentialité
GET /v1/policies/current(toute clé) : la politique de confidentialité en vigueur, pour l'organisation ou pour un produit.POST /v1/policies/{policyId}/diffusions(portéediffusion) : déclarer comment une politique publiée a été diffusée.
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, d'inventaire et de diffusion) 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. /v1/check,
/v1/check/batch et POST /v1/consent-requests vont plus loin : un champ qu'elles
attendent, envoyé dans la chaîne de requête et absent du corps, donne 422 avec la
raison fields_in_query. Seuls les filtres documentés des routes GET (status,
customerId, cursor, limit, changedSince, pageToken, locale,
documentCode, hostSystem, type, product) 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; ou espace de travail fermé (raisonsworkspace_sealedetworkspace_shredding).403workspace_suspended: l'espace de travail est suspendu. Toutes les routes, sauf le retrait.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.409retry: une requête concurrente tenait un verrou dont celle-ci avait besoin, et rien n'a été écrit. Renvoyez la même requête (avec la mêmeIdempotency-Key) après le délai deRetry-After.410gone(raisonreplay_erased) : la demande de consentement qu'uneIdempotency-Keya créée a été effacée depuis; envoyez une nouvelle clé.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).503unavailable: le décompte de la limite de débit n'a pas pu être fait à temps; rien n'a été fait. Renvoyez la même requête après le délai deRetry-After.
Une erreur interne répond 500 avec le code internal_error.
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). - Diffusions de la politique déclarées par l'API : 50 (
diffusion_daily_cap, raisondaily_cap).
Ces plafonds valent pour toutes les organisations et ne se règlent pas pour un
seul client. Trois d'entre eux (consentements verbaux, retraits, levées) sont
fixés par la configuration de la plateforme : les changer les change pour tous
les clients à la fois. Les deux autres (retenues placées, diffusions) sont fixes.
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 sans espace, 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é et le même corps rejoue le201d'origine octet pour octet : ni double émission, ni double courriel. La même clé avec un autre corps donne422idempotency_key_reused.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 avec le même corps répond409already_mintedet ne produit rien. La même clé avec un autre corps donne422idempotency_key_reused.POST /v1/policies/{policyId}/diffusions: la clé est obligatoire (422idempotency_key_required). Même clé et même corps : le201d'origine est rejoué. Même clé et autre corps :422idempotency_key_reused.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.",
"slug": "acme",
"sector": "private",
"statute": "P-39.1",
"publicPolicyUrl": "https://app.agreely.ca/p/acme/privacy"
}
}
slug est l'identifiant public de l'organisation, celui qui figure déjà dans
l'adresse de ses pages publiques. 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 interne 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. 12 al. 1; A-2.1, art. 65.1 al. 1 : l'usage se limite aux fins de la collecte). 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
}
],
"products": [
{
"productKey": "boutique",
"name": "Boutique en ligne"
}
]
}
legalBasis est le motif que l'entreprise déclare pour la cellule; consent
signifie qu'elle exige un consentement. products liste les produits actifs de
l'organisation; leur productKey est celui qu'attend GET /v1/policies/current. 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"
}
}
Refus (422 invalid_request, avec field) : customerId, recipientEmail ou
validUntil absent ou invalide, ni ou les deux de consentDocumentId et
documentCode, un code sans document publié. Un validUntil qui n'est pas dans le
futur ou qui dépasse 10 ans (une règle de produit d'Agreely) est refusé sans field. Un document qui ne
peut pas encore être émis porte une raison : document_placeholder (le document
contient encore un texte d'exemple) ou document_refusal_consequences_missing (il
ne dit pas les conséquences d'un refus). Une organisation dont le domaine n'est pas
vérifié ne peut pas émettre de demande.
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, revoked_before_action ou erased. 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. erased désigne une demande approuvée dont le
consentement a été entièrement effacé depuis.
{
"requests": [
{
"requestId": "0x4b08…1234",
"status": "pending",
"validUntil": "2027-10-09T03:59:59.000000Z",
"expiresAt": "2026-11-08T14:05:12.000000Z",
"createdAt": "2026-10-09T14:05:12.000000Z",
"settledAt": null,
"customerId": "cust_8812",
"documentCode": "formulaire-accueil",
"items": [
{
"category": "Photo",
"purpose": "Publication"
}
],
"document": {
"code": "formulaire-accueil",
"name": "Formulaire d'accueil",
"version": "3"
}
}
],
"nextCursor": null
}
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.isMinor(booléen JSON) ouageBracket(under_14ou14_to_17) déclarent un mineur. Pour un mineur de moins de 14 ans, le consentement est donné par le titulaire de l'autorité parentale ou le tuteur :capacity.consentedByvautrepresentativeetcapacity.representativeCapacityvauttitulaire_autorite_parentaleoututeur_mineur(sinonminor_needs_guardian).guardianName(1 à 128 caractères) peut nommer ce représentant, pour un mineur de moins de 14 ans seulement.capacity(facultatif) décrit qui a consenti :consentedBy(self,self_with_assistantourepresentative), et pour un représentant ou une personne assistéerepresentativeCapacity(tutelle,mandat_protection_homologue,representation_temporaire,curatelle,other,undeclared, ou les deux valeurs d'un mineur),verificationOutcome(verified,inconclusive,mismatchouunverified, requis dans ces deux cas),verificationMethod(representation_register,assistants_register,document_shownouother),verifiedOn,identifierSeen,identifierKind(measure_numberoucourt_file),reviewOn,assistanceRequested,assistanceProvided,assistanceProvidedOnetaskedPersonFirst. Un élément deitemspeut porter son propreconsentedBy. Agreely consigne la déclaration et la clé qui l'a envoyée; il ne vérifie ni la mesure ni l'identité du représentant.- Tout autre membre, dans le corps, dans
evidenceou dans un élément deitems, donne422unknown_field.
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,
"consentedOnBehalf": 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.
consentedOnBehalf vaut true quand le consentement a été donné au nom de la
personne (un représentant, un mineur) : le lien de réclamation encore actif pour ce
client a alors été retiré, et POST /v1/manual-consents/claim-links refuse d'en
émettre un nouveau (409 consented_on_behalf).
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, document_refusal_consequences_missing,
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, capacity_method_minor_refused,
capacity_identifier_invalid, capacity_date_invalid, capacity_item_conflict,
minor_needs_guardian, age_bracket_required, age_bracket_adult_measure,
age_bracket_not_recorded, age_bracket_conflict, no_verified_domain, et
unknown_field (code et raison).
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.409closure_in_progress(codeconflict) : la personne a demandé la fermeture de son compte auprès de l'organisation : 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.422idempotency_key_reused(code et raison) : cetteIdempotency-Keya déjà servi avec un autre corps.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, consented_on_behalf (un consentement sur papier non
rattaché a été donné par un représentant ou concerne un mineur), claim_unavailable
(le dossier ne peut être rattaché à aucun compte citoyen par un lien) ou
closure_in_progress (la personne a demandé la fermeture de son compte); 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) et otherConsents ("keep" ou "withdraw", voir plus bas). 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é).
Cette route ne met jamais fin à un consentement que la personne a signé en ligne
avec sa clé d'accès. Quand un tel consentement est en vigueur pour la finalité, la
requête est refusée (citizen_consent_at_gate) à moins de déclarer
"otherConsents": "keep" : seul cet enregistrement (et sa chaîne) prend fin, le
consentement signé en ligne reste en vigueur, et la réponse ajoute
"stillInForce": true. "withdraw" équivaut à l'absence du champ.
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, et ce consentement est en vigueur. Pour consigner le retrait qu'elle a demandé, utilisez la route de retrait, qui retire les deux au même instant; pour ne mettre fin qu'à cet enregistrement, envoyez"otherConsents": "keep". Rien n'est écrit.422invalid_field:reasontrop long ou invalide, ouotherConsentsautre que"keep"ou"withdraw"(ou envoyé dans la chaîne de requête).
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 et otherConsents, comme pour le retrait. 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, ou envoyez "otherConsents": "keep"). L'effacement
consulte aussi les retenues de conservation :
409hold_active: une retenue active couvre les renseignements de ce consentement. Rien n'est effacé ni retiré. Levez la retenue, avec un motif, quand la loi n'exige plus de conserver ces renseignements, puis effacez.409customer_unresolved: le client et la finalité de ce consentement ne peuvent pas être lus, si bien qu'aucune retenue ne peut être vérifiée. Rien n'est effacé; communiquez avec le soutien d'Agreely en donnant la référence du consentement.
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: trueouageBracket: "under_14"), le représentant doit être le titulaire de l'autorité parentale ou le tuteur, nommé dansrespondent.name.ageBracket: "14_to_17"déclare un mineur de 14 ans ou plus pour qui ce représentant a répondu;respondent.nameest alors refusé.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, age_bracket_required,
age_bracket_adult_measure, age_bracket_not_recorded, age_bracket_conflict,
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-, en commençant par une lettre ou un chiffre). 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é.otherConsents("withdraw"ou"keep") oualsoWithdraw(une liste d'au plus 50consentRef) : la portée du retrait quand un autre consentement de la même finalité est en vigueur pour ce client. Deux personnes peuvent chacune en détenir un, alors Agreely ne devine pas : sans l'un ni l'autre, la requête est refusée (other_consents_running) avec la liste de ces consentements."withdraw"retire tous les autres consentements en vigueur donnés au plus tard aurequestedAtdéclaré,"keep"ne retire que celui-ci, etalsoWithdrawnomme ceux que la demande de la personne couvre aussi. Les deux ne s'envoient jamais ensemble.
Réponse 200 :
{
"consentRef": "0x9f2c…0d2e",
"withdrawn": true,
"alreadyWithdrawn": false,
"recordedOnBehalf": true,
"assurance": "company_documented",
"gate": "denied",
"alsoWithdrawn": [
"0x…"
],
"stillInForce": []
}
recordedOnBehalf: trueetassurance: "company_documented"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é, que l'acte n'a pas atteint, 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, etalsoWithdrawnporte aussi ceux queotherConsentsoualsoWithdrawa visés.stillInForceliste les autres consentements de la finalité encore en vigueur après l'acte, que celui-ci n'a pas atteints.
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.422other_consents_running(codeinvalid_request,fieldotherConsents) : un autre consentement de la finalité est en vigueur et la portée n'est pas déclarée. La réponse liste leurs références danserror.otherConsentRefs. Rien n'est écrit.422not_running_for_purpose(fieldalsoWithdraw) : une référence dealsoWithdrawn'est pas un autre consentement en vigueur pour le même client et la même finalité.422invalid_channel,invalid_operator,requested_at_invalid,requested_at_in_future,requested_at_before_grant,invalid_reason,invalid_field(otherConsentsetalsoWithdrawenvoyés ensemble, ou de forme invalide) (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. 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]",
"noticeLocale": "fr"
}
Le registre ne consigne aucun motif par client : la finalité et le motif
appartiennent au traitement, déclarés cellule par cellule au catalogue. legalBasis
et basisNote ne sont plus des membres de cette route : un corps qui les envoie
encore (un ancien SDK ou une ancienne CLI) est accepté et ils sont ignorés. 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, noticeLocale autre que
fr ou en, un champ qui n'est ni une chaîne ni null); 409 identity_erased
(une déclaration de destruction ou d'anonymisation a retiré l'identité : un nom, un
courriel 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 trois 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, "noticeLocale": "fr",
"createdAt": "2026-10-01T14:05:12.000000Z", "updatedAt": "2026-10-08T13:15:00.000000Z",
"relationship": { "status": "active", "endedAt": null, "endedRecordedAt": null } }
hasEmail: true, jamais l'adresse. registered vaut false pour une référence
connue seulement par son historique de consentement. source dit par où l'identité
est entrée en premier : manual, import, api ou ceremony. 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
(conservé jusqu'à 2 000 caractères) et endedAt facultatif, un instant passé RFC 3339
avec décalage horaire pour un système qui déclare la fin en retard (absent, la fin
est datée de l'appel). 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:05.000000Z",
"endedRecordedAt": "2026-06-26T15:04:05.000000Z",
"endedBy": "company"
}
endedRecordedAt est l'instant où Agreely a consigné la fin; il est postérieur à
endedAt seulement pour une fin déclarée dans le passé.
Refus : 404 (référence inconnue ou étrangère, rien n'est écrit); 422
invalid_request (reason absent ou vide, le message cite la loi de
l'organisation; ou endedAt illisible), ended_at_in_future, ended_at_too_old
(plus de 60 mois dans le passé, une règle de produit d'Agreely) ou
ended_at_before_relationship (antérieur au premier enregistrement qu'Agreely
détient pour ce client, à sa dernière demande, à son dernier consentement consigné
ou à sa dernière réclamation).
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:00.000000Z",
"endedRecordedAt": "2026-03-31T20:00:00.000000Z"
},
"clock": {
"trigger": "purpose_achieved",
"recipe": "For an unnamed purpose-achieved event only: dueAt = the tenant-local date of endedAt plus duration in its entered unit",
"dueAt": null,
"reason": "hold_active",
"rules": [
{
"ruleId": "b7e0…",
"label": "Dossiers clients",
"labelEn": "Customer files",
"periodMonths": 24,
"duration": {
"value": 24,
"unit": "months"
},
"eventLabel": null,
"eventLabelEn": null,
"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.rulesliste les règles déclenchées par l'atteinte des fins.durationdonne la durée dans l'unité saisie (days,monthsouyears);periodMonthsne la répète que pour une durée en mois, et vautnullsinon.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. Une règle dont l'organisation a nommé l'événement de départ (eventLabel) n'a pas dedueAt: Agreely ne connaît pas la date de cet événement.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": null,
"hasBasisNote": false,
"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 et la langue d'avis que le registre d'Agreely détient pour ce client (et la note de motif qu'un enregistrement ancien portait encore). La référence, 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.
basis et hasBasisNote relisent le motif et la note figés sur une déclaration
faite quand le registre consignait encore un motif par client; ils valent null et
false sur toute déclaration plus récente. 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",
"fiscalYearEnd": null,
"eventLabel": null,
"eventLabelEn": null,
"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. duration.unit vaut days,
months ou years. trigger vaut collection, purpose_achieved,
last_activity, decision ou fiscal_year_end; pour ce dernier, fiscalYearEnd
donne la fin d'exercice de l'organisation (MM-JJ), null pour tout autre
déclencheur. eventLabel et eventLabelEn sont le nom que l'organisation donne à
l'événement de départ, ou null. 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 : 409 host_retired (l'organisation a consigné dans Agreely le retrait de
ce hostSystem : ses déclarations d'inventaire sont refusées jusqu'à ce que ce
retrait soit levé, mais ses purges et ses passes sont encore acceptées); 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",
"eventLabel": null,
"eventLabelEn": null,
"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.
La politique de confidentialité (policies, diffusion)
Lire la politique en vigueur
GET /v1/policies/current?type=privacy · toute clé valide · filtre facultatif
product
La politique de confidentialité en vigueur, pour que votre système cite et lie
la version que les personnes peuvent lire aujourd'hui plutôt qu'une copie qu'il
conserve. type est requis et vaut privacy (422 sinon). product est le
productKey d'un produit actif (voir products dans GET /v1/catalog); sans lui,
la réponse porte sur la politique de l'organisation.
{
"policyId": "9d41…",
"version": "3",
"effectiveDate": "2026-09-01",
"publishedAt": "2026-08-28T15:00:00.000000Z",
"declarationHash": "0x…",
"publicUrl": "https://app.agreely.ca/p/acme/privacy",
"title": {
"fr": "Politique de confidentialité",
"en": "Privacy policy"
},
"crossBorder": false
}
declarationHashest l'empreinte consignée quand la version a été scellée, relue telle quelle et jamais recalculée;nullpour une version publiée avant le scellement.crossBorderdit si le registre des transferts déclare aujourd'hui une communication hors du Québec pour cette portée. Il est lu en direct : le texte publié est figé et peut précéder un changement du registre.publicUrlest l'adresse publique de la politique (suivie de la clé du produit pour un produit),nullsi l'organisation n'a pas d'adresse publique.
Refus : 404 not_found (aucun produit actif ne porte ce productKey, ou aucune
politique de confidentialité n'est en vigueur pour cette portée); 422
invalid_request (type absent ou autre que privacy).
Déclarer la diffusion d'une politique
POST /v1/policies/{policyId}/diffusions · portée diffusion · Idempotency-Key
obligatoire
Consigne comment l'organisation a fait connaître une version publiée de sa
politique : le jumeau /v1 du formulaire « Consigner une diffusion » de la
politique. Le registre des diffusions est en ajout seulement : une déclaration ne
se retire pas, d'où la clé d'idempotence obligatoire. La déclaration est attribuée à
la clé d'API, jamais à une personne.
{
"channel": "own_email",
"note": "Courriel envoyé à la liste des clients actifs le 2 septembre.",
"declaredReach": 1240
}
channel(requis) :site_notice,in_service,own_email,sms,postal,point_of_service,public_noticeouother.note(requise) : de 10 à 2 000 caractères, qui décrivent comment la politique a été diffusée.declaredReach(facultatif) : le nombre de personnes jointes, tel que déclaré (un entier de 1 à 1 000 000 000), ounull. Agreely le consigne, il ne le vérifie pas.
Réponse 201 (rejouée telle quelle pour la même clé et le même corps) :
{
"diffusionId": "51c2…",
"policyId": "9d41…",
"firstPublication": false,
"channel": "own_email",
"declaredReach": 1240
}
firstPublication est déduit par Agreely, comme dans le formulaire : true quand
la version est la première politique de ce type que l'organisation a publiée, false
quand la diffusion annonce une modification. Il n'est jamais lu dans le corps.
Refus : 404 not_found (identifiant inconnu, malformé ou d'une autre
organisation); 409 policy_not_in_force (code et raison : la version est un
brouillon ou n'est plus publiée); 422 idempotency_key_required,
idempotency_key_reused, unknown_field (tout autre membre) ou invalid_request
(channel, note ou declaredReach invalide, raisons missing_field et
invalid_field); 429 diffusion_daily_cap (raison daily_cap, sans
Retry-After) au-delà de 50 diffusions déclarées par l'API par 24 heures glissantes.
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.