Skip to content

Latest commit

 

History

History
774 lines (657 loc) · 25.9 KB

api-documentation.md

File metadata and controls

774 lines (657 loc) · 25.9 KB

Secure CMS Cookie Consent Management Platform — Direct REST API Documentation

This documentation is designed for engineering teams integrating directly with the Secure CMS Cookie Consent Platform via REST APIs without using the JavaScript SDK.

Use direct API integration when building:

  • Mobile Applications (iOS Swift, Android Kotlin, Flutter, React Native).
  • Custom / Headless Web Frontends (Next.js Server Components, SvelteKit, Nuxt, custom design systems).
  • Backend & Microservice Architectures (APIs enforcing privacy consent before processing personal data).
  • Connected Devices & Smart TVs (OTT platforms, embedded web views).

📑 Table of Contents

  1. Platform Overview & Base URLs
  2. Direct API Integration Flow
  3. Authentication & Cryptographic Identity Model (RS256 & JWKS)
  4. Client Consent & Configuration REST APIs
  5. Admin & Management REST APIs
  6. Client-Side Implementation Guidelines (Without SDK)
  7. Code Integration Examples
  8. Client IdP Server Endpoints (JWKS Reference)
  9. Error Codes & Troubleshooting

1. Platform Overview & Base URLs

The SecureCMS Consent Engine manages multi-tenant compliance policies, records immutable audit trails, and validates user identity claims using asymmetric public key cryptography.

Environment Base URLs

Environment Base URL Description
Production (Hosted) https://cookie-be.securedapp.io/api Live production consent service
Local Development http://localhost:3000/api Local backend instance
Demo Client IdP https://cookie-demo-be.securedapp.io Reference client auth server (JWKS + Login)

2. Direct API Integration Flow

When building a custom client without the SDK, your application coordinates with both your own authentication service and the SecureCMS platform:

sequenceDiagram
    autonumber
    actor User as User / Device
    participant ClientApp as Custom Client App (Web/Mobile/Backend)
    participant ClientIdP as Client Auth Server (IdP)
    participant SecureBE as SecureCMS Consent API (cookie-be.securedapp.io)

    Note over User, SecureBE: Phase 1: Fetch Policy & Record Anonymous Consent
    ClientApp->>SecureBE: GET /api/config/{tenantId}/{appId}
    SecureBE-->>ClientApp: 200 OK (Purposes, Vendors, Policy Version)
    ClientApp->>User: Displays Custom Privacy UI / Consent Screen
    User->>ClientApp: Selects Preferences (e.g., Essential=true, Analytics=true, Marketing=false)
    ClientApp->>ClientApp: Generate/Retrieve Device ID (e.g. dev_9x2k...)
    ClientApp->>SecureBE: POST /api/cookie-consents
    SecureBE-->>ClientApp: 200 OK (Returns consent_id: csnt_abc123)
    ClientApp->>ClientApp: Store consent_id, policy_version, and expiry locally

    Note over User, SecureBE: Phase 2: User Login & Cross-Device Identity Linking
    User->>ClientApp: Logs in with username & password
    ClientApp->>ClientIdP: POST /api/login
    ClientIdP-->>ClientApp: 200 OK (Returns signed RS256 JWT Token)
    ClientApp->>SecureBE: POST /api/cookie-consents/link (Header: Authorization: Bearer <JWT>)
    SecureBE->>ClientIdP: GET /.well-known/jwks.json (Fetches public key)
    ClientIdP-->>SecureBE: 200 OK (RSA Public JWK)
    SecureBE->>SecureBE: Validates signature, audience (app_id), tenant_id, and sub (user_id)
    SecureBE-->>ClientApp: 200 OK (Consent linked to User ID across all devices)

    Note over User, SecureBE: Phase 3: Fetching Existing Consent on New Device
    ClientApp->>SecureBE: GET /api/cookie-consents/user/{userId}?tenant_id={tenantId}&app_id={appId}
    SecureBE-->>ClientApp: 200 OK (Returns active purposes & preferences)
    ClientApp->>ClientApp: Applies synced consent to local services
