Consent Docs

Consent

The consent record object and the endpoints that record, read, change and withdraw one record, with parameters, examples and errors.

Reference12 min readFor developers calling the REST API

These endpoints record, read, change and withdraw one consent record. Paths are relative to https://console.dpconsent.com/consent/sdk/v1. The tasks are explained in Record consent.

Every endpoint on this page returns the record in this shape.

JSON
{
  "consentId": "cns_3f1c9a7e2b8d4c6fa0e5d1b7c9f2a4e8",
  "status": "PARTIAL",
  "formId": "0b9c6f0e-6a55-4c1e-9d0a-2f6f0f4f7a11",
  "formVersion": 3,
  "formContentHash": "9b1f04c7…",
  "decisions": [
    {
      "purposeKey": "course-updates-by-email",
      "purposeName": "Course updates by email",
      "category": "MARKETING",
      "granted": true
    },
    {
      "purposeKey": "class-reminders-by-sms",
      "purposeName": "Class reminders by SMS",
      "category": "MARKETING",
      "granted": false
    }
  ],
  "countryCode": "IN",
  "subdivisionCode": null,
  "lawCode": "DPDP",
  "collectedOn": "2026-09-24T10:12:03.000Z",
  "expiresOn": "2027-09-24T10:12:03.000Z",
  "withdrawnOn": null,
  "proofHash": "5d1f6e0a…",
  "test": true
}
Field Type Description
consentId string The record's id. Store it next to your user.
status string GRANTED, PARTIAL, DENIED, WITHDRAWN, EXPIRED or PENDING. See Status values.
formId, formVersion, formContentHash UUID, integer, string The form version the person answered.
decisions array One entry for each purpose: purposeKey, purposeName, category and granted.
decisions[].partners object Partner key to allowed. Present only when the purpose is allowed and at least one of its partners was refused.
countryCode, subdivisionCode string or null Where the person was.
lawCode string or null The law of the region rule that matched the person's country, such as DPDP.
collectedOn, expiresOn time When the current answer was given and when it ends.
withdrawnOn time or null When the record was withdrawn.
proofHash string The seal of the record's current state. See How consent is proven.
test boolean true when the record was written with a test key.
metadata object Your own fields. Returned only by GET /consent/{consentId} to a secret key.
basis string PRE_COMMENCEMENT, when the answer was brought in with collectedAt and you stated that basis. Present only when set.
noticeReference, noticeSha256 string The notice the person saw, for consent brought in from earlier. Present only when set.
ageDeclaration object The person's answer to "Are you 18 or older?": eighteenOrOver, and parentalConsentUrl when the answer was No and the form names a page for a parent or guardian. Sealed. Present only when set.
captureContext object How the current state was taken, when the write that made it sent capture. Returned to a secret key only. See Capture fields.

The record never contains the identifier it was recorded under.

Status values#

Status Meaning
GRANTED Every optional purpose is allowed.
PARTIAL Some optional purposes are allowed.
DENIED No optional purpose is allowed.
WITHDRAWN The record was withdrawn. Nothing optional is allowed.
EXPIRED The record passed expiresOn. Nothing optional is allowed.
PENDING An email opt-in is waiting for confirmation. Nothing optional is allowed.

Endpoints#

POST /consent#

Records a person's answers to a form. The first answer creates a record, and a later answer from the same person on the same form updates it.

Key Scope Idempotency-Key Webhook sent
Publishable or secret consent:write for a secret key Accepted consent.granted for a new record, consent.updated after that

Parameters

