privacy
Privacy and consent management for GDPR, UK GDPR, CCPA/CPRA, LGPD and PIPEDA: the purposes a tenant processes personal data for, each person's consent with a s…
Privacy and consent management for GDPR, UK GDPR, CCPA/CPRA, LGPD and PIPEDA: the purposes a tenant processes personal
data for, each person's consent with a signed receipt and an append-only history, data-subject requests with statutory
deadlines, legal holds that stop erasure, and restriction of processing. People make their own choices from their
session, applications record consent for their own customers, and policies read the result as principal.consents.
See the privacy guide; the repository guide is docs/privacy.md.
Subjects
Methods that take a subject accept either identityId (a person of the tenant; service accounts and agents are not
data subjects, so they answer INVALID_INPUT) or externalId (1 to 256 visible characters the application chooses,
such as a customer number or a visitor ID), never both. Records name them identity:ID and external:ID. A request
from someone known only by an email address gets an email: subject, a hash of the address keyed with the deployment
secret, until a handler links it with linkRequest.
Purposes and consent states
A purpose has a permanent key, a legalBasis and, for consent, a mode. Only consent (opt-in or opt-out) and
legitimate-interests purposes are decided by the person; contract, legal-obligation, vital-interests and
public-task purposes are allowed without a record. check and the other readers answer allowed with a
reason: CONSENT_GIVEN, NOT_OPTED_OUT, LEGITIMATE_INTERESTS or LEGAL_BASIS when processing may go ahead, and
NO_CONSENT, CONSENT_WITHDRAWN, CONSENT_EXPIRED, CONSENT_OUTDATED, OBJECTED, RESTRICTED, ERASED or
PURPOSE_ARCHIVED when it may not. Publishing a new version (updatePurpose with newVersion: true) makes opt-in
grants for older versions outdated unless the purpose sets reconsentOnVersion: false, and consentLifetimeDays makes
grants lapse. Restriction and an erased application subject hold back everything except legal-obligation and
vital-interests purposes.
Requests and deadlines
A data-subject request has a type (access, portability, erasure, rectification, restriction, objection,
opt-out), a regulation and a status: pending-verification until someone confirms who is asking, open with a
dueAt once verified, then completed, rejected or cancelled. The response window starts at verification: GDPR
and UK GDPR 30 days (extendable once by 60), CCPA 45 (plus 45), LGPD 15 (no extension), PIPEDA and other 30 (plus
30); the tenant's responseDays setting can only shorten it. iam.privacy.sendDeadlineReminders() reminds handlers
of requests due soon and overdue, and lapses public requests nobody confirmed within seven days.
Permissions
iam:privacy:manage covers purposes, legal holds and settings; iam:privacy:read consents, history, requests,
holds, restrictions, settings and the summary; iam:privacy:record recording consent; iam:privacy:check checking
it; and iam:privacy:handle handling requests. Actions are checked on iam/TENANT_ID, or on the purpose, request or
hold a call acts on (iam/ID). Fulfilling access and portability requests, downloading someone else's export and
linking a request to an account also need iam:identities:read on the account, and erasure iam:identities:delete.
People manage their own choices and requests from their own signed-in session of the organization without any
permission: role sessions, API keys, session tokens and agents acting for them are refused with ACCESS_DENIED, and
impersonation with IMPERSONATION_RESTRICTED. submitPublic and confirmPublic need no credential.
| Method | What it does | Access |
|---|---|---|
addNote | Adds an internal note to a request's timeline. | Credential |
assignRequest | Hands a request to a handler, or clears the assignment with assigneeId: null. | Credential |
audience | Lists the subjects a purpose may be processed for right now. | Credential |
cancelMyRequest | Withdraws one of your own requests while it is still being handled. | Credential |
check | Tells whether a purpose may be processed for one subject right now, and why. | Credential |
confirmPublic | Confirms a public request from the link emailed to the requester. | Public |
createPurpose | Adds a processing purpose at version 1. | Credential |
createRequest | Files a data-subject request on someone's behalf, received by phone, mail or a support ticket. | Credential |
decide | Records your own decision on a purpose and returns its signed receipt. | Credential |
deletePurpose | Deletes a purpose nobody has decided on yet. | Credential |
downloadExport | Returns the export an access or portability request produced. | Credential |
extendRequest | Extends an open request's deadline once by the regulation's extension, telling the subject why. | Credential |
filterSubjects | Splits up to 1000 subjects into those a purpose may be processed for and those it may not. | Credential |
fulfilRequest | Completes an open request by doing what it asks, and emails the subject. | Credential |
getRequest | Returns one request with the subject's legal hold and restriction status. | Credential |
getSettings | Returns the tenant's privacy settings with the statutory windows. | Credential |
history | Lists a subject's recorded decisions, newest first. | Credential |
importDecisions | Imports up to 500 consent decisions from another system in one transaction. | Credential |
liftRestriction | Ends a restriction of processing for a subject. | Credential |
linkRequest | Links a request known only by an email address to the account or application subject it is about. | Credential |
listConsents | Lists current decisions, newest first, each with its state. | Credential |
listHolds | Lists the tenant's legal holds, newest first. | Credential |
listPurposes | Lists the tenant's purposes by name. | Credential |
listRequests | Lists requests, open ones first by deadline, then the newest. | Credential |
listRestrictions | Lists subjects whose processing is restricted, newest first. | Credential |
mine | Returns your privacy page: every purpose with your decision and its effect, your requests, and the privacy contact. | Credential |
myHistory | Lists your own recorded decisions, newest first. | Credential |
myReceipt | Returns the signed receipt of one of your own decisions. | Credential |
placeHold | Places a legal hold on a subject: erasure, and every deletion of their account, is refused until it is released. | Credential |
record | Records a consent decision for a person of the tenant or an application subject, and returns its receipt. | Credential |
rejectRequest | Declines an open or unverified request with a reason the subject is emailed. | Credential |
releaseHold | Releases a legal hold. | Credential |
submitPublic | Files a request from someone without an account, from a public form, when the organization turned on public intake. | Public |
submitRequest | Files a data-subject request for yourself; signing in verified who you are, so its deadline starts at once. | Credential |
summary | Returns consent counts per purpose, the request queue, holds and restrictions at a glance. | Credential |
updatePurpose | Edits a purpose, optionally publishing the change as a new version or archiving it. | Credential |
updateSettings | Changes the privacy contact, default regulation, internal response windows, public intake and export lifetime. | Credential |
verifyReceipt | Checks a consent receipt someone presents against the signature and the history. | Credential |
verifyRequest | Records how the requester's identity was confirmed, which opens the request and starts its deadline. | Credential |
addNote
Adds an internal note to a request's timeline.
- Permission:
iam:privacy:handleon the request. - Audited as:
iam:privacy:handle. - Errors:
CONFLICT(409) when erasure redacted the request;NOT_FOUNDwhen the request is not in this tenant;INVALID_INPUTfor an empty note or one over 2000 characters.
Notes are never shown to the subject: their own view of the request (mine) leaves notes and assignments out. The
timeline keeps the newest 100 events.
Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/addNote" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"note": "<note>"
}'iam.api.privacy.addNote(
credential: CredentialInput,
input: { tenantId: string; requestId: string; note: string },
): Promise<SubjectRequestView>assignRequest
Hands a request to a handler, or clears the assignment with assigneeId: null.
- Permission:
iam:privacy:handleon the request. - Audited as:
iam:privacy:handle. - Errors:
INVALID_TRANSITION(409) when the request is closed;INVALID_INPUTwhen the assignee is not an active person of the tenant;NOT_FOUNDwhen the request or the assignee is not in this tenant.
The assignee receives the request's deadline reminders instead of the privacy contact.
Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/assignRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"assigneeId": "<assigneeId>"
}'iam.api.privacy.assignRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string; assigneeId: string | null },
): Promise<SubjectRequestView>audience
Lists the subjects a purpose may be processed for right now.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read. - Errors:
NOT_FOUNDfor an unknown purpose;INVALID_INPUTfor alimitoutside 1 to 1000.
The list holds every active person of the tenant for whom the purpose is allowed (everyone not opted out or objected
for opt-out and legitimate-interest purposes, those with a live grant for opt-in ones), then every application subject
with a decision that allows it. The result is total and a page of subjects (limit 1000 by default, offset).
People carry email and name only when the caller may also read identities (iam:identities:read on the tenant);
otherwise they are listed by identityId. Use filterSubjects to check a list you already have.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/audience" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"purposeKey": "<purposeKey>"
}'iam.api.privacy.audience(
credential: CredentialInput,
input: { tenantId: string; purposeKey: string; limit?: number; offset?: number },
): Promise<{
purposeKey: string;
total: number;
subjects: {
subject: string;
identityId?: string;
externalId?: string;
email?: string;
name?: string;
}[];
}>cancelMyRequest
Withdraws one of your own requests while it is still being handled.
- Permission: None beyond a person's own session of the tenant.
- Audited as:
privacy:request:cancel. - Errors:
NOT_FOUNDwhen the request is not yours;INVALID_TRANSITION(409) when it is already closed.
Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/cancelMyRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>"
}'iam.api.privacy.cancelMyRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string },
): Promise<SubjectRequestView>check
Tells whether a purpose may be processed for one subject right now, and why.
- Permission:
iam:privacy:checkon the tenant. - Audited as:
iam:privacy:check. - Errors:
NOT_FOUNDfor an unknown purpose or identity (including a deleted one);INVALID_INPUTfor a malformed subject or an identity that is not a person.
The result is purposeKey, the purpose's current purposeVersion, allowed, reason and, when a decision is
recorded, consent (granted, version, time, expiry and receipt id). Server code calls iam.privacy.check with the
same input and no credential, not audited.
const { allowed, reason } = await iam.api.privacy.check(apiKey, {
tenantId,
subject: { externalId: 'cus_100' },
purposeKey: 'marketing-email',
});Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/check" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"subject": {
"identityId": "<identityId>"
},
"purposeKey": "<purposeKey>"
}'iam.api.privacy.check(
credential: CredentialInput,
input: { tenantId: string; subject: SubjectInput; purposeKey: string },
): Promise<{
consent?:
| {
receiptId: string;
expiresAt?: number | undefined;
granted: boolean;
purposeVersion: number;
recordedAt: number;
}
| undefined;
allowed: boolean;
reason: ConsentReason;
purposeKey: string;
purposeVersion: number;
}>confirmPublic
Confirms a public request from the link emailed to the requester.
- Permission: None: public, tenant-bound (on an organization's own address
tenantIdmay be left out). - Audited as:
privacy:request:verify(methodemail-link, and whether it waslinkedto an account), orprivacy:request:email-confirmedfor a request naming anexternalId; actorpublic-intake. - Errors:
CONFIRMATION_INVALID(400) for a wrong, used or expired token, an unknown request, or one that is no longer waiting;RATE_LIMITED(429) after 10 attempts per request in a rate-limit window.
The confirmation opens the request and starts its deadline. When the address belongs to a person of the tenant with a
verified email, the request is linked to their account. A request naming an externalId stays
pending-verification, because owning an address does not prove owning the identifier: the call records
emailConfirmedAt, emails the privacy contact, and a handler checks the identifier with
verifyRequest. The result is number, status and, once open, dueAt. Tokens are single use and
lapse after seven days.
await client.privacy.confirmPublic({ tenantId, requestId, token });Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/confirmPublic" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"token": "<token>"
}'iam.api.privacy.confirmPublic(
input: { tenantId: string; requestId: string; token: string },
): Promise<{ dueAt?: number | undefined; number: string; status: SubjectRequestStatus }>createPurpose
Adds a processing purpose at version 1.
Used inPrivacy and consent
- Permission:
iam:privacy:manageon the tenant. - Audited as:
iam:privacy:manage. - Errors:
CONFLICT(409) when a purpose with the key exists;LIMIT_EXCEEDED(409) past 200 purposes;INVALID_INPUTfor a malformed key, a name over 120 characters, an empty description or one over 5000,opt-outon a basis other thanconsent,consentLifetimeDayson a basis other thanconsent, aretentionDaysoutside 1 to 36500 or aconsentLifetimeDaysoutside 1 to 3650, or more than 32dataCategories.
Keys are permanent: 1 to 64 lowercase letters, digits, dots, underscores or hyphens, starting with a letter. mode
defaults to opt-in and reconsentOnVersion to true. dataCategories are lowercase identifiers such as contact or
usage.
await iam.api.privacy.createPurpose(admin, {
tenantId,
key: 'marketing-email',
name: 'Marketing email',
description: 'Product news and offers by email.',
legalBasis: 'consent',
dataCategories: ['contact'],
consentLifetimeDays: 365,
});Prop
Type
A PrivacyPurpose object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/createPurpose" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"legalBasis": "consent",
"tenantId": "<tenantId>"
}'iam.api.privacy.createPurpose(
credential: CredentialInput,
input: PurposeInput & { tenantId: string },
): Promise<PrivacyPurpose>createRequest
Files a data-subject request on someone's behalf, received by phone, mail or a support ticket.
- Permission:
iam:privacy:handleon the tenant. - Audited as:
iam:privacy:handle, plusprivacy:request:submitwith channelstaff. - Errors:
INVALID_INPUTwithout asubjector arequesterEmail, or for a malformed email, details over 5000 characters, more than 50purposeKeysor an unknowntypeorregulation;NOT_FOUNDfor an unknown identity or purpose key.
Name the subject, or for someone without an account the requesterEmail (the request then has an email: subject
and must be linked with linkRequest before most types can be fulfilled). With verified (the
method the handler used to confirm who is asking, and an optional note) the request opens at once and the privacy
contact is emailed; without it, it waits for verifyRequest. regulation defaults to the tenant's.
await iam.api.privacy.createRequest(handler, {
tenantId,
type: 'erasure',
subject: { identityId },
verified: { method: 'Call-back to the number on file' },
});Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/createRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"type": "access"
}'iam.api.privacy.createRequest(
credential: CredentialInput,
input: {
tenantId: string;
type: SubjectRequestType;
subject?: SubjectInput;
requesterEmail?: string;
requesterName?: string;
regulation?: Regulation;
details?: string;
purposeKeys?: string[];
verified?: { method: string; note?: string };
},
): Promise<SubjectRequestView>decide
Records your own decision on a purpose and returns its signed receipt.
Used inPrivacy and consent
- Permission: None beyond a person's own session of the tenant.
- Audited as:
privacy:consent, with the purpose key,granted, version, sourceselfand receipt id. - Errors:
VERSION_CONFLICT(409) whenversionis not the purpose's current version;INVALID_INPUTfor a purpose with a basis other than consent or legitimate interests, a non-booleangranted, or a malformedmethodorevidence;CONFLICT(409) when granting an archived purpose;NOT_FOUNDfor an unknown purpose;ACCESS_DENIEDandIMPERSONATION_RESTRICTEDfor anything but a person's own session.
granted: true consents, or withdraws an objection to a legitimate-interest purpose; false withdraws consent, opts
out, or objects. Pass the version you showed the person (from mine), so nobody agrees to text they were not shown.
method (a short label, self-service by default) and evidence (up to 2000 characters) are kept in the history with
the client's IP address and user agent. The decision applies to principal.consents from the next check.
const receipt = await iam.api.privacy.decide(session, {
tenantId,
purposeKey: 'marketing-email',
version: 1,
granted: true,
evidence: 'Signup form v3',
});Prop
Type
A ConsentReceipt object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/decide" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"purposeKey": "<purposeKey>",
"version": 1,
"granted": true
}'iam.api.privacy.decide(
credential: CredentialInput,
input: {
tenantId: string;
purposeKey: string;
version: number;
granted: boolean;
method?: string;
evidence?: string;
},
): Promise<ConsentReceipt>deletePurpose
Deletes a purpose nobody has decided on yet.
- Permission:
iam:privacy:manageon the purpose. - Audited as:
iam:privacy:manage. - Errors:
RESOURCE_IN_USE(409) once any decision is recorded for it;NOT_FOUNDwhen the purpose is not in this tenant.
A purpose with recorded decisions is kept as proof of what people agreed to: archive it with
updatePurpose (archived: true) instead.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/deletePurpose" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"purposeId": "<purposeId>"
}'iam.api.privacy.deletePurpose(
credential: CredentialInput,
input: { tenantId: string; purposeId: string },
): Promise<{ deleted: boolean }>downloadExport
Returns the export an access or portability request produced.
- Permission: None for the person it is about, from their own session; otherwise
iam:privacy:handleon the request,iam:identities:readon the account when the request is about one, and a recent sign-in. - Audited as:
privacy:export:download, withby: 'subject'or'handler'; refusals asdenyon the missing action. - Errors:
NOT_FOUNDwhen the request has no export or the export has expired;ACCESS_DENIEDwithout the permissions;RECENT_AUTH_REQUIREDfor a handler without a recent sign-in.
The result is number, createdAt, expiresAt, sha256 (of the canonical JSON, so a copy handed over can be
checked later) and data. Exports stay downloadable for the tenant's exportLifetimeDays (14 by default), and
iam.sweepExpired() deletes them afterwards. People without an account cannot sign in, so a handler downloads theirs
and delivers it another way.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/downloadExport" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>"
}'iam.api.privacy.downloadExport(
credential: CredentialInput,
input: { tenantId: string; requestId: string },
): Promise<{
number: string;
createdAt: number;
expiresAt: number;
sha256: string;
data: Json;
}>extendRequest
Extends an open request's deadline once by the regulation's extension, telling the subject why.
- Permission:
iam:privacy:handleon the request. - Audited as:
iam:privacy:handle, plusprivacy:request:extendwith the newdueAt. - Errors:
INVALID_TRANSITION(409) when the request is not open;CONFLICT(409) when it was already extended or its regulation allows no extension (LGPD);INVALID_INPUTfor an empty reason or one over 2000 characters.
GDPR and UK GDPR add 60 days, CCPA 45, PIPEDA and other 30. The subject is emailed (privacy-request-update) and sees
the reason on their privacy page. Deadline reminders start over.
Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/extendRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"reason": "<reason>"
}'iam.api.privacy.extendRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string; reason: string },
): Promise<SubjectRequestView>filterSubjects
Splits up to 1000 subjects into those a purpose may be processed for and those it may not.
Used inPrivacy and consent
- Permission:
iam:privacy:checkon the tenant. - Audited as:
iam:privacy:check. - Errors:
INVALID_INPUTfor more than 1000 subjects or a malformed one;NOT_FOUNDfor an unknown purpose.
The result is purposeKey, allowed (the subjects as you passed them) and refused (each with its reason, or
UNKNOWN_SUBJECT for an identity that does not exist or was deleted). Use it in batch jobs such as a marketing send.
const { allowed } = await iam.api.privacy.filterSubjects(mailerKey, {
tenantId,
purposeKey: 'marketing-email',
subjects: customers.map((customer) => ({ externalId: customer.id })),
});Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/filterSubjects" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"purposeKey": "<purposeKey>",
"subjects": [
{
"identityId": "<identityId>"
}
]
}'iam.api.privacy.filterSubjects(
credential: CredentialInput,
input: { tenantId: string; purposeKey: string; subjects: SubjectInput[] },
): Promise<{
purposeKey: string;
allowed: SubjectInput[];
refused: { subject: SubjectInput; reason: string }[];
}>fulfilRequest
Completes an open request by doing what it asks, and emails the subject.
- Permission:
iam:privacy:handleon the request; access and portability alsoiam:identities:read, and erasureiam:identities:delete, on the account the request is about. - Audited as:
iam:privacy:handleandprivacy:request:completewith theactions; an erasure alsoprivacy:erasureand, when it deletes an account,identity:deletewitherasure: true. - Errors:
INVALID_TRANSITION(409) for a request stillpending-verificationor already closed;INVALID_INPUTfor a request other than rectification with anemail:subject (link it first) or a rectification without anote;LEGAL_HOLD(409) for an erasure under a legal hold;RECENT_AUTH_REQUIREDfor access, portability and erasure without a recent sign-in;ACCESS_DENIEDwithout the directory permission; identity deletion errors such asLAST_OWNER.
What each type does:
accessbuilds a full export (profile, groups, access, sessions, sign-ins, MFA and passkeys, linked sign-ins, agreements, onboarding, access requests, consents and their history, processing purposes, privacy requests, and the person's audited activity when the handler may read the audit log,activityOmitted: trueotherwise);portabilitybuilds one of what the person provided.erasuredeletes the account, renames the tombstone "Erased person" and removes its remaining details, deletes sign-in records, devices, onboarding progress, queued messages, consent decisions, restrictions and exports, redacts the consent history and the subject's requests, and for an application subject leaves a suppression marker so later checks answerERASED.restrictionrestricts processing of the subject.objectionrecords a withdrawal for each purpose inpurposeKeys, or every consent and legitimate-interest purpose;opt-outfor each named purpose, or every opt-out consent purpose.rectificationis done by hand; thenotesays what was corrected.
The result lists what was done in actions, such as export:full, consents-withdrawn:3, restricted,
rectified, account-deleted, history-redacted:2 or nothing-held. The subject is emailed at the address they had
before any erasure.
const done = await iam.api.privacy.fulfilRequest(handler, { tenantId, requestId });Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/fulfilRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>"
}'iam.api.privacy.fulfilRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string; note?: string },
): Promise<SubjectRequestView>getRequest
Returns one request with the subject's legal hold and restriction status.
- Permission:
iam:privacy:readon the request. - Audited as:
iam:privacy:read. - Errors:
NOT_FOUNDwhen the request is not in this tenant.
The request carries its timeline (events), overdue, subjectName for an existing account, and legalHold and
restricted for the subject. Confirmation tokens are never returned.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/getRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>"
}'iam.api.privacy.getRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string },
): Promise<{
legalHold: boolean;
restricted: boolean;
id: string;
tenantId: string;
number: string;
type: SubjectRequestType;
regulation: Regulation;
status: SubjectRequest['status'];
subject: string;
identityId?: string;
externalId?: string;
subjectName?: string;
requesterEmail?: string;
requesterName?: string;
details?: string;
purposeKeys?: string[];
channel: SubjectRequest['channel'];
verification: SubjectRequest['verification'];
submittedAt: number;
receivedAt?: number;
dueAt?: number;
extendedAt?: number;
extensionReason?: string;
assigneeId?: string;
closedAt?: number;
closedBy?: string;
rejectionReason?: RejectionReason;
actions?: string[];
exportId?: string;
redacted?: boolean;
overdue: boolean;
events: SubjectRequestEvent[];
}>getSettings
Returns the tenant's privacy settings with the statutory windows.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read.
The result is contactEmail, contactName, defaultRegulation, responseDays, publicIntake, exportLifetimeDays
and statutory (each regulation's response and extension days) for display beside the tenant's own.
Prop
Type
A PrivacySettingsView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/getSettings" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.getSettings(
credential: CredentialInput,
input: { tenantId: string },
): Promise<PrivacySettingsView>history
Lists a subject's recorded decisions, newest first.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read. - Errors:
NOT_FOUNDfor an unknown identity;INVALID_INPUTfor a malformed subject or purpose key.
At most 1000 entries, optionally for one purposeKey, with source, method, evidence, IP address and user agent.
Deleted accounts are accepted, and entries redacted by erasure are marked redacted without evidence, IP address or
user agent.
Prop
Type
An array of ConsentHistoryEntry.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/history" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"subject": {
"identityId": "<identityId>"
}
}'iam.api.privacy.history(
credential: CredentialInput,
input: { tenantId: string; subject: SubjectInput; purposeKey?: string },
): Promise<ConsentHistoryEntry[]>importDecisions
Imports up to 500 consent decisions from another system in one transaction.
- Permission:
iam:privacy:recordon the tenant. - Audited as:
iam:privacy:record, plus oneprivacy:consent-importwith the count, how many became current, and the purpose keys. - Errors:
INVALID_INPUTfor an empty list or more than 500 decisions, an entry withoutversionor with arecordedAtin the future, a non-booleangranted, or a purpose without a say for the person;NOT_FOUNDfor an unknown purpose or identity;CONFLICT(409) for a grant on an archived purpose.
It is all or nothing. Each entry follows record with source import: recordedAt and the version the
person decided on are required, so an imported grant never counts for newer text. Every entry joins the history, but
one older than the subject's current decision does not replace it. The result is imported and current.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/importDecisions" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"decisions": [
{
"subject": {
"identityId": {}
},
"purposeKey": "<purposeKey>",
"granted": true,
"recordedAt": 1790000000000,
"version": 1
}
]
}'iam.api.privacy.importDecisions(
credential: CredentialInput,
input: {
tenantId: string;
decisions: Array<{
subject: SubjectInput;
purposeKey: string;
granted: boolean;
method?: string;
evidence?: string;
recordedAt: number;
version: number;
}>;
},
): Promise<{ imported: number; current: number }>liftRestriction
Ends a restriction of processing for a subject.
- Permission:
iam:privacy:handleon the tenant. - Audited as:
iam:privacy:handle, plusprivacy:restriction:lift. - Errors:
NOT_FOUNDwhen the subject has no restriction or is unknown.
Tell the person before lifting a restriction (GDPR Art. 18(3)); lifting sends no email.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/liftRestriction" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"subject": {
"identityId": "<identityId>"
}
}'iam.api.privacy.liftRestriction(
credential: CredentialInput,
input: { tenantId: string; subject: SubjectInput },
): Promise<{ lifted: boolean }>linkRequest
Links a request known only by an email address to the account or application subject it is about.
- Permission:
iam:privacy:handleon the request, andiam:identities:readon the account when linking to one. - Audited as:
iam:privacy:handle, plusprivacy:request:linkwithsubjectKind(accountorexternal). - Errors:
CONFLICT(409) when the request already has a subject;INVALID_TRANSITION(409) when it is closed;NOT_FOUNDfor an unknown request or identity;ACCESS_DENIEDwithoutiam:identities:read.
A public request from an address that matches no account, or a staff request filed with only a requesterEmail,
cannot be fulfilled until the handler has established who it is about: answering "nothing held" while an account
exists under another address would be wrong. Link it, or decline it with rejectRequest and reason
no-data. The optional note goes on the timeline.
await iam.api.privacy.linkRequest(handler, {
tenantId,
requestId,
subject: { identityId },
note: 'Same person, second address',
});Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/linkRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"subject": {
"identityId": "<identityId>"
}
}'iam.api.privacy.linkRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string; subject: SubjectInput; note?: string },
): Promise<SubjectRequestView>listConsents
Lists current decisions, newest first, each with its state.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read. - Errors:
NOT_FOUNDfor an unknown purpose or identity;INVALID_INPUTfor alimitoutside 1 to 1000.
Filter by purposeKey, subject or granted; page with limit (100 by default) and offset. The result is total
and consents, each with its source, method, expiry, receipt id and state.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/listConsents" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.listConsents(
credential: CredentialInput,
input: {
tenantId: string;
purposeKey?: string;
subject?: SubjectInput;
granted?: boolean;
limit?: number;
offset?: number;
},
): Promise<{ total: number; consents: ConsentView[] }>listHolds
Lists the tenant's legal holds, newest first.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read.
Each hold carries its subject, reason, who placed it and when, expiresAt when set, and active (it has not
lapsed).
Prop
Type
An array of object.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/listHolds" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.listHolds(
credential: CredentialInput,
input: { tenantId: string },
): Promise<{
active: boolean;
subject: string;
identityId?: string;
externalId?: string;
reason: string;
placedBy: string;
placedAt: number;
expiresAt?: number;
id: string;
tenantId: string;
uniqueKey?: string;
}[]>listPurposes
Lists the tenant's purposes by name.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read.
Archived purposes are left out unless includeArchived: true.
Prop
Type
An array of PrivacyPurpose.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/listPurposes" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.listPurposes(
credential: CredentialInput,
input: { tenantId: string; includeArchived?: boolean },
): Promise<PrivacyPurpose[]>listRequests
Lists requests, open ones first by deadline, then the newest.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read. - Errors:
INVALID_INPUTfor an unknownstatusortype, or alimitoutside 1 to 500.
Filter by status, type, assigneeId, subject or overdue: true; page with limit (100 by default) and
offset. The result is total and requests, each with overdue and subjectName for an existing account.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/listRequests" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.listRequests(
credential: CredentialInput,
input: {
tenantId: string;
status?: SubjectRequest['status'];
type?: SubjectRequestType;
assigneeId?: string;
overdue?: boolean;
subject?: SubjectInput;
limit?: number;
offset?: number;
},
): Promise<{ total: number; requests: SubjectRequestView[] }>listRestrictions
Lists subjects whose processing is restricted, newest first.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read.
Suppression markers left by the erasure of an application subject are listed too, with erased: true.
Prop
Type
An array of PrivacyRestriction.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/listRestrictions" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.listRestrictions(
credential: CredentialInput,
input: { tenantId: string },
): Promise<PrivacyRestriction[]>mine
Returns your privacy page: every purpose with your decision and its effect, your requests, and the privacy contact.
Used inPrivacy and consent
- Permission: None beyond a person's own session of the tenant.
- Audited as: Not audited; it only reads.
- Errors:
ACCESS_DENIEDfor anything but a person's own session of the tenant;IMPERSONATION_RESTRICTEDwhile impersonating;TENANT_INACTIVEfor a suspended tenant.
purposes lists the live purposes, the ones you decide on first, each with decidable, state (allowed and
reason) and your consent when recorded. restricted says whether processing of your data is restricted.
requests shows your requests without internal notes and assignments. contact is the privacy contact, when set.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/mine" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.mine(
credential: CredentialInput,
input: { tenantId: string },
): Promise<{
contact?: { name?: string | undefined; email: string } | undefined;
purposes: MyPurpose[];
restricted: boolean;
requests: SubjectRequestView[];
}>myHistory
Lists your own recorded decisions, newest first.
- Permission: None beyond a person's own session of the tenant.
- Audited as: Not audited; it only reads.
At most 500 entries, optionally for one purposeKey, with the evidence, IP address and user agent recorded with each.
Prop
Type
An array of ConsentHistoryEntry.
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/myHistory" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.myHistory(
credential: CredentialInput,
input: { tenantId: string; purposeKey?: string },
): Promise<ConsentHistoryEntry[]>myReceipt
Returns the signed receipt of one of your own decisions.
- Permission: None beyond a person's own session of the tenant.
- Audited as: Not audited; it only reads.
- Errors:
NOT_FOUNDwhen the receipt is not one of your decisions.
Receipts are rebuilt from the history and signed with the current deployment secret, so this also gives a fresh copy after a secret rotation.
Prop
Type
A ConsentReceipt object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/myReceipt" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"receiptId": "<receiptId>"
}'iam.api.privacy.myReceipt(
credential: CredentialInput,
input: { tenantId: string; receiptId: string },
): Promise<ConsentReceipt>placeHold
Places a legal hold on a subject: erasure, and every deletion of their account, is refused until it is released.
- Permission:
iam:privacy:manageon the tenant. - Audited as:
iam:privacy:manage, plusprivacy:hold:placewith the hold id and expiry. - Errors:
NOT_FOUNDfor an unknown or deleted identity;INVALID_INPUTfor an empty reason or one over 2000 characters, or anexpiresAtthat is not in the future or is more than ten years away.
While a hold is live, fulfilRequest refuses erasure and identity deletion refuses the person (LEGAL_HOLD, 409),
whether an administrator (identities.delete) or an erasure request asks; offboarding and SCIM deprovisioning only
disable accounts, so they keep the data anyway. Holds do not stop processing or other requests. expiresAt makes the
hold lapse on its own.
await iam.api.privacy.placeHold(admin, {
tenantId,
subject: { identityId },
reason: 'Litigation 2026-17',
});Prop
Type
A PrivacyHold object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/placeHold" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"subject": {
"identityId": "<identityId>"
},
"reason": "<reason>"
}'iam.api.privacy.placeHold(
credential: CredentialInput,
input: {
tenantId: string;
subject: SubjectInput;
reason: string;
expiresAt?: number;
},
): Promise<PrivacyHold>record
Records a consent decision for a person of the tenant or an application subject, and returns its receipt.
Used inPrivacy and consent
- Permission:
iam:privacy:recordon the tenant. - Audited as:
iam:privacy:record, plusprivacy:consentwith the purpose key,granted, version, source, receipt id andexternalId. - Errors:
INVALID_INPUTfor a non-booleangranted, arecordedAtwithoutsource: 'import', an import withoutversion, aversionabove the current one, a malformed subject,methodorevidence, or a purpose with a basis other than consent or legitimate interests;CONFLICT(409) for a grant on an archived purpose;NOT_FOUNDfor an unknown purpose or identity.
source is api (the default), admin for a decision captured elsewhere, or import for one moved from another
system with its original recordedAt and version. ip and userAgent record where the decision was captured. The
result is the receipt plus current, false when an older imported decision did not replace a newer one. Server code
records decisions without a credential with iam.privacy.record, audited with actor application.
await iam.api.privacy.record(apiKey, {
tenantId,
subject: { externalId: 'cus_100' },
purposeKey: 'marketing-email',
granted: true,
method: 'banner',
ip: '203.0.113.9',
});Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/record" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"subject": {
"identityId": "<identityId>"
},
"purposeKey": "<purposeKey>",
"granted": true
}'iam.api.privacy.record(
credential: CredentialInput,
input: {
tenantId: string;
subject: SubjectInput;
purposeKey: string;
granted: boolean;
version?: number;
source?: 'api' | 'admin' | 'import';
method?: string;
evidence?: string;
recordedAt?: number;
ip?: string;
userAgent?: string;
},
): Promise<{
current: boolean;
version: 1;
receiptId: string;
tenantId: string;
subject: string;
purpose: { key: string; name: string; version: number; legalBasis: LegalBasis };
granted: boolean;
recordedAt: number;
source: ConsentSource;
method?: string;
signature: string;
}>rejectRequest
Declines an open or unverified request with a reason the subject is emailed.
- Permission:
iam:privacy:handleon the request. - Audited as:
iam:privacy:handle, plusprivacy:request:rejectwith the reason. - Errors:
INVALID_TRANSITION(409) when the request is closed;INVALID_INPUTfor an unknown reason or a note over 2000 characters.
Reasons are unverified, unfounded, excessive, exempt (for example a legal obligation to keep the data, or a
legal hold), duplicate, no-data (nothing is held about the person) and other. The email names the reason and
tells the person they may complain to a data protection authority; the optional note appears on their privacy page.
Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/rejectRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"reason": "duplicate"
}'iam.api.privacy.rejectRequest(
credential: CredentialInput,
input: {
tenantId: string;
requestId: string;
reason: RejectionReason;
note?: string;
},
): Promise<SubjectRequestView>releaseHold
Releases a legal hold.
- Permission:
iam:privacy:manageon the hold. - Audited as:
iam:privacy:manage, plusprivacy:hold:release. - Errors:
NOT_FOUNDwhen the hold is not in this tenant.
Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/releaseHold" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"holdId": "<holdId>"
}'iam.api.privacy.releaseHold(
credential: CredentialInput,
input: { tenantId: string; holdId: string },
): Promise<{ released: boolean }>submitPublic
Files a request from someone without an account, from a public form, when the organization turned on public intake.
- Permission: None: public, tenant-bound (on an organization's own address
tenantIdmay be left out). - Audited as:
privacy:request:submitwith channelpublic, actorpublic-intake. - Errors:
NOT_FOUNDwhilepublicIntakeis off or the tenant is inactive;DELIVERY_REQUIREDwithout an email delivery callback;RATE_LIMITED(429) past 3 requests per address or 500 per organization in a rate-limit window (and the per-IP limit whenrateLimits.ipAttemptsis set);INVALID_INPUTfor a malformed email, name,externalIdor details.
The requester is emailed a confirmation link (privacy-request-verify) and nobody handles the request until they
confirm it with confirmPublic; unconfirmed requests lapse after seven days. The result is only the
reference number and status. externalId names the identifier the application knows the requester by; such a
request also needs a handler's verifyRequest.
await client.privacy.submitPublic({ tenantId, type: 'access', email: 'alice@example.com' });Prop
Type
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/submitPublic" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"type": "access",
"email": "<email>"
}'iam.api.privacy.submitPublic(
input: {
tenantId: string;
type: SubjectRequestType;
email: string;
name?: string;
externalId?: string;
regulation?: Regulation;
details?: string;
purposeKeys?: string[];
},
): Promise<{ number: string; status: SubjectRequestStatus }>submitRequest
Files a data-subject request for yourself; signing in verified who you are, so its deadline starts at once.
- Permission: None beyond a person's own session of the tenant.
- Audited as:
privacy:request:submitwith channelself-service. - Errors:
CONFLICT(409) when you already have an open request of this type;NOT_FOUNDfor an unknown purpose key;INVALID_INPUTfor an unknowntypeorregulation, details over 5000 characters or more than 50purposeKeys.
The request opens with verification method authenticated-session, and the privacy contact is emailed.
purposeKeys names the purposes an objection or opt-out is about. The result is the request as you see it in mine.
await iam.api.privacy.submitRequest(session, { tenantId, type: 'access' });Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/submitRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"type": "access"
}'iam.api.privacy.submitRequest(
credential: CredentialInput,
input: {
tenantId: string;
type: SubjectRequestType;
regulation?: Regulation;
details?: string;
purposeKeys?: string[];
},
): Promise<SubjectRequestView>summary
Returns consent counts per purpose, the request queue, holds and restrictions at a glance.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read.
Per purpose: subjects it may be processed for on the strength of a recorded decision (granted), withdrawn
(including objections), expired and outdated. Requests: pendingVerification, open, overdue, dueSoon
(within seven days), completedLast30Days, medianDaysToClose over the last 90 days, and open requests byType.
Plus live holds and restrictions.
Prop
Type
A PrivacySummary object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/summary" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.summary(
credential: CredentialInput,
input: { tenantId: string },
): Promise<PrivacySummary>updatePurpose
Edits a purpose, optionally publishing the change as a new version or archiving it.
- Permission:
iam:privacy:manageon the purpose. - Audited as:
iam:privacy:manage. - Errors:
INVALID_INPUTfor a changedkey, a changedlegalBasisormodewithoutnewVersion: true, or the validation ofcreatePurpose;NOT_FOUNDwhen the purpose is not in this tenant.
Fields you leave out keep their values, and null clears retentionDays or consentLifetimeDays. newVersion: true
increments the version: opt-in grants for older versions stop counting (unless reconsentOnVersion is false) and
decide needs the new version. archived: true stops all processing for the purpose and hides it from people; the
decisions stay.
await iam.api.privacy.updatePurpose(admin, {
tenantId,
purposeId,
description: 'Product news, offers and partner offers by email.',
newVersion: true,
});Prop
Type
A PrivacyPurpose object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/updatePurpose" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"purposeId": "<purposeId>"
}'iam.api.privacy.updatePurpose(
credential: CredentialInput,
input: Partial<Omit<PurposeInput, 'retentionDays' | 'consentLifetimeDays'>> & {
tenantId: string;
purposeId: string;
newVersion?: boolean;
archived?: boolean;
retentionDays?: number | null;
consentLifetimeDays?: number | null;
},
): Promise<PrivacyPurpose>updateSettings
Changes the privacy contact, default regulation, internal response windows, public intake and export lifetime.
- Permission:
iam:privacy:manageon the tenant. - Audited as:
iam:privacy:manage. - Errors:
INVALID_INPUTfor a malformed email, an unknown regulation, aresponseDaysvalue outside 1 to the statutory window, or anexportLifetimeDaysoutside 1 to 90;DELIVERY_REQUIREDwhen turning onpublicIntakewithout an email delivery callback.
contactEmail (the data protection officer or privacy team) receives new requests and deadline reminders and is shown
to people; null clears it, contactName and responseDays. responseDays sets shorter internal windows per
regulation, never longer than the law's.
await iam.api.privacy.updateSettings(admin, {
tenantId,
contactEmail: 'dpo@acme.test',
responseDays: { gdpr: 20 },
publicIntake: true,
});Prop
Type
A PrivacySettingsView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/updateSettings" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>"
}'iam.api.privacy.updateSettings(
credential: CredentialInput,
input: {
tenantId: string;
contactEmail?: string | null;
contactName?: string | null;
defaultRegulation?: Regulation;
responseDays?: Partial<Record<Regulation, number>> | null;
publicIntake?: boolean;
exportLifetimeDays?: number;
},
): Promise<PrivacySettingsView>verifyReceipt
Checks a consent receipt someone presents against the signature and the history.
- Permission:
iam:privacy:readon the tenant. - Audited as:
iam:privacy:read.
The result is valid: true with current (still the subject's latest decision on the purpose) and redacted when
erasure redacted the entry, or valid: false with a reason: SIGNATURE_INVALID (not signed by this deployment for
this tenant, or altered), NOT_RECORDED or CONTENT_MISMATCH. Signatures made with any of previousSecrets still
verify during a secret rotation.
Prop
Type
One of object | object | object | object.
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/verifyReceipt" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"receipt": {
"version": 1,
"receiptId": "<receiptId>",
"tenantId": "<tenantId>",
"subject": "<subject>",
"purpose": {
"key": "<key>",
"name": "<name>",
"version": 1,
"legalBasis": "consent"
},
"granted": true,
"recordedAt": 1790000000000,
"source": "request",
"signature": "<signature>"
}
}'iam.api.privacy.verifyReceipt(
credential: CredentialInput,
input: { tenantId: string; receipt: ConsentReceipt },
): Promise<| { valid: false; reason: 'SIGNATURE_INVALID' }
| { valid: false; reason: 'NOT_RECORDED' }
| { valid: false; reason: 'CONTENT_MISMATCH' }
| {
redacted?: boolean | undefined;
valid: true;
current: boolean;
reason?: undefined;
}>verifyRequest
Records how the requester's identity was confirmed, which opens the request and starts its deadline.
- Permission:
iam:privacy:handleon the request. - Audited as:
iam:privacy:handle, plusprivacy:request:verifywith the method. - Errors:
INVALID_TRANSITION(409) when the request is notpending-verification;INVALID_INPUTfor an empty method or one over 120 characters, or a note over 2000.
The privacy contact is emailed once the request opens. Use it for staff-filed requests created without verified,
and for public requests naming an externalId once you have checked the requester owns it.
await iam.api.privacy.verifyRequest(handler, {
tenantId,
requestId,
method: 'Customer portal sign-in',
});Prop
Type
A SubjectRequestView object:
Prop
Type
Example HTTP request
Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.
curl -X POST "$IAM_URL/api/iam/privacy/verifyRequest" \
-H "Authorization: Bearer $BETTER_IAM_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Better-IAM: 1" \
-d '{
"tenantId": "<tenantId>",
"requestId": "<requestId>",
"method": "<method>"
}'iam.api.privacy.verifyRequest(
credential: CredentialInput,
input: { tenantId: string; requestId: string; method: string; note?: string },
): Promise<SubjectRequestView>Better IAM is created by Sean Filimon
Last updated