API Reference

Complete REST API for DPDPA Shield. All endpoints return JSON. Requires HTTPS.

Base URL

https://api.dpdpashield.in

Response Envelope

Every response is wrapped in a consistent envelope.

// Success
{ "data": { ... } }

// Error
{
  "error": {
    "message": "Human-readable description",
    "code": "MACHINE_READABLE_CODE",
    "fields": { "fieldName": "validation message" }
    // fields only present on VALIDATION_ERROR responses
  }
}

Authentication

DPDPA Shield uses two authentication methods depending on the endpoint type.

Method 1 - Bearer Token (Dashboard API)

Used for all protected dashboard endpoints. Obtain a token by logging in via POST /api/v1/auth/login. Access tokens expire in 15 minutes; use the refresh token to obtain a new one.

// Send on every request:
Authorization: Bearer <your-jwt-token>

// Obtain a token:
POST /api/v1/auth/login
{
  "email": "dpo@yourcompany.com",
  "password": "..."
}
// Response: { "data": { "token": "eyJ...", "refreshToken": "..." } }

// Refresh when expired:
POST /api/v1/auth/refresh
{ "refreshToken": "..." }
// Response: { "data": { "token": "eyJ..." } }

Method 2 - API Key (SDK / Public Endpoints)

Used by the JavaScript SDK and public-facing endpoints. Passed as a request body field or query parameter. Generate keys in Settings β†’ API Keys.

// As a request body field (SDK endpoints):
{
  "apiKey": "dpdpa_live_YOUR_KEY",
  ...
}

// As a query parameter (GET endpoints):
GET /api/v1/consent/public-notice?apiKey=dpdpa_live_YOUR_KEY

SDK Domain Allowlist & Rate Limits

Because SDK keys are public (embedded in browser HTML), three layers prevent abuse:

  • 1. Origin allowlist. Every public SDK call must come from a domain registered under the API key. Register domains in Settings β†’ API Keys β†’ Domains. The request Origin header (falling back to Referer) is matched against the list. Mismatches return 403 ORIGIN_NOT_ALLOWED. Keys with zero registered domains are unenforced (allow-all) until the first domain is added - a one-time grace state for new tenants.
  • 2. Per-apiKey rate limits. 600 requests / minute per (apiKey, IP) and 5000 requests / minute per apiKey globally, applied to /public-notice, /sdk-record, /withdraw-public. Excess returns 429 SDK_RATE_LIMIT with a Retry-After header.
  • 3. localhost exempt. localhost and 127.0.0.1 are always allowed for local development. Vercel / Amplify preview hosts must be registered explicitly.

Manage domains: GET / POST /api/v1/api-keys/:keyId/domains Β·DELETE /api/v1/api-keys/:keyId/domains/:domainId.

Which auth method for which endpoint?