Name Type Required Description
formKey string, up to 60 One of formKey, formId The form's key.
formId UUID One of formKey, formId The form's id.
formVersion integer, 1 or more No The version the person was shown. Left out, or higher than the published version, the published version is used.
subjectId string, up to 255 Yes The identifier to record under. Only a keyed hash is stored. See Identifiers.
subjectType string No COOKIE_ID (default), SESSION_ID, EMAIL, USER_ID, PHONE or DEVICE_ID.
decisions array, 1 to 100 Yes One entry for every purpose on the form that is not always on.
decisions[].purposeKey string, up to 60 Yes The purpose's key.
decisions[].granted boolean Yes The answer.
decisions[].partners object No Partner key to boolean, on forms that offer choices per partner. See Partner choices.
countryCode 2 letters No Where the person is. Without it the API locates the caller.
subdivisionCode 1 to 3 letters or digits No State or province.
locale string, up to 10 No The language the form was shown in.
pageUrl string, up to 500 No The page or screen the answer was given on.
interaction string No What the person did. See Interaction values.
identifiers array, up to 5 No Other identifiers of the same person. Each has subjectType, subjectId and, from a publishable key, proof. Entries must not repeat.
identityProof 64 hex characters With a publishable key and EMAIL, USER_ID or PHONE The proof for subjectType and subjectId. See Signed-in visitors.
metadata object No Your own fields, up to 8,192 bytes of JSON.
collectedAt ISO 8601 time No Secret key only. When the consent was given, for consent brought in from earlier. Not in the future and not before 1990. See Earlier consent.
basis string No Secret key only, with collectedAt. PRE_COMMENCEMENT: your statement that the person consented before the DPDP Act commenced.
noticeReference string, up to 120 No Secret key only, with collectedAt. The name or number of the notice the person saw: letters, digits, spaces and . _ : / # ( ) -.
noticeSha256 64 hex characters No Secret key only, with collectedAt. The SHA-256 of that notice.
expiresAt ISO 8601 time No Secret key only, with collectedAt. When the consent ends: after the call and within 3,650 days of it. Without it, the form's term counts from the call.
lastActivityAt ISO 8601 time No Secret key only, with collectedAt. When the person last used your service, between collectedAt and now.
capture object No Secret key only. How consent was taken in a branch, by phone or on paper. Sealed with this write only. See Capture fields.
dataPrincipalType string No Secret key only. CHILD or PERSON_WITH_DISABILITY.
ageDeclaration object No { "eighteenOrOver": boolean }: the person's own answer to "Are you 18 or older?". false records the person as a child, with the page for a parent or guardian that the form version names. The browser runtime sends it when the form asks.
guardian object With dataPrincipalType, in most cases Secret key only. See Guardian fields.

Request

cURLShell
curl -X POST https://console.dpconsent.com/consent/sdk/v1/consent \
  -H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-usr_8f2K1xQz" \
  -d '{
    "formKey": "kalpa-account",
    "subjectId": "usr_8f2K1xQz",
    "subjectType": "USER_ID",
    "countryCode": "IN",
    "interaction": "SAVE_PREFERENCES",
    "pageUrl": "https://www.example.in/signup",
    "decisions": [
      { "purposeKey": "course-updates-by-email", "granted": true },
      { "purposeKey": "class-reminders-by-sms", "granted": false }
    ],
    "identifiers": [
      { "subjectType": "EMAIL", "subjectId": "kavya.menon@example.in" }
    ]
  }'
Node.jsJavaScript
const record = await client.recordConsent({
  formKey: 'kalpa-account',
  subjectId: 'usr_8f2K1xQz',
  subjectType: 'USER_ID',
  countryCode: 'IN',
  interaction: 'SAVE_PREFERENCES',
  pageUrl: 'https://www.example.in/signup',
  decisions: [
    { purposeKey: 'course-updates-by-email', granted: true },
    { purposeKey: 'class-reminders-by-sms', granted: false },
  ],
  identifiers: [
    { subjectType: 'EMAIL', subjectId: 'kavya.menon@example.in' },
  ],
  idempotencyKey: 'signup-usr_8f2K1xQz',
});
Python
from piedap_consent import Decision, Identifier

consent = client.record_consent(
    form_key="kalpa-account",
    subject_id="usr_8f2K1xQz",
    subject_type="USER_ID",
    country_code="IN",
    interaction="SAVE_PREFERENCES",
    page_url="https://www.example.in/signup",
    decisions=[
        Decision("course-updates-by-email", True),
        Decision("class-reminders-by-sms", False),
    ],
    identifiers=[Identifier("EMAIL", "kavya.menon@example.in")],
    idempotency_key="signup-usr_8f2K1xQz",
)