Loading

3. Authentication & Cryptographic Identity Model (RS256 & JWKS)

To ensure zero shared secrets between organizations:

  • Client IdP creates an RSA 2048-bit key pair.
  • Client IdP signs user tokens using private key (RS256).
  • Client IdP publishes public key at GET /.well-known/jwks.json.
  • SecureCMS validates token signatures dynamically using the public JWK without needing private keys.

Token Header & Payload Specification

Header:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "demo-client-key-1"
}

Payload:

{
  "iss": "https://cookie-demo-be.securedapp.io",
  "aud": "hdfc",
  "sub": "user_123",
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc",
  "name": "Alex Johnson",
  "iat": 1724410000,
  "nbf": 1724410000,
  "exp": 1724413600
}
Field Type Description
iss string Issuer URL: The base URL of your IdP / backend.
aud string Audience: Must match the app_id.
sub string Subject: The unique identifier of the user.
tenant_id string The organization / tenant identifier registered in SecureCMS.
app_id string The application identifier registered in SecureCMS.
exp number Expiration timestamp in seconds (recommended TTL: 3600s / 1 hour).

4. Client Consent & Configuration REST APIs

1. Fetch Application Policy & Configuration

Retrieves the active policy version, banner content, purpose categories, and vendor script URLs for a specific application.

  • HTTP Method: GET
  • Path: /api/config/{tenantId}/{appId}
  • Authentication: None (Public)

Request Parameters:

  • tenantId (path, string, required): Organization identifier (e.g., ICICI Bank).
  • appId (path, string, required): Application identifier (e.g., hdfc).

cURL Example:

curl -X GET "https://cookie-be.securedapp.io/api/config/ICICI%20Bank/hdfc" \
  -H "Accept: application/json"

Successful Response (200 OK):

{
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc",
  "policy_version": "v1.0.0",
  "banner_title": "We Value Your Privacy",
  "banner_description": "We use cookies and similar technologies to optimize your experience, analyze traffic, and personalize content in compliance with the DPDP Act 2023.",
  "purposes": [
    {
      "purpose_id": "p_essential",
      "name": "Essential",
      "description": "Required for basic website functionality, authentication, and security.",
      "is_essential": true
    },
    {
      "purpose_id": "p_analytics",
      "name": "Analytics",
      "description": "Allows us to count visits and traffic sources to measure performance.",
      "is_essential": false,
      "vendors": [
        {
          "vendor_id": "v_ga4",
          "name": "Google Analytics 4",
          "scripts": [
            "https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
          ]
        }
      ]
    },
    {
      "purpose_id": "p_marketing",
      "name": "Marketing",
      "description": "Used by advertising partners to build a profile of your interests.",
      "is_essential": false,
      "vendors": [
        {
          "vendor_id": "v_meta_pixel",
          "name": "Meta Pixel",
          "scripts": [
            "https://connect.facebook.net/en_US/fbevents.js"
          ]
        }
      ]
    }
  ]
}

2. Record Device Consent Decision

Submits an anonymous or device-level consent decision when the user accepts, rejects, or customizes preferences.

  • HTTP Method: POST
  • Path: /api/cookie-consents
  • Authentication: None (Public)
  • Headers: Content-Type: application/json

Request Body Schema:

{
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc",
  "device_id": "dev_4k9x18a7b_1724410000",
  "policy_version": "v1.0.0",
  "purposes": [
    {
      "name": "Essential",
      "status": "granted",
      "timestamp": "2026-08-23T12:00:00.000Z"
    },
    {
      "name": "Analytics",
      "status": "granted",
      "timestamp": "2026-08-23T12:00:00.000Z"
    },
    {
      "name": "Marketing",
      "status": "denied",
      "timestamp": "2026-08-23T12:00:00.000Z"
    }
  ]
}
Field Type Required Description
tenant_id string Yes Organization identifier.
app_id string Yes Application identifier.
device_id string Yes Persistent device identifier (generated on client).
policy_version string Yes The policy version obtained from GET /api/config.
purposes array[object] Yes Array of purpose consent decisions.
purposes[].name string Yes Purpose name (Essential, Analytics, Marketing, etc.).
purposes[].status string Yes "granted" or "denied". (Essential must always be "granted").
purposes[].timestamp string Yes ISO 8601 UTC timestamp of the user action.

cURL Example:

curl -X POST "https://cookie-be.securedapp.io/api/cookie-consents" \
  -H "Content-Type: application/json" \
  -d '{
    "tenant_id": "ICICI Bank",
    "app_id": "hdfc",
    "device_id": "dev_4k9x18a7b_1724410000",
    "policy_version": "v1.0.0",
    "purposes": [
      { "name": "Essential", "status": "granted", "timestamp": "2026-08-23T12:00:00.000Z" },
      { "name": "Analytics", "status": "granted", "timestamp": "2026-08-23T12:00:00.000Z" },
      { "name": "Marketing", "status": "denied", "timestamp": "2026-08-23T12:00:00.000Z" }
    ]
  }'

Successful Response (200 OK / 201 Created):

{
  "consent_id": "csnt_9876543210abcdef",
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc",
  "device_id": "dev_4k9x18a7b_1724410000",
  "policy_version": "v1.0.0",
  "purposes": [
    { "name": "Essential", "status": "granted", "timestamp": "2026-08-23T12:00:00.000Z" },
    { "name": "Analytics", "status": "granted", "timestamp": "2026-08-23T12:00:00.000Z" },
    { "name": "Marketing", "status": "denied", "timestamp": "2026-08-23T12:00:00.000Z" }
  ],
  "created_at": "2026-08-23T12:00:01.120Z"
}

3. Link Consent with User JWT Token

Links an existing anonymous device consent_id to an authenticated user ID. The backend verifies the RS256 JWT using your JWKS.

  • HTTP Method: POST
  • Path: /api/cookie-consents/link
  • Authentication: Bearer Token (Authorization: Bearer <RS256_JWT>)
  • Headers:
    • Content-Type: application/json
    • Authorization: Bearer <JWT_TOKEN>

Request Body Schema:

{
  "consent_id": "csnt_9876543210abcdef",
  "user_id": "user_123",
  "device_id": "dev_4k9x18a7b_1724410000",
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc"
}

cURL Example:

curl -X POST "https://cookie-be.securedapp.io/api/cookie-consents/link" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRlbW8tY2xpZW50LWtleS0xIn0..." \
  -d '{
    "consent_id": "csnt_9876543210abcdef",
    "user_id": "user_123",
    "device_id": "dev_4k9x18a7b_1724410000",
    "tenant_id": "ICICI Bank",
    "app_id": "hdfc"
  }'

Successful Response (200 OK):

{
  "success": true,
  "consent_id": "csnt_9876543210abcdef",
  "user_id": "user_123",
  "linked_at": "2026-08-23T12:05:00.000Z",
  "purposes": [
    { "name": "Essential", "status": "granted" },
    { "name": "Analytics", "status": "granted" },
    { "name": "Marketing", "status": "denied" }
  ]
}

4. Fetch User Consent by ID

Retrieves the latest synchronized consent preferences for a logged-in user (useful when logging in on a new device or browser).

  • HTTP Method: GET
  • Path: /api/cookie-consents/user/{userId}
  • Query Parameters:
    • tenant_id (string, optional/recommended): Organization ID.
    • app_id (string, optional/recommended): Application ID.
  • Authentication: Bearer Token (Authorization: Bearer <RS256_JWT>)

cURL Example:

