Skip to main content

Data Subject Access Requests (DSAR)

A Data Subject Access Request (also called a privacy request or consumer rights request) is the formal mechanism a person uses to exercise their rights over the personal data you hold about them — the right to access, delete, correct, port, or opt out of the sale of that data. Most privacy laws give you a hard deadline to respond (30 days under GDPR, 45 under CCPA/CPRA). Orbit gives you two intake paths and one fulfilment pipeline:
  • Operator-filed DSAR — your support or compliance team files a request on a customer’s behalf through the authenticated API or dashboard.
  • Public self-service portal — the data subject files their own request through a public, unauthenticated flow that proves their identity with a two-factor email + SMS OTP before anything is queued.
This page describes Orbit’s platform controls. It is not legal advice. Your obligations — which laws apply, what you must disclose, and how long you have — depend on where your data subjects live and what data you process. Confirm with qualified counsel.
All endpoints below are rooted at https://api.orbit.devotel.io/api/v1/compliance.

Supported jurisdictions and deadlines

The applicable_jurisdiction on a request controls which statutory clock Orbit’s SLA tracker applies. Operators can reclassify a request after intake.
JurisdictionCodeResponse SLA
EU / EEA GDPRgdpr30 days
California CCPAccpa45 days
California CPRAcpra45 days
Brazil LGPDlgpd15 days
Singapore / Thailand PDPApdpa30 days
Canada PIPEDApipeda30 days
India DPDPdpdp30 days

Request types

request_type describes what the subject is asking for. The full CCPA/CPRA verb set is available to operators; the public portal exposes a friendlier subset that maps onto it.
Operator request_typeMeaningPublic portal verb
knowAccess — disclose the data held (GDPR Art 15, CCPA §1798.110)access
deleteErasure (GDPR Art 17, CCPA §1798.105)delete
correctRectification (GDPR Art 16, CPRA §1798.106)
portabilityMachine-readable export (GDPR Art 20)portability
opt_out_saleOpt out of sale/share (CCPA §1798.120)opt_out
limit_sensitive_piLimit use of sensitive PI (CPRA §1798.121)
non_discriminationNon-discrimination right (CCPA §1798.125)
For CCPA access requests you may also attach consumer_categories — the CCPA §1798.100(b) categories the subject is asking about: identifiers, customer_records, protected_classifications, commercial, biometric, internet_activity, geolocation, sensory, professional, education, inferences, sensitive_pi.

Operator-filed requests

Create a request

POST /compliance/dsar — requires an admin or owner API key. Provide at least one subject identifier (contact_id, subject_email, or subject_phone) plus the requester_email that should receive correspondence.
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/dsar \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_email": "jordan@example.com",
    "requester_email": "jordan@example.com",
    "applicable_jurisdiction": "gdpr",
    "request_type": "know",
    "verification_method": "email_link"
  }'
Returns 202 Accepted:
{
  "id": "dsar_8x2k…",
  "status": "received",
  "applicable_jurisdiction": "gdpr",
  "request_type": "know",
  "verification_status": "pending",
  "message": "Request received and queued for verification."
}
FieldTypeNotes
contact_idstringOptional. Links the request to a known contact.
subject_emailemailOne of email / phone / contact_id is required.
subject_phonestringE.164.
requester_emailemailRequired. Where status updates are sent.
applicable_jurisdictionenumDefault gdpr.
request_typeenumDefault know.
consumer_categoriesstring[]CCPA categories (access only).
verification_methodenumemail_link, email_phone, document, manual_review.
requester_statementstringFree text, ≤ 4096 chars.
authorized_agentobject{ agent_name, agent_email, permission_document_id? } when an agent files on the subject’s behalf.

Status lifecycle

A request moves through: receivedprocessingcompleted with terminal branches failed, expired, and cancelled. The verification sub-state is tracked independently: pendingverified (worker proceeds) or rejected (worker halts). GDPR/admin-filed rows default to not_required.

Verify or reject identity

Higher-assurance requests (delete, opt-out, limit-sensitive) require an operator decision before fulfilment proceeds:
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/dsar/dsar_8x2k…/verification \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "decision": "verified", "notes": "Matched gov-ID upload." }'
decision is verified or rejected; notes is optional (≤ 2048 chars). Returns the new verification_status and verified_at.

Cancel a request

