Skip to main content
Home›Docs›API Reference›Privacy and consent
API Reference

Privacy and consent

DSAR privacy requests (export/delete), consent records, recording-consent tracking, and Terms of Service / Privacy Policy acceptance.

Privacy and consent

These endpoints cover data-subject (DSAR) privacy requests (data export and account deletion), consent record management, per-subject recording consent for playbooks, and Terms of Service / Privacy Policy acceptance tracking. Unless noted otherwise, endpoints are user-scoped: authenticate with X-API-Key: pk_live_xxxxxxxxxxxx or Authorization: Bearer <jwt>.

Privacy requests (DSAR)

A privacy request tracks one export or deletion job through its lifecycle. Request object fields:

Field Type Description
id string (UUID) Request identifier
user_id string Owner of the request
request_type string export or deletion
status string pending, processing, completed, failed, or cancelled
regulation string | null gdpr, ccpa, cpra, or null
requested_at string (ISO 8601) Creation time
acknowledged_at string | null When processing started
completed_at string | null When the request completed
expires_at string | null Compliance deadline (see below)
processed_by string | null Admin who processed the request
notes string | null Processing notes

expires_at is set at creation from the regulation deadline: 30 days for gdpr, 45 days for ccpa/cpra, 30 days otherwise.

POST /v1/privacy/export

Create a data-export request. The export is prepared asynchronously; download it once the status reaches completed.

Request body — regulation is optional (gdpr, ccpa, or omit):

{
  "regulation": "gdpr"
}
curl -X POST https://api.perceive8.com/v1/privacy/export \
  -H "X-API-Key: pk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"regulation": "gdpr"}'

Response 201 Created

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "user_id": "u_abc123",
  "request_type": "export",
  "status": "pending",
  "regulation": "gdpr",
  "requested_at": "2026-01-15T10:30:00+00:00",
  "acknowledged_at": null,
  "completed_at": null,
  "expires_at": "2026-02-14T10:30:00+00:00",
  "processed_by": null,
  "notes": null
}

POST /v1/privacy/delete

Create an account-deletion request. Only one pending or processing deletion request is allowed per account.

Request body

{
  "regulation": "gdpr",
  "confirmation": "DELETE MY ACCOUNT"
}

confirmation must be exactly "DELETE MY ACCOUNT" (400 otherwise). Returns 409 if a deletion request is already in flight.

Response 201 Created — a privacy request object with request_type: "deletion".

GET /v1/privacy/requests

List all privacy requests for the authenticated user.

Response 200 OK — array of privacy request objects.

GET /v1/privacy/requests/{request_id}

Get the status of one privacy request. Returns 404 if the request does not exist or belongs to another user.

Parameters

Name In Type Description
request_id path string (UUID) Privacy request identifier

Response 200 OK — a privacy request object.

POST /v1/privacy/requests/{request_id}/cancel

Cancel a privacy request. Only pending requests can be cancelled (404 otherwise).

Parameters

Name In Type Description
request_id path string (UUID) Privacy request identifier

Response 200 OK — the privacy request object with status: "cancelled".

GET /v1/privacy/export/{request_id}/download

Get the download URL for a completed export. Returns 400 if the request is not an export or is not yet completed; 404 if the request or export file is not available.

Parameters

Name In Type Description
request_id path string (UUID) Export request identifier

Response 200 OK — object with download_url (string), file_size (integer or null), and request_id (string UUID).

Consent records

Consent records are immutable entries capturing a grant or withdrawal of consent for a given type (for example tos, privacy_policy, audio_processing). Consent record fields:

Field Type Description
id string (UUID) Consent record identifier
consent_type string Consent category
consent_version string Version string, e.g. "1.0"
granted boolean true for a grant, false for a withdrawal
granted_at string (ISO 8601) When the record was created
withdrawn_at string | null When consent was withdrawn
source string | null Origin of the record ("api" for records created via the API)

GET /v1/privacy/consents

List all consent records for the authenticated user.

Response 200 OK — array of consent record objects.

POST /v1/privacy/consents

Record a consent grant or withdrawal. The server also stores the client IP address and User-Agent header with the record.

Request body

{
  "consent_type": "audio_processing",
  "consent_version": "1.0",
  "granted": true
}

Response 201 Created — a consent record object.

GET /v1/privacy/consents/status

Summarize active consent for the authenticated user.

Response 200 OK — {"consents": {"<consent_type>": true | false}}: each consent type maps to whether the user currently has an active grant for it.

Combined legal-document status

GET /v1/consent/status

Return Terms of Service and Privacy Policy acceptance status in a single call, so a client can gate access with one request. If no current version of a document exists, that document counts as accepted.

Response 200 OK — top-level fields:

Field Type Description
tos object Terms of Service status (item fields below)
privacy_policy object Privacy Policy status (item fields below)
all_accepted boolean true only when neither document needs acceptance

Each status item has needs_acceptance (boolean), current_version, current_title, accepted_version (string or null), and accepted_at (ISO 8601 or null).

Recording consent

Per-subject recording consent for playbooks (for example HIPAA-adjacent use cases). These endpoints require the workspace admin role (owner also passes). The workspace is taken from the X-Workspace-Id header (or the workspace_id query parameter); when omitted, the caller's Personal workspace is used.

Consent subjects have a lifecycle status of active, revoked, pending, or expired. Every status change is written to an immutable per-subject audit log.

Template object fields: id (UUID), workspace_id (UUID), playbook_id (string), template_text (string), created_at, updated_at (ISO 8601).