Auth methodEndpoints
Bearer tokenAll /api/v1/* dashboard endpoints
API key/consent/public-notice
/consent/sdk-record
/consent/withdraw-public
No auth/consent/portal/:token
/consent/portal/:token/withdraw

Rate Limits

Endpoint groupLimit
Bearer token endpoints100 req/min per tenant
SDK endpoints (sdk-record, public-notice)1,000 req/min per API key
Withdrawal endpoint (withdraw-public)5 req/hour per IP

Notice Endpoints

Manage consent notices - the legal disclosure documents shown to data principals. Notices follow a state machine: DRAFT β†’ PUBLISHED β†’ ARCHIVED. Publishing auto-archives the previous published notice.

GET/api/v1/notices
Bearer

List all notices for the tenant. Includes consent count per notice.

ParameterTypeRequiredDescription
includeArchivedbooleanOptionalInclude ARCHIVED notices in resultsDefault: false
POST/api/v1/notices
BearerRoles: DPO, SUPER_ADMIN

Create a new DRAFT consent notice.

ParameterTypeRequiredDescription
titlestringRequiredNotice title (3–200 chars)
introTextstringRequiredIntroduction text explaining data processing (20–1000 chars)
contactEmailstringRequiredDPO or support email for data principal queries
contactUrlstringOptionalPrivacy policy URL
rightsTextstringRequiredText explaining data principal rights (20–500 chars)
complaintTextstringRequiredInstructions for raising grievances (20–500 chars)
withdrawalUrlstringOptionalURL for consent withdrawal (DPDPA Rule 3 disclosure)
purposeIdsstring[]RequiredArray of processing purpose UUIDs to include (min 1)
purposeConfigPurposeConfig[]OptionalPer-purpose display config: { purposeId, isRequired, displayOrder }
translationsRecord<lang, TranslationFields>OptionalLanguage translations map. Keys are language codes (HI, TA, …)
GET/api/v1/notices/active
Optional authBearer token OR X-Tenant-Id header

Returns the currently published notice. Used by the dashboard notice card.

GET/api/v1/notices/:id
Bearer

Fetch a single notice by ID with full purpose config and translation data.

PUT/api/v1/notices/:id
BearerRoles: DPO, SUPER_ADMIN - DRAFT only

Update a DRAFT notice. All fields are optional (partial update). Returns 409 if notice is PUBLISHED or ARCHIVED - duplicate to a new draft instead.

POST/api/v1/notices/:id/publish
BearerRoles: DPO, SUPER_ADMIN

Publish a DRAFT notice. Atomically archives any existing published notice. Returns materialChangeDetected flag for re-consent campaign prompting.

ParameterTypeRequiredDescription
changeNotestringOptionalInternal change summary stored in the version snapshot (max 500 chars)
Response
{
  "data": {
    "id": "uuid",
    "status": "PUBLISHED",
    "versionNumber": 3,
    "materialChangeDetected": true,
    "materialChanges": [
      { "field": "purposes", "type": "added", "isMaterial": true, "newValue": "Marketing" }
    ],
    "fromVersionNumber": 2,
    "toVersionNumber": 3
  }
}
POST/api/v1/notices/:id/archive
Bearer

Archive or delete a notice. Behaviour depends on consent history: DRAFT with no consents is hard-deleted; notices with consent records are soft-archived to preserve DPDPA Section 6(10) proof evidence.

Response
{
  "data": {
    "action": "ARCHIVED",  // or "DELETED"
    "noticeId": "uuid",
    "consentCount": 1240,
    "message": "Notice archived and preserved for compliance records."
  }
}
GET/api/v1/notices/:id/versions
Bearer

Returns the full version history (immutable snapshots) for a notice, ordered by version number descending.

GET/api/v1/notices/:id/versions/diff
Bearer

Field-by-field diff between two notice versions. Returns hasMaterialChange: true if any purpose, data category, or legal basis changed - signals re-consent is needed.

ParameterTypeRequiredDescription
fromnumberRequiredVersion number to compare from (older)
tonumberRequiredVersion number to compare to (newer)
Response
{
  "data": {
    "from": 2,
    "to": 3,
    "hasMaterialChange": true,
    "totalChanges": 2,
    "changes": [
      {
        "field": "purposes",
        "type": "added",
        "isMaterial": true,
        "newValue": "Marketing Communications"
      },
      {
        "field": "title",
        "type": "modified",
        "isMaterial": false,
        "oldValue": "Privacy Notice v2",
        "newValue": "Privacy Notice v3"
      }
    ]
  }
}
GET/api/v1/notices/:id/preview
Bearer

Preview how a notice will render in a given language. Returns translated fields if available, falls back to English.

ParameterTypeRequiredDescription
langstringOptionalLanguage code for translated preview (e.g. HI, TA)Default: EN
POST/api/v1/notices/:id/translations
Bearer

Add a translation for a specific language. Supports all 22 scheduled Indian languages per DPDPA.

ParameterTypeRequiredDescription
languagestringRequiredLanguage code (EN, HI, TA, TE, …)
titlestringOptionalTranslated notice title
introTextstringOptionalTranslated introduction text
rightsTextstringOptionalTranslated rights text
complaintTextstringOptionalTranslated complaint text
GET/api/v1/notices/:id/translations
Bearer

List all translations for a notice.

PUT/api/v1/notices/:id/translations/:lang
Bearer

Update an existing translation. :lang is the uppercase language code (e.g. HI, TA).

DELETE/api/v1/notices/:id/translations/:lang
Bearer

Remove a translation for a specific language.

POST/api/v1/notices/:id/auto-translate
Bearer

One-click auto-translate to all 22 scheduled Indian languages via google-translate-api-x. Translations can be reviewed and edited before publishing.

PATCH/api/v1/notices/:id/withdrawal-url
Bearer

Set or clear the DPDPA Rule 3 withdrawal URL on a notice - works on PUBLISHED notices without requiring a full draft-and-publish cycle.

ParameterTypeRequiredDescription
withdrawalUrlstring | nullOptionalFull URL to the withdrawal page. Pass null to clear.

Webhook Endpoints

Webhooks deliver real-time event notifications to your server via signed HTTP POST requests. Each delivery is signed with X-DPDPAShield-Signature (HMAC-SHA256) for verification. Failed deliveries are retried with exponential backoff.

GET/api/v1/webhooks
Bearer

List all webhook configurations for the tenant.

Response
{
  "data": [
    {
      "id": "uuid",
      "label": "Production Events",
      "url": "https://yourserver.com/webhooks/dpdpa",
      "events": ["consent.recorded", "consent.withdrawn"],
      "isActive": true,
      "createdAt": "2026-01-15T00:00:00.000Z"
    }
  ]
}
POST/api/v1/webhooks
BearerRoles: DPO, SUPER_ADMIN - plan limit applies

Create a new webhook. The secret is used to sign deliveries - store it securely and never expose it client-side.

ParameterTypeRequiredDescription
labelstringRequiredHuman-readable label (1–100 chars)
urlstringRequiredHTTPS endpoint to receive events
secretstringRequiredSigning secret for HMAC-SHA256 signature verification (16–256 chars)
eventsstring[]RequiredEvent types to subscribe: consent.recorded | consent.withdrawn | notice.published | rights.submitted | breach.created
Available event types
consent.recorded      // New consent decision recorded
consent.withdrawn     // Consent withdrawn by data principal
notice.published      // Consent notice published or updated
rights.submitted      // Data principal rights request submitted
breach.created        // Breach incident created
PUT/api/v1/webhooks/:id
Bearer

Update an existing webhook. All fields are optional. Use isActive: false to temporarily disable.

ParameterTypeRequiredDescription
labelstringOptionalNew label
urlstringOptionalNew endpoint URL
secretstringOptionalNew signing secret (16–256 chars)
eventsstring[]OptionalNew event types array
isActivebooleanOptionalEnable or disable the webhook
DELETE/api/v1/webhooks/:id
Bearer

Permanently delete a webhook configuration and its delivery history.

GET/api/v1/webhooks/:id/deliveries
Bearer

View delivery history for a webhook - useful for debugging failed deliveries.

Response
{
  "data": [
    {
      "id": "uuid",
      "event": "consent.recorded",
      "statusCode": 200,
      "deliveredAt": "2026-05-01T10:00:00.000Z",
      "attempts": 1,
      "success": true
    }
  ]
}
POST/api/v1/webhooks/:id/test
Bearer

Send a test event to the webhook URL to verify connectivity and signature verification. Sends a sample consent.recorded payload.

Verifying webhook signatures

// Each delivery includes:
// X-DPDPAShield-Signature: sha256=<hmac-hex>
// X-DPDPAShield-Event: consent.recorded
// X-DPDPAShield-Timestamp: 1746720000000

import { createHmac } from 'crypto'

function verifySignature(payload: string, signature: string, secret: string): boolean {
  const expected = createHmac('sha256', secret)
    .update(payload)
    .digest('hex')
  return `sha256=${expected}` === signature
}

Cookie Consent (Shield CMP) Endpoints

A separate SDK and endpoint family from the consent-notice widget above - covers cookies, tracking pixels, and third-party scripts. Full integration guide: /docs/cookie-consent. Bundled free on every standard plan; not available on the standalone Shield Collect plans.

SDK Endpoint - Public
GET/api/v1/cmp/config/:tenantId

Fetches the tenant's category config, tracker registry, and label sets. Called by the CMP SDK on load. Cached for 5 minutes (Cache-Control: public, max-age=300).

Response
{
  "data": {
    "tenantId": "uuid",
    "categories": {
      "NECESSARY": { "enabled": true, "toggleable": false },
      "ANALYTICS": { "enabled": true, "toggleable": true }
    },
    "defaultDeny": true,
    "childDirected": false,
    "bannerTheme": { "position": "bottom" },
    "languages": ["en"],
    "reconsentDays": 180,
    "scanEnabled": false,
    "scanUrls": [],
    "trackers": [],
    "labelSets": { "en": { "labels": { "...": "..." }, "status": "REVIEWED" } },
    "showPoweredBy": true,
    "brandName": null,
    "logoUrl": null
  }
}
SDK Endpoint - Public, rate-limited
POST/api/v1/cmp/web-event

Records a cookie-consent decision into the same audit trail (ConsentAuditLog, channel: WEB_COOKIE) used by notice-based consent. Rate limited to 60 requests/minute per (tenantId, IP).

ParameterTypeRequiredDescription
tenantIdstring (UUID)RequiredTenant identifying the banner - sent from the data-tenant-id attribute
consentRecordIdstring (UUID)OptionalThe visitor ID cookie value, if one already exists
eventstringRequiredCOOKIE_CONSENT_GIVEN | COOKIE_CONSENT_UPDATED | COOKIE_CONSENT_WITHDRAWN
categoriesRecord<string, boolean>RequiredPer-category grant/deny map (max 20 keys)
widgetVersionstringOptionalSDK build version string
Response (201)
{ "data": { "recorded": true, "consentRecordId": "visitor-uuid" } }
PUT/api/v1/cmp/config
BearerRoles: DPO, SUPER_ADMIN

Upsert the tenant's CMP config. Validates the merged state (existing + incoming fields) against child-directed rules before saving - see CHILD_DIRECTED_VIOLATION below.

ParameterTypeRequiredDescription
categoriesRecord<string, {enabled, toggleable}>OptionalKeys: NECESSARY | FUNCTIONAL | ANALYTICS | MARKETING | UNCLASSIFIED
defaultDenybooleanOptionalToggleable categories start unchecked until the visitor opts inDefault: true
childDirectedbooleanOptionalHard-blocks ANALYTICS and MARKETING (DPDPA S.9(3)) - rejects the request if either is set toggleable:true
bannerThemeobjectOptionalposition, primaryColor, backgroundColor, textColor, borderRadius, showLogo
languagesstring[]OptionalEnabled Eighth Schedule language codes (max 22)Default: ['en']
reconsentDaysnumberOptionalDays before the SDK re-prompts a returning visitor (30–730)Default: 180
scanEnabledbooleanOptionalTurns on the automated tracker scanner for this tenantDefault: false
scanUrlsstring[]OptionalURLs the scanner crawls (max 5). Falls back to Tenant.website if emptyDefault: []
GET/api/v1/cmp/trackers
PUT/api/v1/cmp/trackers
BearerGET: all roles Β· PUT: DPO, SUPER_ADMIN

List or batch-upsert (max 200 per call) the declared tracker registry. Trackers found by the automated scanner set declaredByAdmin: false; manually declared trackers set it true.

ParameterTypeRequiredDescription
trackersTracker[]RequiredArray (max 200) of { domain, cookieName?, category, vendor?, location?, declaredByAdmin? }
GET/api/v1/cmp/analytics
BearerRoles: DPO, SUPER_ADMIN, ANALYST, VIEWER

Accept/reject/partial rates, per-category opt-in rate, and a daily time series for the dashboard chart. Query params: from, to (max 365-day range).

Server-to-Server - API Key Auth
GET/api/v1/cmp/consent-status

Check a visitor's consent decision before firing a backend-triggered tag. X-API-Key header required. Rate limited to 600 requests/minute per API key.

ParameterTypeRequiredDescription
visitorIdstring (UUID)RequiredThe visitor's browser-cookie identifier (query parameter)
Response
{
  "data": {
    "visitorId": "uuid",
    "found": true,
    "categories": { "NECESSARY": true, "ANALYTICS": true, "MARKETING": false },
    "lastUpdated": "2026-07-01T10:00:00.000Z",
    "withdrawn": false
  }
}
// found: false β†’ treat every non-necessary category as false (do not fire)
Server-to-Server - API Key Auth
POST/api/v1/cmp/server-event-log

Log that your backend checked consent and whether it fired or suppressed a server-side tracker - produces audit evidence that server-side firing was consent-gated.

ParameterTypeRequiredDescription
visitorIdstring (UUID)RequiredSame visitor ID checked via /cmp/consent-status
trackerNamestringRequiredIdentifier for the tracker fired or suppressed (max 200 chars)
firedbooleanRequiredWhether the tracker was actually fired
categorystringRequiredNECESSARY | FUNCTIONAL | ANALYTICS | MARKETING | UNCLASSIFIED
POST/api/v1/cmp/scan-now
BearerRoles: DPO, SUPER_ADMIN - rate limited, 1 per 10 min

Triggers an on-demand headless crawl of your configured scanUrls. Returns 202 immediately with a scanRunId - poll GET /api/v1/cmp/scan-runs to check completion. A DPO alert email fires automatically only when the scan finds genuinely new, previously-undeclared trackers.

GET/api/v1/cmp/translations
POST/api/v1/cmp/translations/draft
POST/api/v1/cmp/translations/draft-all
PUT/api/v1/cmp/translations/:language
Bearer

GET /cmp/translations lists label sets; POST /cmp/translations/draft (body: { language }) AI-drafts one language; POST /cmp/translations/draft-all drafts all 22 in one call (capped at 3 runs/tenant/day); PUT /cmp/translations/:language saves edited labels and marks status: REVIEWED.

Error Codes

All errors follow the envelope format: { "error": { "code": "...", "message": "..." } }. On validation errors, a fields object is included with per-field messages.

HTTPCodeWhen it occurs
400VALIDATION_ERRORRequest body failed Zod schema validation. Check the fields object for per-field errors.
401UNAUTHORIZEDMissing, malformed, or expired Authorization header or API key.
401TOKEN_INVALIDJWT access token is invalid or expired. Refresh using /auth/refresh.
403FORBIDDENAuthenticated but insufficient role permissions for this operation.
403PLAN_LIMIT_REACHEDResource limit for your current plan has been reached (webhooks, notices, team members, etc.).
403FEATURE_NOT_AVAILABLEFeature is not available on your current plan. Upgrade required.
403PLAN_REQUIREDPhase 2 feature gate: plan or feature override required to access this endpoint.
404NOT_FOUNDThe requested resource does not exist or belongs to a different tenant.
409CONFLICTState conflict - e.g. publishing an already-published notice, or duplicate resource.
409ASSET_LINKED_TO_PUBLISHED_NOTICECannot delete a data asset that is linked to a published consent notice.
409PROCESSOR_HAS_LINKED_ASSETSCannot delete a processor that has data assets linked via the ProcessorAssets relation.
403ORIGIN_NOT_ALLOWEDThe request Origin/Referer hostname is not in the apiKey's SDK domain allowlist. Register the domain under Settings β†’ API Keys.
403ORIGIN_MISSINGThe request did not send an Origin or Referer header. Required for browser-based SDK endpoints.
429RATE_LIMITToo many requests from this IP. Applies to /consent/withdraw-public (5 req/hour).
429SDK_RATE_LIMITPer-apiKey rate limit exceeded on a public SDK endpoint. Limits: 600/min per (apiKey + IP), 5000/min per apiKey global. Returns Retry-After header.
429MONTHLY_LIMIT_REACHEDMonthly consent volume limit for the account has been reached. Resets next month.
404CMP_NOT_CONFIGUREDNo Cookie Manager config exists yet for this tenant, or the tenant is on a Shield Collect-only plan (Shield CMP is not included on those plans).
400CHILD_DIRECTED_VIOLATIONPUT /cmp/config set childDirected:true together with ANALYTICS or MARKETING toggleable:true - not allowed under DPDPA S.9(3).
429CMP_RATE_LIMITPOST /cmp/web-event exceeded 60 requests/minute for this (tenantId, IP) pair.
409SCAN_ALREADY_RUNNINGPOST /cmp/scan-now called while a scan for this tenant is already in progress (within the last 10 minutes).
429SCAN_RATE_LIMITPOST /cmp/scan-now called more than once per tenant per 10 minutes.
400NO_SCAN_URLSPOST /cmp/scan-now called with scanUrls empty and no Tenant.website set.
500INTERNAL_ERRORUnexpected server error. Contact support if this persists.