Response

201 with the consent record when a record was created. 200 when an existing record was updated, with the same consentId. A secret key also gets captureContext when the write sent capture.

A write with collectedAt that the records already overrule answers 409, writes nothing and sends no webhook. errorData.skipped holds the code, errorData.consentId the record that overrules it, and errorData.current its status, collectedOn and withdrawnOn. See Earlier consent.

Errors

Status Code When
400 INVALID_PURPOSE_DECISIONS A purpose was left unanswered, or an answer names a purpose the form does not offer.
400 INVALID_PARTNER_DECISIONS A partners answer names a partner the version does not list, or the version offers no choices per partner.
400 METADATA_TOO_LARGE metadata is over 8,192 bytes of JSON.
400 INVALID_COLLECTED_AT collectedAt is in the future or before 1990.
400 IMPORT_FIELDS_NEED_COLLECTED_AT basis, noticeReference, noticeSha256, expiresAt or lastActivityAt was sent without collectedAt. errorData.fields lists them.
400 INVALID_EXPIRES_AT expiresAt is not after the call, or is more than 3,650 days after it.
400 INVALID_LAST_ACTIVITY_AT lastActivityAt is before collectedAt or in the future.
400 PHONE_NOT_E164 With collectedAt, a PHONE subject or identifier is not + and 7 to 15 digits. errorData.field names it.
400 CAPTURE_REFERENCE_REQUIRED The capture method needs a reference that was not sent. errorData.needs lists the fields that would do.
400 CAPTURE_REFERENCE_LOOKS_PERSONAL capture.methodReference looks like a phone number.
400 CONSENT_FORM_NOT_PUBLISHED The form has no published version.
400 AMBIGUOUS_FORM_KEY The formKey exists on more than one property.
400 FORM_NOT_ON_PROPERTY The form belongs to a different property than the site or key sending the answer.
400 EMAIL_NEEDS_DOUBLE_OPT_IN A publishable key recorded an EMAIL subject on a form that requires double opt-in.
400 GUARDIAN_CONSENT_REQUIRED, GUARDIANSHIP_ORDER_REQUIRED dataPrincipalType was sent without a guardian verified in the way the rules ask.
403 IDENTITY_PROOF_REQUIRED A publishable key sent an EMAIL, USER_ID or PHONE subject without a valid identityProof.
403 IDENTIFIER_PROOF_REQUIRED A publishable key sent a linked EMAIL, PHONE, USER_ID or DEVICE_ID without a valid proof.
403 GUARDIAN_CONSENT_SERVER_ONLY A publishable key sent dataPrincipalType or guardian.
403 IMPORT_FIELDS_SERVER_ONLY A publishable key sent basis, noticeReference, noticeSha256, expiresAt or lastActivityAt.
403 CAPTURE_SERVER_ONLY A publishable key sent capture.
403 PROPERTY_NOT_FOR_KEY The key is tied to a property that is no longer active.
404 CONSENT_FORM_NOT_FOUND No form matches formKey or formId.
409 CONSENT_WITHDRAWN_ON_RECORD, CONSENT_ALREADY_ON_RECORD, CONSENT_NEWER_ON_RECORD, CONSENT_ERASED_ON_RECORD, CONSENT_HELD_UNDER_OTHER_IDENTIFIER A write with collectedAt was skipped. See Earlier consent. Not worth retrying.
429 CHECK_BUDGET_EXCEEDED A write with collectedAt from a key that holds consent:check used up that key's check budget. Each identifier the write names counts.

Also the errors every call can return.

Returns the record as it stands now.

Key Scope Idempotency-Key Webhook sent
Publishable or secret consent:read for a secret key Ignored None

Parameters

Name Type Required Description
consentId string Yes Path parameter. The record's id.

Request

cURLShell
curl https://console.dpconsent.com/consent/sdk/v1/consent/$CONSENT_ID \
  -H "Authorization: Bearer $PIEDAP_SECRET_KEY"