POST /compliance/dsar/{id}/cancel withdraws an in-flight request (GDPR Art 7(3)). Only works while the request is received or processing; a terminal request returns 409 Conflict.
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/dsar/dsar_8x2k…/cancel \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Duplicate of dsar_7a1f…" }'

List and read requests

  • GET /compliance/dsar — paginated list. Query: page (≥ 1), page_size (≤ 100, default 25), and an optional status filter.
  • GET /compliance/dsar/{id} — fetch one request. The response includes the signed export_url (and its export_expires_at) once an access/portability export has been produced, plus tables_exported describing the per-table row counts.

Erasure requests

GDPR Art 17 erasures are tracked as their own resource so you can audit and intervene before data is destroyed:
  • GET /compliance/dsar/erasure-requests — list. Query: status (pending, cancelled, executing, executed, failed) and limit (≤ 500).
  • POST /compliance/dsar/erasure-requests/{id}/cancel — cancel a pending erasure before it executes. Optional reason (≤ 500 chars). Returns 409 if it is already executing or done.

SLA dashboard

GET /compliance/dsar/sla returns a combined export + erasure SLA snapshot so you never miss a statutory deadline:
{
  "items": [
    {
      "id": "dsar_8x2k…",
      "kind": "export",
      "status": "processing",
      "days_elapsed": 22,
      "days_remaining": 8,
      "severity": "amber",
      "sla_deadline_at": "2026-07-01T00:00:00.000Z",
      "approaching": true,
      "breach": false,
      "escalation_due": false
    }
  ],
  "alerts": {
    "breached": 0,
    "approaching": 1,
    "escalation_due": 0,
    "worst_severity": "amber",
    "has_alert": true
  },
  "sla_days": 30
}
Severity tiers: green (< 20 days elapsed), amber (20–25), red (26–30), red + breach (> 30). escalation_due flips at day 25.

Public self-service portal

The public flow lets a data subject file a request without an account. Identity is proven with a two-factor OTP — an email code and an SMS code — before any request is queued. The endpoints live under /compliance/public/dsar and are unauthenticated, but are defended by Cloudflare Turnstile, per-IP and per-identifier rate limits, and a privacy-preserving response shape that never reveals whether an email/phone pair matches a real contact.
SMS verification codes are delivered through the Devotel softswitch (the platform’s sole outbound SMS path). They are platform OTPs, not tenant-billable traffic, and carry no delivery-receipt persistence.

Flow overview

1

Begin

POST /compliance/public/dsar/begin with email, phone (E.164), request_type (access | delete | portability | opt_out), and a Cloudflare turnstile_token (required in production). Returns an opaque claim_id, email_sent: true, and expires_in: 600. An email OTP is sent immediately.
2

Verify email

POST /compliance/public/dsar/verify-email with claim_id and the 6-digit code. Returns state email_verified and next step phone_send. Codes expire after 10 minutes; max 3 attempts. POST …/resend-email (with claim_id + email) issues a new code, subject to a 60-second cooldown.
3

Send phone code

POST /compliance/public/dsar/send-phone with claim_id and the phone that matches the one given at begin. Sends an SMS OTP (expires_in: 600). A 60-second cooldown applies between sends; a too-soon retry returns 429 with Retry-After.
4

Verify phone

POST /compliance/public/dsar/verify-phone with claim_id and the 6-digit code. Returns state phone_verified and next step submit.
5

Submit

POST /compliance/public/dsar/submit with claim_id. Persists an audit row and — only if the verified email + phone match a contact in your tenant — queues a real DSAR (pre-marked verification_status: verified, since the OTP already proved identity). Returns a reference_id (e.g. dsar_pub_…) and a queued boolean.

Abuse defenses

ControlLimit
Cloudflare TurnstileRequired on begin in production (fail-closed).
Per-IP begin3 per hour.
Per-email cooldown1 per 60 s.
Per-phone SMS cooldown1 per 60 s.
Fastify per-IP gate30 requests/min across all public endpoints.
OTP TTL / attempts10 minutes, max 3 attempts per code.
Claim TTL30 minutes end-to-end.
The response shape is identical whether or not the identifiers match a real contact — the portal never confirms or denies that someone is in your database. When Redis is unavailable the rate-limit gates fail open to preserve availability.
Publish the public portal under your privacy policy as the “Submit a privacy request” link. Because the flow self-verifies via OTP, requests that arrive through it are already identity-proven — they land in your operator queue ready to fulfil, and appear in GET /compliance/dsar alongside operator-filed requests.