JSON reference
This section documents the on-the-wire artifacts field by field. Every example is the real shape Agreely's code builds, not an illustration. Read alongside the protocol for the cryptography behind each field.
- The consent request (this page): the signed offer a company issues.
- The consent receipt: the Verifiable Credential the citizen receives.
- DID documents: company and citizen.
- Verify a receipt: the four checks an auditor runs.
The consent request (the signed offer)
When a company issues a consent request, Agreely builds a number-free offer,
signs its JCS-canonical bytes with the company key, and wraps the signature as a
companySig Data Integrity proof. No value is a number (only strings, booleans and
objects), so the canonical bytes a verifier re-derives can never diverge.
The company's customer reference is not in the signed offer: it would follow every receipt and every verification bundle, and let them be correlated across organizations. Agreely adds it only to the encrypted copy it keeps (see at rest).
{
"requestId": "0x4b08245219c0e1a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081234",
"issuer": "did:web:app.agreely.ca:c:acme",
"consent": {
"action": "grant",
"items": [
{
"itemId": "0x9a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081",
"category": "phone number",
"purpose": "billing",
"sensitive": false,
"automatedDecision": false,
"legalBasis": "consent"
}
]
},
"nonce": "0x7081234b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f",
"validUntil": "2031-01-01T00:00:00Z",
"createdAt": "2026-06-26T15:00:00Z",
"expiresAt": "2026-07-26T15:00:00Z",
"document": {
"code": "acme-billing-v3",
"name": "Billing consent v3",
"version": "3",
"effectiveDate": "2026-01-01"
},
"disclosure": {
"purpose": { "fr": "…", "en": "…" },
"categories": { "fr": "…", "en": "…" },
"retention": { "fr": "…", "en": "…" },
"withdrawal": { "fr": "…", "en": "…" },
"recipients": { "fr": "…", "en": "…" },
"rights": { "fr": "…", "en": "…" },
"responsable": { "name": "…", "contact": "…" }
},
"disclosureVersion": "1",
"disclosureHash": "0x…sha256…",
"companySig": {
"type": "DataIntegrityProof",
"cryptosuite": "eddsa-jcs-2022",
"created": "2026-06-26T15:00:00Z",
"verificationMethod": "did:web:app.agreely.ca:c:acme#kms-1",
"proofPurpose": "assertionMethod",
"proofValue": "z3Ge8wQ5...base58btc..."
}
}
Field by field
| Field | Type | Meaning |
|---|---|---|
requestId |
0x + 64 hex |
The protocol handle: 32 fresh random bytes, never derived. The public id everywhere, never an internal uuid. |
issuer |
string | The company DID, did:web:app.agreely.ca:c:{slug}. |
consent.action |
string | grant. |
consent.items[].itemId |
0x + 64 hex |
A fresh random opaque handle for this item. |
consent.items[].category |
string | The normalized category key (lowercased canonical), not the display label. |
consent.items[].purpose |
string | The normalized purpose key. |
consent.items[].sensitive |
boolean | The cell is declared sensitive (its consent must then be express). |
consent.items[].automatedDecision |
boolean | The cell feeds a decision based exclusively on automated processing. |
consent.items[].legalBasis |
string | The ground the company declares for the cell; consent for a consent ask. |
nonce |
0x + 64 hex |
Single-use random nonce, bound into the approval challenge so the answer cannot be replayed. |
validUntil |
RFC 3339 | The consent lifespan if approved. |
createdAt |
RFC 3339 | When the offer was minted. |
expiresAt |
RFC 3339 | The answer deadline (30 days). A pending request past this flips to expired. |
document |
object | The consent document descriptor: code, name, nameEn when the document has an English name, version, effectiveDate, and ipfsCid only when the version is pinned to IPFS. |
disclosure |
object | The bilingual information the person was given, section by section ({fr, en} per section; responsable is {name, contact}). The means, withdrawal, refusalConsequences, crossBorder, automatedDecision and profiling sections appear only when the document carries them. |
disclosureVersion |
string | "1": disclosureHash is over disclosure alone. "2": at least one cell rests on a ground other than consent, and the hash is over {disclosure, legalBasis}. |
disclosureHash |
0x + 64 hex |
The SHA-256 of the JCS-canonical preimage disclosureVersion names. |
legalBasis |
object | Present only in version "2": for each itemId of a cell that does not rest on consent, {basis, justificationFr, justificationEn?}. |
minor |
object | Present only when the person is a minor under 14: {isMinorUnder14: true, parentalConsentObtained}. The guardian's name never enters the signed bytes. |
companySig |
object | The eddsa-jcs-2022 Data Integrity proof over the offer. |
What companySig signs
The signature covers the offer object before companySig is added, that is: requestId, issuer, consent, nonce, validUntil, createdAt, expiresAt, document, disclosure, disclosureVersion, disclosureHash, and legalBasis and minor when present. To verify it, drop companySig (and the at-rest subject, status and result fields), JCS-canonicalize what remains, and check the Ed25519 signature against the company key.
At rest
The object Agreely stores (encrypted under the tenant DEK) is the offer, its
companySig, and three envelope fields that are not covered by companySig:
subject (the company's own customer reference, ref, and status, known or
unonboarded depending on whether the recipient already has a citizen DID),
status and result:
{
"...": "all offer fields above",
"companySig": { "...": "the proof" },
"subject": { "ref": "cust_8812", "status": "known" },
"status": "pending",
"result": { "receipts": null, "settledAt": null }
}
status advances to approved, refused, expired, or
revoked_before_action; result is populated when the request settles.
The SDK-facing shapes
The /v1 API returns trimmed views of a request rather than the raw signed
object. The issuance response (IssuedRequest) and the list/show view
(ConsentRequest) are documented in the
API reference and the
interactive spec.