Node.jsJavaScript
const record = await client.getConsent(consentId);
Python
consent = client.get_consent(consent_id)

Response

200 with the consent record. A secret key also gets metadata, and captureContext when the current state carries one. The SDK calls return null or None for a 404.

Errors

Status Code When
404 None. The message is No live consent for this id No record has this id, the record's expiresOn has passed, or the key is tied to a different property.

Also the errors every call can return.

Records a change of mind on an existing record. Purposes you leave out keep their answer.

Key Scope Idempotency-Key Webhook sent
Publishable or secret consent:write for a secret key Accepted consent.updated

Parameters

Name Type Required Description
consentId string Yes Path parameter. The record's id.
decisions array, 1 to 100 Yes The answers that changed, as in POST /consent.
pageUrl string, up to 500 No Where the change was made.
interaction string No What the person did. See Interaction values.
metadata object No Replaces your fields on the record. Up to 8,192 bytes of JSON.
identifiers array, up to 5 No More identifiers to link to this record.
capture object No Secret key only. How this change was taken. A change without it clears the record's capture context; the earlier event keeps it. See Capture fields.

Request

cURLShell
curl -X PATCH https://console.dpconsent.com/consent/sdk/v1/consent/$CONSENT_ID \
  -H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "decisions": [
      { "purposeKey": "class-reminders-by-sms", "granted": true }
    ],
    "interaction": "PREFERENCE_CENTRE",
    "pageUrl": "https://www.example.in/account/privacy"
  }'
Node.jsJavaScript
const record = await client.updateConsent(
  consentId,
  [{ purposeKey: 'class-reminders-by-sms', granted: true }],
  {
    interaction: 'PREFERENCE_CENTRE',
    pageUrl: 'https://www.example.in/account/privacy',
  },
);
Python
from piedap_consent import Decision

consent = client.update_consent(
    consent_id,
    [Decision("class-reminders-by-sms", True)],
    interaction="PREFERENCE_CENTRE",
    page_url="https://www.example.in/account/privacy",
)

Response

200 with the updated consent record. collectedOn and expiresOn are counted again from now. Updating a withdrawn record puts it back in force. A change clears basis, noticeReference and noticeSha256.

Errors

Status Code When
400 CONSENT_RECORD_ERASED The record has been erased and can no longer change.
400 INVALID_PURPOSE_DECISIONS, INVALID_PARTNER_DECISIONS As for POST /consent.
400 METADATA_TOO_LARGE As for POST /consent.
400 GUARDIAN_CONSENT_REQUIRED A secret key added a grant to a record kept for a child or a person with a guardian. Record new grants with POST /consent and the verified guardian.
400 CAPTURE_REFERENCE_REQUIRED, CAPTURE_REFERENCE_LOOKS_PERSONAL As for POST /consent.
403 IDENTIFIER_PROOF_REQUIRED As for POST /consent.
403 CAPTURE_SERVER_ONLY A publishable key sent capture.
404 CONSENT_RECORD_NOT_FOUND No record with this id exists for a key of this kind, test or live.
404 None. The message is No live consent for this id The key is tied to a different property.

Also the errors every call can return.

POST /consent/{consentId}/withdraw#

Withdraws one record: every optional purpose becomes refused, the status becomes WITHDRAWN and withdrawnOn is set.

Key Scope Idempotency-Key Webhook sent
Publishable or secret consent:write for a secret key Accepted consent.withdrawn

Parameters

Name Type Required Description
consentId string Yes Path parameter. The record's id.
reason string, up to 500 No Kept on the withdrawal event.

Request

cURLShell
curl -X POST https://console.dpconsent.com/consent/sdk/v1/consent/$CONSENT_ID/withdraw \
  -H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Asked by phone on the support line" }'
Node.jsJavaScript
await client.withdrawConsent(
  consentId,
  'Asked by phone on the support line',
);
Python
client.withdraw_consent(
    consent_id,
    reason="Asked by phone on the support line",
)

Response

200 with the consent record. Strictly necessary purposes stay on. Withdrawing a record that is already withdrawn also answers 200, changes nothing and sends no webhook.

