Consent check
The endpoint that answers whether one person allows a purpose now, with parameters, the decision rule, responses, budgets and errors.
This endpoint answers whether one person allows one purpose now, from every record held for them. Paths are relative to https://console.dpconsent.com/consent/sdk/v1. The task is in Check consent before you process.
Shared rules#
| Rule | Detail |
|---|---|
| Key | A secret key with consent:check. No key holds it unless you grant it. A publishable key gets 403 SCOPE_REQUIRED. |
| Identifiers | Sent in the body, never in the address. |
| Match | A record matches when an identifier is the one it was recorded under, or one linked to it. Records of every property of the project count. |
| Test and live | A test key reads test records only. A live key reads live records only. |
| Audit log | No entry for each check. One entry, CHECK_BUDGET_EXCEEDED, when a key runs out of its budget, once a minute. |
| Budget | 600 identifiers a minute for each key unless you change Checks per minute. See Budget. |
| Caching | Cache-Control: no-store. Each call reads the records as they are. |
Endpoints#
POST /consent/check#
Answers whether the person allows the purpose, and for one partner when you name it.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | consent:check |
Ignored | None |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
subjectId |
string, up to 255 | Yes | The identifier, as you recorded or linked it. |
subjectType |
string | Yes | EMAIL, PHONE, USER_ID, DEVICE_ID, SESSION_ID or COOKIE_ID. |
alsoKnownAs |
array, up to 4 | No | Other identifiers of the same person, each { subjectType, subjectId }. Each is looked up too. Nothing is linked. An entry equal to the subject is dropped. |
purposeKey |
string, up to 60 | Yes | A purpose key of the project. |
partnerKey |
string | No | A partner the purpose lists now. The answer then covers sharing with that partner. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/consent/check \
-H "Authorization: Bearer $PIEDAP_CHECK_KEY" \
-H "Content-Type: application/json" \
-d '{
"subjectId": "CIF00412873",
"subjectType": "USER_ID",
"alsoKnownAs": [
{ "subjectType": "PHONE", "subjectId": "+919846011223" },
{ "subjectType": "EMAIL", "subjectId": "anjali.menon@example.in" }
],
"purposeKey": "offers-by-sms",
"partnerKey": "kaleyra"
}'const check = await client.checkConsent({
subjectId: 'CIF00412873',
subjectType: 'USER_ID',
alsoKnownAs: [{ subjectType: 'PHONE', subjectId: '+919846011223' }],
purposeKey: 'offers-by-sms',
partnerKey: 'kaleyra',
});from piedap_consent import Identifier
check = client.check_consent(
"CIF00412873",
"USER_ID",
"offers-by-sms",
partner_key="kaleyra",
also_known_as=[Identifier("PHONE", "+919846011223")],
)Response
200:
{
"allowed": true,
"reason": "granted",
"recorded": "granted",
"purpose": {
"key": "offers-by-sms",
"category": "MARKETING",
"declaredBasis": "CONSENT",
"legitimateUse": null,
"consentRequired": true
},
"partnerKey": "kaleyra",
"validUntil": "2027-08-01T06:12:44.000Z",
"basedOn": [
{
"consentId": "cns_5b0e2d4c9f1a4e7b8c3d6a2f1e9b7c40",
"proofHash": "9be14c0f3a6d…",
"state": "granted",
"decidedAt": "2026-08-01T06:12:44.000Z",
"matchedBy": ["PHONE"],
"attestedByStaffOnly": false
}
],
"notes": [],
"checkedAt": "2026-10-03T09:15:42.120Z",
"test": false
}| Field | Type | Description |
|---|---|---|
allowed |
boolean | true only when reason is granted. |
reason |
string | See Reasons. |
recorded |
string | What the records say, whatever the purpose's basis. The same as reason unless reason is not_consent_based. |
purpose.key, purpose.category |
string | From the purpose. |
purpose.declaredBasis |
string | The purpose's legal basis, for information. |
purpose.legitimateUse |
string or null | The section 7 clause the purpose names, if any. |
purpose.consentRequired |
boolean | true when the basis is consent, the purpose is not always on and names no section 7 clause. |
partnerKey |
string or null | As sent. |
validUntil |
time or null | When the grant behind the answer expires. null unless allowed, or when the record has no expiry. |
basedOn |
array | The records behind the answer, newest first, at most 25. |
basedOn[].consentId, basedOn[].proofHash |
string or null | The record and its current seal. null for a record of another property when the key is tied to one, and for a withdrawal made on a notice link that is not applied yet. |
basedOn[].state |
string | granted, refused, withdrawn, expired, erased or pending_confirmation. |
basedOn[].decidedAt |
time or null | When that answer, withdrawal or erasure took effect. null for pending_confirmation. |
basedOn[].matchedBy |
array | The identifier types that matched the record. |
basedOn[].attestedByStaffOnly |
boolean | true for a grant whose current answer rests only on a member of staff's attestation. |
notes |
array | See Notes. |
checkedAt |
time | The moment the rule used as now. |
test |
boolean | true for a test key. |
Reasons#
reason |
allowed |
Meaning |
|---|---|---|
granted |
true |
The newest answer for the purpose, and the partner, is yes and still valid. |
refused |
false |
The newest answer is no, or the partner was refused. |
withdrawn |
false |
The newest event is a withdrawal of a record that held the purpose. |
expired |
false |
The newest answer was yes and has lapsed. Ask again. |
pending_confirmation |
false |
Only an unconfirmed email double opt-in mentions the purpose. |
erased |
false |
The person was erased after their newest answer. |
no_record |
false |
Nothing held under these identifiers answers this purpose. |
not_consent_based |
false |
The purpose declares a basis other than consent. The product does not decide whether that basis applies to this person. |
The Node.js and Python helpers add unavailable when no answer arrived. See Node.js.
Notes#
| Note | Added when |
|---|---|
PARTNER_NOT_OFFERED |
A record grants the purpose under a form version that did not offer the partner. |
PHONE_NOT_E164 |
A PHONE identifier is not in E.164, such as +919846011223. It was still looked up exactly as sent. |
ATTESTED_BY_STAFF_ONLY |
The answer is granted and every basedOn entry has attestedByStaffOnly: true. |
WITHDRAWAL_IN_PROGRESS |
A withdrawal made on a notice link, not yet applied to the records, is among the answers. |
Errors#
| Status | Code | When |
|---|---|---|
400 |
None. The message is a list | A field is missing or not valid, or alsoKnownAs has more than four entries or a repeated one. |
403 |
SCOPE_REQUIRED |
A publishable key, or a secret key without consent:check. |
404 |
PURPOSE_NOT_FOUND |
No purpose has the key in this project. The message is No purpose has the key "offers-by-sms" in this project. |
404 |
PARTNER_NOT_FOUND |
The purpose does not list the partner now. The message is The purpose "offers-by-sms" lists no partner "kaleyra". |
429 |
CHECK_BUDGET_EXCEEDED |
The key checked its limit of identifiers this minute. Retry-After is set. |
429 |
None | The key's own budget or the budget for each address was reached. |
Also the errors every call can return.
How the answer is reached#
Each matched record gives at most one answer, read from its sealed events. The date of an answer is when the person last changed it for this purpose.
| Order | The record | Gives |
|---|---|---|
| 1 | Was erased, whatever it holds | erased, at the erasure |
| 2 | Waits for an email confirmation and holds the purpose | pending_confirmation |
| 3 | Never answered the purpose | Nothing |
| 4 | Is withdrawn | withdrawn, at the withdrawal |
| 5 | Answered no | refused |
| 6 | Answered yes on a version that did not offer the partner | Nothing, and the note PARTNER_NOT_OFFERED |
| 7 | Answered yes and has expired, or its expiry has passed | expired |
| 8 | Answered yes | granted, valid until the record's expiry |
An open notice-link withdrawal gives withdrawn at the moment of the click. It counts when the campaign listed the person under one of the identifiers, or under one a matched record holds.
| Step | Rule |
|---|---|
| 1 | Find the newest erased, withdrawn or refused answer. |
| 2 | Grants newer than it give granted. |
| 3 | Otherwise the newest of the rest wins. On a tie: erased, then withdrawn, then refused, then expired. |
| 4 | Otherwise pending_confirmation if there is one, else no_record. |
| 5 | A purpose not based on consent then gives allowed: false and not_consent_based. |
The reasoning, with examples, is in How a consent check decides.
Budget#
| Budget | Default | Counts |
|---|---|---|
| Identifiers checked, for each key | 600 a minute | The subject and each distinct alsoKnownAs entry: 1 to 5 a call. Charged before the purpose is looked up. A write with collectedAt from a key that holds consent:check is charged too, one for each identifier it names, because its 409 shows who is on record. |
| The key's own budget | 600 or 6,000 a minute | One for each call |
| For each address | 3,000 a minute | One for each call to /sdk |
- A write with
collectedAtover the budget answers429 CHECK_BUDGET_EXCEEDEDand records nothing, like a check. - Set a key's own number, from 60 to 6,000 identifiers a minute, under Checks per minute on the key.
- Whoever runs the deployment can raise the most a key may be given with
SDK_CHECK_MAX_PER_MINUTE. - A server that makes more than 3,000 calls a minute from one address also needs
RATE_LIMIT_SDK_MAXraised.
The budget limits how fast a copied key can test identifiers. It does not stop a copied key. Revoke any key you suspect was copied.