GET /v1/consent/templates

List consent templates for the current workspace, most recently updated first.

Response 200 OK — array of template objects.

POST /v1/consent/templates

Create a consent template for a playbook. If a template already exists for the workspace and playbook_id, its text is updated instead (upsert).

Request body

{
  "playbook_id": "clinical-intake",
  "template_text": "I consent to the recording and processing of this call..."
}

Response 200 OK — the created or updated template object.

Subject object fields:

Field Type Description
id string (UUID) Subject identifier
workspace_id string (UUID) Owning workspace
subject_ref string Your identifier for the subject (max 255 chars)
playbook_id string Playbook the consent applies to (max 100 chars)
status string active, revoked, pending, or expired
consented_at string | null When consent was granted
revoked_at string | null When consent was revoked
expires_at string | null When consent expires
created_at, updated_at string (ISO 8601) Creation and last-update times

GET /v1/consent/subjects

List consent subjects for the current workspace, newest first.

Parameters

Name In Type Description
playbook_id query string Optional filter by playbook

Response 200 OK — array of subject objects.

POST /v1/consent/subjects

Record consent for a new subject. The subject is created with status active and consented_at set to now; an audit entry with action consented is written.

Request body — expires_at is optional:

{
  "subject_ref": "patient-12345",
  "playbook_id": "clinical-intake",
  "expires_at": "2027-01-15T10:00:00+00:00"
}

Response 200 OK — the created subject object.

PATCH /v1/consent/subjects/{subject_id}

Update a subject's consent status. Returns 400 for an invalid status; 404 if the subject does not exist in the current workspace.

Parameters

Name In Type Description
subject_id path string (UUID) Consent subject identifier

Request body — status must be one of active, revoked, pending, expired:

{
  "status": "revoked",
  "expires_at": null
}

Side effects per status (an audit entry whose action is the new status is written on every update):

Status Effect
active Sets consented_at to now, clears revoked_at, applies expires_at from the body
revoked Sets revoked_at to now, clears expires_at
expired Sets expires_at to now
pending Clears consented_at and revoked_at

Response 200 OK — the updated subject object.

GET /v1/consent/subjects/{subject_id}/audit

Return the audit log for a single subject, newest entry first. Returns 404 if the subject does not exist in the current workspace.

Parameters

Name In Type Description
subject_id path string (UUID) Consent subject identifier

Response 200 OK — array of audit entries:

Field Type Description
id string (UUID) Audit entry identifier
consent_subject_id string (UUID) Subject the entry belongs to
action string consented for creation, or the new status (active, revoked, pending, expired) for updates
actor_id string (UUID) | null User who performed the action
timestamp string (ISO 8601) When the action occurred
details object | null Status-specific metadata (e.g. expires_at)

Terms of Service

GET /v1/tos/current

Return the current Terms of Service version. Public — no authentication required. Returns 404 if no current version exists.

Response 200 OK

{
  "id": "8e7d6c5b-4a3f-4e2d-9c1b-0a9f8e7d6c5b",
  "version": "2.1",
  "title": "Terms of Service",
  "effective_date": "2026-01-01T00:00:00+00:00",
  "is_current": true,
  "changelog": "Updated data-processing terms"
}

GET /v1/tos/acceptance-status

Check whether the authenticated user has accepted the current Terms of Service. If no current version exists, the response reports accepted: true and needs_acceptance: false.

Response 200 OK

{
  "accepted": false,
  "current_version": "2.1",
  "accepted_at": null,
  "needs_acceptance": true
}

POST /v1/tos/accept

Accept the current Terms of Service version. The server records the client IP address and User-Agent header. Returns 400 if version does not match the current version and 409 if the current version was already accepted.

Request body

{
  "version": "2.1"
}

Response 201 Created — { "accepted": true, "version": "2.1" }.

GET /v1/tos/history

Return the authenticated user's Terms of Service acceptance history, newest first.

Response 200 OK — array of items with version, title, accepted_at (ISO 8601), and ip_address (string or null).

Privacy Policy

These endpoints mirror the Terms of Service endpoints for the Privacy Policy document; request and response shapes are identical.

GET /v1/privacy-policy/current

Return the current Privacy Policy version. Public — no authentication required. Returns 404 if no current version exists.

Response 200 OK — a version object with the same fields as GET /v1/tos/current.

GET /v1/privacy-policy/acceptance-status

Check whether the authenticated user has accepted the current Privacy Policy. If no current version exists, the response reports accepted: true and needs_acceptance: false.

Response 200 OK — same shape as TOS acceptance status: accepted, current_version, accepted_at, needs_acceptance.

POST /v1/privacy-policy/accept

Accept the current Privacy Policy version. Records the client IP address and User-Agent header. Returns 400 on version mismatch and 409 if the current version was already accepted.

Request body

{
  "version": "1.4"
}

Response 201 Created — { "accepted": true, "version": "1.4" }.

GET /v1/privacy-policy/history

Return the authenticated user's Privacy Policy acceptance history, newest first.

Response 200 OK — array of items with version, title, accepted_at, and ip_address, same shape as TOS history.

Admin endpoints

Administrative endpoints for managing TOS and Privacy Policy versions, listing and processing DSAR requests, and configuring data-retention jobs exist under /v1/admin/tos, /v1/admin/privacy-policy, and /v1/admin/privacy. They are restricted to server-configured admin accounts and are not part of the public integration surface, so they are not documented here.