Consent Docs

Consent check

The endpoint that answers whether one person allows a purpose now, with parameters, the decision rule, responses, budgets and errors.

Reference6 min readFor developers calling the REST API

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

cURLShell
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"
  }'
Node.jsJavaScript
const check = await client.checkConsent({
  subjectId: 'CIF00412873',
  subjectType: 'USER_ID',
  alsoKnownAs: [{ subjectType: 'PHONE', subjectId: '+919846011223' }],
  purposeKey: 'offers-by-sms',
  partnerKey: 'kaleyra',
});
Python
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:

JSON
{
  "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 collectedAt over the budget answers 429 CHECK_BUDGET_EXCEEDED and 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_MAX raised.

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.

↑↓ to move, Enter to open