curl -X GET "https://cookie-be.securedapp.io/api/cookie-consents/user/user_123?tenant_id=ICICI%20Bank&app_id=hdfc" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRlbW8tY2xpZW50LWtleS0xIn0..." \
  -H "Accept: application/json"

Successful Response (200 OK):

{
  "consent_id": "csnt_9876543210abcdef",
  "user_id": "user_123",
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc",
  "policy_version": "v1.0.0",
  "purposes": [
    { "name": "Essential", "status": "granted" },
    { "name": "Analytics", "status": "granted" },
    { "name": "Marketing", "status": "denied" }
  ],
  "updated_at": "2026-08-23T12:05:00.000Z"
}

5. Admin & Management REST APIs

These APIs are used by backend administrative dashboards to manage purpose taxonomies, third-party vendor scripts, and inspect consent audit trails.

1. List Configured Purposes

GET /api/admin/purposes

  • Response 200 OK: Returns array of all configured purposes.

2. Create or Update Purpose

POST /api/admin/purposes

  • Request Body:
{
  "name": "Personalization",
  "description": "Enables enhanced functionality and customization.",
  "is_essential": false
}

3. List Configured Vendors

GET /api/admin/vendors

  • Response 200 OK: Returns list of all vendors and script tags.

4. Create or Update Vendor

POST /api/admin/vendors

  • Request Body:
{
  "vendor_id": "v_hotjar",
  "name": "Hotjar",
  "purpose_name": "Analytics",
  "scripts": ["https://static.hotjar.com/c/hotjar-12345.js"]
}

5. Upsert Application Configuration

POST /api/config

  • Request Body:
{
  "tenant_id": "ICICI Bank",
  "app_id": "hdfc",
  "policy_version": "v1.1.0",
  "banner_title": "Privacy Settings",
  "banner_description": "We respect your data privacy under the DPDP Act 2023.",
  "purposes": [ ... ]
}

6. Audit Log: Query Consent Records

GET /api/admin/cookie-consents?page=1&limit=25

  • Response 200 OK: Returns paginated immutable consent logs for compliance audits.

6. Client-Side Implementation Guidelines (Without SDK)

When you choose to implement your own frontend or mobile layer instead of using the JavaScript SDK, ensure you replicate these critical platform requirements:

1. Device ID Fingerprinting

Generate a persistent pseudo-unique ID stored in persistent storage (e.g., localStorage, Keychain, EncryptedSharedPreferences):

function getOrCreateDeviceId() {
  let deviceId = localStorage.getItem('consent_device_id');
  if (!deviceId) {
    deviceId = 'dev_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now().toString(36);
    localStorage.setItem('consent_device_id', deviceId);
  }
  return deviceId;
}

2. Policy Validity & Expiration Logic

Consent must be renewed if:

  1. No local record exists.
  2. The policy_version returned from GET /api/config differs from the local record.
  3. The consent has expired (default validity: 6 months / 180 days).
function isConsentValid(localConsent, serverPolicyVersion) {
  if (!localConsent) return false;
  if (localConsent.policy_version !== serverPolicyVersion) return false;
  if (localConsent.expiry && new Date(localConsent.expiry) < new Date()) return false;
  return true;
}

3. Offline Queuing & Sync Engine

If the client is offline when the user makes a choice:

  1. Save the choice locally immediately to unblock the UI.
  2. Push the payload into a local offline queue (consent_offline_queue).
  3. Re-attempt transmission when the network is restored or at regular intervals (e.g., every 60s).

4. Executing / Blocking Tracking Scripts

  • Only execute vendor scripts or third-party SDKs when the user's purposes status for that category equals "granted".
  • Essential scripts (is_essential: true) may execute immediately.
  • Maintain a runtime cache/set of executed vendors to prevent duplicate script execution.

7. Code Integration Examples

Node.js / TypeScript Example

import axios from 'axios';

const SECURE_CMS_BASE = 'https://cookie-be.securedapp.io/api';

interface ConsentChoice {
  name: string;
  status: 'granted' | 'denied';
  timestamp: string;
}

