People
The endpoints that find, export, withdraw, erase and link everything held for one person, with parameters, examples, responses and errors.
These endpoints act on everything held for one identifier: the records recorded under it and the records it is linked to. Paths are relative to https://console.dpconsent.com/consent/sdk/v1. The tasks are in Find a person and Withdraw and erase.
Shared rules#
| Rule | Detail |
|---|---|
| Key | A secret key with the endpoint's own scope. A publishable key gets 403 SCOPE_REQUIRED. |
| Identifier | Sent in the body, never in the address. subjectType is required. |
| Match | A record matches when the identifier is the one it was recorded under, or one linked to it. |
| Test and live | A test key reaches test records only. A live key reaches live records only. |
| Audit log | Every call is logged with the key's name and the first 12 characters of the identifier's hash. |
| Budget | 60 calls a minute for each key by default, on top of the key's own budget. |
subjectType is EMAIL, PHONE, USER_ID, DEVICE_ID, SESSION_ID or COOKIE_ID.
Endpoints#
POST /subjects/find#
Lists every record held for the person, oldest first.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | subjects:read |
Ignored | None |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
subjectId |
string, up to 255 | Yes | The identifier, as you recorded or linked it. |
subjectType |
string | Yes | The type of the identifier. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/subjects/find \
-H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "subjectId": "+919846011223", "subjectType": "PHONE" }'const found = await client.findSubject('+919846011223', 'PHONE');found = client.find_subject("+919846011223", "PHONE")Response
200, trimmed:
{
"subjectType": "PHONE",
"fingerprint": "41d2c7…",
"records": [
{
"consentId": "cns_3f1c9a7e2b8d4c6fa0e5d1b7c9f2a4e8",
"status": "PARTIAL",
"decisions": [],
"collectedOn": "2026-09-24T10:12:03.000Z",
"test": false,
"erasedOn": null,
"matchedBy": ["PHONE"]
}
]
}| Field | Type | Description |
|---|---|---|
subjectType |
string | The type you sent. |
fingerprint |
string | The subject fingerprint of the identifier you sent. See Identifiers. |
records |
array | Each entry is a consent record plus erasedOn and matchedBy. An empty list means nothing is held. It is not an error. |
records[].erasedOn |
time or null |
Set on records that have been erased. They stay in the list. |
records[].matchedBy |
array | The identifier types that matched this record, such as ["PHONE"]. |
Errors
Only the shared errors, explained in Errors.
POST /subjects/export#
Returns the package to give a person who asks what you hold: a signed receipt for each record, a summary, their nominations and whom to contact.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | subjects:export |
Ignored | None |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
subjectId |
string, up to 255 | Yes | The identifier, as you recorded or linked it. |
subjectType |
string | Yes | The type of the identifier. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/subjects/export \
-H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "subjectId": "usr_8f2K1xQz", "subjectType": "USER_ID" }' \
-o access-usr_8f2K1xQz.jsonconst pack = await client.exportSubject('usr_8f2K1xQz', 'USER_ID');pack = client.export_subject("usr_8f2K1xQz", "USER_ID")Response
200, trimmed:
{
"subjectType": "USER_ID",
"generatedOn": "2026-09-24T10:20:11.000Z",
"summary": {
"purposes": [
{
"purposeKey": "course-updates-by-email",
"purposeName": "Course updates by email",
"granted": true,
"dataItems": ["Email address"]
}
],
"sharedWith": [
{
"name": "Example Mail Pvt Ltd",
"purposes": ["Course updates by email"],
"dataShared": ["Email address"]
}
]
},
"nominations": [],
"contact": {
"organisation": "Kalpa Learning",
"email": "grievance@kalpalearning.example.in",
"dataProtectionOfficer": "Meera Iyer",
"phone": null,
"postalAddress": null,
"grievanceOfficer": null
},
"receipts": []
}| Field | Type | Description |
|---|---|---|
generatedOn |
time | When the package was made. |
summary.purposes |
array | Each purpose the person was asked about: its key and name, whether it is allowed now, any partner answers and the data it uses. |
summary.sharedWith |
array | Each partner the data could be shared with, including under consent since withdrawn. |
summary.processorNotices |
array | Each instruction a Data Processor was given for this person: partner, partnerKey, action, purposes, issuedOn, dueOn, status, outcome, and completedOn, reportedOn and reportedVia of the latest report. No notes or names of your staff. |
nominations |
array | Each nomination the person made: nomineeName, relationship, status (ACTIVE or REVOKED), nominatedOn, revokedOn and requestReference. Never the nominee's email address or phone number. |
contact |
object | Whom to contact, from the console's settings. See The contact object. |
receipts |
array | One signed receipt for each record, in the shape GET /consent/{consentId}/verify returns. |
The package names the person only by hashes. Each export is noted in the history of every record it covers.
Errors
Only the shared errors, explained in Errors.
POST /subjects/withdraw#
Withdraws every consent the person has given, on every form.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | subjects:withdraw |
Checked, not replayed | consent.withdrawn, once for each record withdrawn |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
subjectId |
string, up to 255 | Yes | The identifier, as you recorded or linked it. |
subjectType |
string | Yes | The type of the identifier. |
reason |
string, up to 500 | No | Kept on each withdrawal event. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/subjects/withdraw \
-H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: close-usr_8f2K1xQz" \
-d '{
"subjectId": "usr_8f2K1xQz",
"subjectType": "USER_ID",
"reason": "Account closed at the learner’s request"
}'const { withdrawn } = await client.withdrawSubject(
'usr_8f2K1xQz',
'USER_ID',
{
reason: 'Account closed at the learner’s request',
idempotencyKey: 'close-usr_8f2K1xQz',
},
);withdrawn = client.withdraw_subject(
"usr_8f2K1xQz",
"USER_ID",
reason="Account closed at the learner’s request",
idempotency_key="close-usr_8f2K1xQz",
)Response
200:
{ "withdrawn": 2 }withdrawn is how many records changed. Every record that is not erased and not already withdrawn becomes WITHDRAWN. A second call returns 0. The Python call returns the number itself.
Errors
Only the shared errors, explained in Errors.
POST /subjects/erase#
Removes what identifies the person from every record, and withdraws any consent still in force first.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | subjects:erase |
Checked, not replayed | consent.withdrawn for each record still in force, then subject.erased for each record erased |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
subjectId |
string, up to 255 | Yes | The identifier, as you recorded or linked it. |
subjectType |
string | Yes | The type of the identifier. |
reason |
string, up to 500 | No | Kept on the erasure event and sent in subject.erased. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/subjects/erase \
-H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: erase-usr_8f2K1xQz" \
-d '{
"subjectId": "usr_8f2K1xQz",
"subjectType": "USER_ID",
"reason": "Erasure requested by the learner"
}'const { erased } = await client.eraseSubject(
'usr_8f2K1xQz',
'USER_ID',
{
reason: 'Erasure requested by the learner',
idempotencyKey: 'erase-usr_8f2K1xQz',
},
);erased = client.erase_subject(
"usr_8f2K1xQz",
"USER_ID",
reason="Erasure requested by the learner",
idempotency_key="erase-usr_8f2K1xQz",
)Response
200:
{ "erased": 2 }erased is how many records this call erased. A second call returns 0. The Python call returns the number itself. What is cleared, and the one-year hold under the DPDP Act, are in Withdraw and erase.
Errors
Only the shared errors, explained in Errors.
POST /subjects/link#
Links further identifiers to every record of the person that is not erased.
| Key | Scope | Idempotency-Key |
Webhook sent |
|---|---|---|---|
| Secret | subjects:link |
Checked, not replayed | None |
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
subjectId |
string, up to 255 | Yes | The identifier, as you recorded or linked it. |
subjectType |
string | Yes | The type of the identifier. |
identifiers |
array, 1 to 5 | Yes | Each entry has subjectType and subjectId. Entries must not repeat. |
Request
curl -X POST https://console.dpconsent.com/consent/sdk/v1/subjects/link \
-H "Authorization: Bearer $PIEDAP_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: phone-usr_8f2K1xQz-919846099887" \
-d '{
"subjectId": "usr_8f2K1xQz",
"subjectType": "USER_ID",
"identifiers": [
{ "subjectType": "PHONE", "subjectId": "+919846099887" }
]
}'const result = await client.linkIdentifiers(
'usr_8f2K1xQz',
'USER_ID',
[{ subjectType: 'PHONE', subjectId: '+919846099887' }],
{ idempotencyKey: 'phone-usr_8f2K1xQz-919846099887' },
);from piedap_consent import Identifier
result = client.link_identifiers(
"usr_8f2K1xQz",
"USER_ID",
[Identifier("PHONE", "+919846099887")],
idempotency_key="phone-usr_8f2K1xQz-919846099887",
)Response
200:
{ "records": 2, "added": 2 }| Field | Type | Description |
|---|---|---|
records |
integer | How many records the person has that are not erased. |
added |
integer | How many new links were made. An identifier already linked is skipped. |
Errors
Only the shared errors, explained in Errors.
Shared errors#
| Status | Code | When |
|---|---|---|
403 |
SCOPE_REQUIRED |
The key lacks the scope, or the call was made with a publishable key. |
403 |
SECRET_KEY_IN_BROWSER |
The request came from a browser. The key is flagged as exposed. |
400 |
None. The message is a list | A field is missing or not valid: no subjectType, an unknown type, an empty identifiers list, more than five entries or a repeat. |
429 |
None. The message is Too many requests |
The budget for calls on a person, or the key's own budget, is used up. Wait for Retry-After. |
Every code is explained in Errors.