Errors

Status Code When
404 CONSENT_RECORD_NOT_FOUND No record with this id exists for a key of this kind, test or live.
404 None. The message is No live consent for this id The key is tied to a different property.

Also the errors every call can return.

Field values#

Interaction values#

interaction is sealed into the record. From a secret key it defaults to API, or to IMPORT when you send collectedAt.

Value Use it when
ACCEPT_ALL The person chose a single "accept all" control.
REJECT_ALL The person chose a single "reject all" control.
SAVE_PREFERENCES The person made choices purpose by purpose and saved them.
PREFERENCE_CENTRE The person changed earlier choices on a settings page.
DISMISS The person closed a notice without choosing.
DO_NOT_SELL The person used a "do not sell or share" control.
API Your system recorded the answer and none of the above fits.
IMPORT The record is brought in from another system.

The service sets GPC_SIGNAL, EMAIL_CONFIRMATION, WITHDRAW, OPERATOR and EXPIRY itself.

Guardian fields#

Field Type Values
dataPrincipalType string CHILD or PERSON_WITH_DISABILITY
guardian.relationship string PARENT or LAWFUL_GUARDIAN
guardian.verification string EXISTING_ACCOUNT, VIRTUAL_TOKEN, IDENTITY_DOCUMENT or GUARDIANSHIP_ORDER. PERSON_WITH_DISABILITY needs GUARDIANSHIP_ORDER.
guardian.reference string, up to 200 The account id, token id or order number you verified against. Only a hash of it is stored.

A write with collectedAt is checked before anything changes. The first rule that matches skips it with 409:

Code When
CONSENT_WITHDRAWN_ON_RECORD The person's record on this form is withdrawn, whatever the dates.
CONSENT_ALREADY_ON_RECORD The record's answer has this exact collectedAt. The row was sent before.
CONSENT_NEWER_ON_RECORD The record's answer is newer than collectedAt.
CONSENT_ERASED_ON_RECORD A record of the person, on any form, was erased at or after collectedAt. errorData.consentId is null.
CONSENT_HELD_UNDER_OTHER_IDENTIFIER Another record on this form, found through an identifier the request names or one linked to it, is withdrawn or holds the same or a newer answer. errorData.heldBy is WITHDRAWN, SAME or NEWER.

A write that passes seals each decision at collectedAt. The record expires after the form's term counted from the call, or at expiresAt. A person known only under an identifier the request neither names nor links is not reached. The task is in Bring in consent given earlier.

Capture fields#

capture on POST and PATCH, from a secret key:

Field Type Required Description
capture.channel string Yes BRANCH, CALL_CENTRE, IVR, KIOSK, FIELD_AGENT or PAPER.
capture.method string Yes OTP, SIGNED_DECLARATION, RECORDED_CALL, PAPER_FORM, LINK_CONFIRMED or AGENT_ATTESTED.
capture.agentRef string, up to 100 printable ASCII For AGENT_ATTESTED Your id for the member of staff. Stored only as agentRefHash.
capture.locationRef string, up to 100 printable ASCII No Your code for the branch or site. Stored only as locationRefHash.
capture.methodReference string, up to 128 For OTP, RECORDED_CALL, LINK_CONFIRMED Your id for the confirmation: letters, digits and . _ : / # -. Never personal data and never the OTP itself.
capture.documentSha256 64 hex characters For SIGNED_DECLARATION, PAPER_FORM, or methodReference The SHA-256 of the scanned form or declaration.

The response carries captureContext: channel, method, agentRefHash, locationRefHash, methodReference and documentSha256, each only when sent, plus attestedByStaffOnly. That flag is worked out from method and is not part of the seal: it is true unless the method reports the person's own confirmation.

A reference hash is HMAC-SHA256(fingerprint key, "agent:" or "location:" + reference, trimmed, in lower case), as hex. The SDK helpers captureReferenceHash and capture_reference_hash compute it. The task is in Record consent taken in a branch, by phone or on paper.

↑↓ to move, Enter to open