export class CookieConsentClient {
  constructor(
    private tenantId: string,
    private appId: string,
    private deviceId: string
  ) {}

  // 1. Fetch Config
  async fetchConfig() {
    const res = await axios.get(`${SECURE_CMS_BASE}/config/${encodeURIComponent(this.tenantId)}/${encodeURIComponent(this.appId)}`);
    return res.data;
  }

  // 2. Submit Anonymous Consent
  async submitConsent(policyVersion: string, choices: ConsentChoice[]) {
    const res = await axios.post(`${SECURE_CMS_BASE}/cookie-consents`, {
      tenant_id: this.tenantId,
      app_id: this.appId,
      device_id: this.deviceId,
      policy_version: policyVersion,
      purposes: choices
    });
    return res.data;
  }

  // 3. Link with User JWT
  async linkUser(consentId: string, userId: string, jwtToken: string) {
    const res = await axios.post(
      `${SECURE_CMS_BASE}/cookie-consents/link`,
      {
        consent_id: consentId,
        user_id: userId,
        device_id: this.deviceId,
        tenant_id: this.tenantId,
        app_id: this.appId
      },
      {
        headers: {
          Authorization: `Bearer ${jwtToken}`
        }
      }
    );
    return res.data;
  }

  // 4. Fetch User Consent
  async getUserConsent(userId: string, jwtToken: string) {
    const res = await axios.get(
      `${SECURE_CMS_BASE}/cookie-consents/user/${encodeURIComponent(userId)}?tenant_id=${encodeURIComponent(this.tenantId)}&app_id=${encodeURIComponent(this.appId)}`,
      {
        headers: {
          Authorization: `Bearer ${jwtToken}`
        }
      }
    );
    return res.data;
  }
}

Python Example

import requests
from datetime import datetime, timezone

SECURE_CMS_BASE = "https://cookie-be.securedapp.io/api"

class CookieConsentAPI:
    def __init__(self, tenant_id: str, app_id: str, device_id: str):
        self.tenant_id = tenant_id
        self.app_id = app_id
        self.device_id = device_id

    def get_config(self):
        url = f"{SECURE_CMS_BASE}/config/{self.tenant_id}/{self.app_id}"
        response = requests.get(url)
        response.raise_for_status()
        return response.json()

    def record_consent(self, policy_version: str, analytics_granted: bool, marketing_granted: bool):
        now_iso = datetime.now(timezone.utc).isoformat()
        payload = {
            "tenant_id": self.tenant_id,
            "app_id": self.app_id,
            "device_id": self.device_id,
            "policy_version": policy_version,
            "purposes": [
                {"name": "Essential", "status": "granted", "timestamp": now_iso},
                {"name": "Analytics", "status": "granted" if analytics_granted else "denied", "timestamp": now_iso},
                {"name": "Marketing", "status": "granted" if marketing_granted else "denied", "timestamp": now_iso}
            ]
        }
        res = requests.post(f"{SECURE_CMS_BASE}/cookie-consents", json=payload)
        res.raise_for_status()
        return res.json()

    def link_user(self, consent_id: str, user_id: str, jwt_token: str):
        headers = {"Authorization": f"Bearer {jwt_token}"}
        payload = {
            "consent_id": consent_id,
            "user_id": user_id,
            "device_id": self.device_id,
            "tenant_id": self.tenant_id,
            "app_id": self.app_id
        }
        res = requests.post(f"{SECURE_CMS_BASE}/cookie-consents/link", json=payload, headers=headers)
        res.raise_for_status()
        return res.json()

Mobile (Swift / iOS) Example

import Foundation

struct ConsentManager {
    let tenantId = "ICICI Bank"
    let appId = "hdfc"
    let baseURL = "https://cookie-be.securedapp.io/api"
    
    // Save Consent
    func saveConsent(deviceId: String, policyVersion: String, analyticsAllowed: Bool, completion: @escaping (Result<String, Error>) -> Void) {
        guard let url = URL(string: "\(baseURL)/cookie-consents") else { return }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let isoFormatter = ISO8601DateFormatter()
        let now = isoFormatter.string(from: Date())
        
        let payload: [String: Any] = [
            "tenant_id": tenantId,
            "app_id": appId,
            "device_id": deviceId,
            "policy_version": policyVersion,
            "purposes": [
                ["name": "Essential", "status": "granted", "timestamp": now],
                ["name": "Analytics", "status": analyticsAllowed ? "granted" : "denied", "timestamp": now]
            ]
        ]
        
        request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
        
        URLSession.shared.dataTask(with: request) { data, _, error in
            if let error = error { completion(.failure(error)); return }
            if let data = data,
               let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
               let consentId = json["consent_id"] as? String {
                completion(.success(consentId))
            }
        }.resume()
    }
}

Mobile (Kotlin / Android) Example

import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONArray
import org.json.JSONObject
import java.time.Instant

class CookieConsentClient(
    private val tenantId: String = "ICICI Bank",
    private val appId: String = "hdfc",
    private val client: OkHttpClient = OkHttpClient()
) {
    private val baseUrl = "https://cookie-be.securedapp.io/api"

    fun saveConsent(deviceId: String, policyVersion: String, analyticsGranted: Boolean, callback: (String?) -> Unit) {
        val now = Instant.now().toString()
        val json = JSONObject().apply {
            put("tenant_id", tenantId)
            put("app_id", appId)
            put("device_id", deviceId)
            put("policy_version", policyVersion)
            put("purposes", JSONArray().apply {
                put(JSONObject().apply {
                    put("name", "Essential")
                    put("status", "granted")
                    put("timestamp", now)
                })
                put(JSONObject().apply {
                    put("name", "Analytics")
                    put("status", if (analyticsGranted) "granted" else "denied")
                    put("timestamp", now)
                })
            })
        }

        val request = Request.Builder()
            .url("$baseUrl/cookie-consents")
            .post(json.toString().toRequestBody("application/json".toMediaType()))
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: java.io.IOException) = callback(null)
            override fun onResponse(call: Call, response: Response) {
                val body = response.body?.string() ?: return callback(null)
                val resObj = JSONObject(body)
                callback(resObj.optString("consent_id"))
            }
        })
    }
}

8. Client IdP Server Endpoints (JWKS Reference)

Your Identity Provider must implement the following endpoints to support secure identity linking:

1. GET /.well-known/jwks.json

Returns the public JSON Web Key Set:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "demo-client-key-1",
      "use": "sig",
      "alg": "RS256",
      "n": "u1w_...",
      "e": "AQAB"
    }
  ]
}

2. POST /api/login

Authenticates the user and returns the signed RS256 token:

{
  "success": true,
  "userId": "user_123",
  "tenantId": "ICICI Bank",
  "appId": "hdfc",
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRlbW8tY2xpZW50LWtleS0xIn0...",
  "issuer": "https://cookie-demo-be.securedapp.io",
  "jwks_url": "https://cookie-demo-be.securedapp.io/.well-known/jwks.json",
  "expires_in": 3600
}

9. Error Codes & Troubleshooting

HTTP Status Error Code / Message Meaning & Resolution
400 Bad Request Missing required fields Verify that tenant_id, app_id, device_id, and purposes are present.
401 Unauthorized Invalid JWT Signature Verify that the token was signed with the private key matching your published JWKS kid.
401 Unauthorized JWT expired Tokens must be fresh (exp > current time). Renew JWT upon login.
404 Not Found Config not found Check that tenantId and appId are spelled accurately and configured in the Admin backend.
500 Server Error Failed to reach JWKS Ensure your BASE_URL/.well-known/jwks.json is publicly reachable with valid SSL certificates.

For further integration support or direct API credentials, contact SecuredApp support.