openapi: 3.1.0
info:
  title: DPDPA Consent Manager API
  version: 1.0.0
  description: |
    RESTful API specification for a DPDPA-compliant Consent Manager platform.

    Implements:
    - First Schedule, Part B, Item 1: Multi-fiduciary consent routing
    - First Schedule, Part B, Item 2: Zero-knowledge data handling
    - First Schedule, Part B, Item 4: 7-year retention + machine-readable export
    - Section 13: Grievance redressal

    **Security:** OAuth 2.0 + TLS 1.3 minimum.

    **Architecture:** Consent Manager routes consent metadata only. Personal data payloads
    are end-to-end encrypted between Data Principal and Data Fiduciary.
  contact:
    name: StateCheck Security
    url: https://statechecksecurity.com
  license:
    name: Proprietary
servers:
  - url: https://api.consent-manager.example.com/v1
    description: Production API
  - url: https://sandbox.consent-manager.example.com/v1
    description: Sandbox environment for Data Fiduciary testing

security:
  - OAuth2: []

tags:
  - name: Fiduciary Onboarding
    description: Register and manage Data Fiduciaries on the platform
  - name: Consent Routing
    description: Route consent grants, denials, and withdrawals (metadata only)
  - name: Data Sharing
    description: Route encrypted personal data with zero-knowledge guarantees
  - name: Data Subject Rights (DSAR)
    description: Data Principal access, export, and erasure requests
  - name: Grievance Redressal
    description: Section 13 grievance intake and tracking

paths:
  /fiduciaries:
    post:
      tags:
        - Fiduciary Onboarding
      summary: Register a Data Fiduciary
      description: |
        Onboard a new Data Fiduciary onto the Consent Manager platform.

        **Part B, Item 1:** Enable Data Principal to give consent to any onboarded Data Fiduciary.
      operationId: registerFiduciary
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FiduciaryRegistration'
      responses:
        '201':
          description: Fiduciary registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Fiduciary'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Fiduciary with this identifier already exists
      security:
        - OAuth2: [fiduciary:write]

    get:
      tags:
        - Fiduciary Onboarding
      summary: List all registered Data Fiduciaries
      description: Query fiduciaries with pagination and filtering
      operationId: listFiduciaries
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [ACTIVE, SUSPENDED, PENDING]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of fiduciaries
          content:
            application/json:
              schema:
                type: object
                properties:
                  fiduciaries:
                    type: array
                    items:
                      $ref: '#/components/schemas/Fiduciary'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
      security:
        - OAuth2: [fiduciary:read]

  /fiduciaries/{fiduciary_id}:
    get:
      tags:
        - Fiduciary Onboarding
      summary: Get Data Fiduciary details
      operationId: getFiduciary
      parameters:
        - $ref: '#/components/parameters/FiduciaryId'
      responses:
        '200':
          description: Fiduciary details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Fiduciary'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2: [fiduciary:read]

    patch:
      tags:
        - Fiduciary Onboarding
      summary: Update Fiduciary status or metadata
      operationId: updateFiduciary
      parameters:
        - $ref: '#/components/parameters/FiduciaryId'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [ACTIVE, SUSPENDED]
                webhook_url:
                  type: string
                  format: uri
                public_key:
                  type: string
                  description: PEM-encoded public key for E2E encryption
      responses:
        '200':
          description: Fiduciary updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Fiduciary'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2: [fiduciary:write]

  /consent/grant:
    post:
      tags:
        - Consent Routing
      summary: Route consent grant (metadata only)
      description: |
        Data Principal grants consent to a Data Fiduciary for a specific purpose.

        **Zero-Knowledge (Part B, Item 2):** The Consent Manager records only metadata.
        The personal data payload is NOT accessible to the CM.

        **Workflow:**
        1. DP encrypts consent token with DF's public key
        2. CM stores: principal_id, fiduciary_id, purpose_id, payload_hash, timestamp
        3. CM forwards encrypted token to DF via webhook
      operationId: grantConsent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConsentGrant'
      responses:
        '201':
          description: Consent grant recorded and routed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsentRecord'
        '400':
          $ref: '#/components/responses/BadRequest'
      security:
        - OAuth2: [consent:grant]
      callbacks:
        consentGranted:
          '{$request.body#/fiduciary_webhook_url}':
            post:
              summary: Notify Data Fiduciary of consent grant
              requestBody:
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/ConsentWebhook'
              responses:
                '200':
                  description: Fiduciary acknowledged consent grant

  /consent/withdraw:
    post:
      tags:
        - Consent Routing
      summary: Route consent withdrawal
      description: |
        Data Principal withdraws previously granted consent.

        **Part B, Item 3:** All consent withdrawals are recorded in the immutable ledger.
      operationId: withdrawConsent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConsentWithdrawal'
      responses:
        '201':
          description: Consent withdrawal recorded and routed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsentRecord'
        '400':
          $ref: '#/components/responses/BadRequest'
      security:
        - OAuth2: [consent:withdraw]
      callbacks:
        consentWithdrawn:
          '{$request.body#/fiduciary_webhook_url}':
            post:
              summary: Notify Data Fiduciary of consent withdrawal
              requestBody:
                content:
                  application/json:
                    schema:
                      $ref: '#/components/schemas/ConsentWebhook'
              responses:
                '200':
                  description: Fiduciary acknowledged withdrawal

  /consent/{principal_id}:
    get:
      tags:
        - Consent Routing
      summary: Query Data Principal's consent history
      description: |
        Data Principal accesses their consent records via the platform.

        **Part B, Item 4(a):** CM must give DP platform access to consent records.
      operationId: getConsentHistory
      parameters:
        - name: principal_id
          in: path
          required: true
          schema:
            type: string
        - name: fiduciary_id
          in: query
          schema:
            type: string
          description: Filter by specific Data Fiduciary
        - name: from_date
          in: query
          schema:
            type: string
            format: date-time
        - name: to_date
          in: query
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Consent history
          content:
            application/json:
              schema:
                type: object
                properties:
                  records:
                    type: array
                    items:
                      $ref: '#/components/schemas/ConsentRecord'
                  total:
                    type: integer
      security:
        - OAuth2: [consent:read]

  /data-share:
    post:
      tags:
        - Data Sharing
      summary: Route encrypted personal data (zero-knowledge)
      description: |
        Route personal data from source Data Fiduciary to target Data Fiduciary.

        **Zero-Knowledge (Part B, Item 2):** CM stores only the SHA-256 hash of the
        encrypted payload. The payload itself is NOT accessible to the CM.
      operationId: shareData
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DataShare'
      responses:
        '201':
          description: Data share recorded and routed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShareRecord'
        '400':
          $ref: '#/components/responses/BadRequest'
      security:
        - OAuth2: [data:share]

  /export/{principal_id}:
    get:
      tags:
        - Data Subject Rights (DSAR)
      summary: Machine-readable consent record export
      description: |
        Data Principal requests export of all consent records in machine-readable format.

        **Part B, Item 4(b):** CM must provide records in machine-readable form on request.

        **Export Format:** JSON per schema version 1.0
      operationId: exportConsentRecords
      parameters:
        - name: principal_id
          in: path
          required: true
          schema:
            type: string
        - name: format
          in: query
          schema:
            type: string
            enum: [json, csv]
            default: json
      responses:
        '200':
          description: Machine-readable export
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsentExport'
            text/csv:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2: [export:read]

  /principal/{principal_id}:
    delete:
      tags:
        - Data Subject Rights (DSAR)
      summary: Right to erasure (after 7-year retention ends)
      description: |
        Erase Data Principal's consent records ONLY if 7-year retention period has ended.

        **Part B, Item 4(c):** Records must be kept for at least seven years, or longer
        if agreed with the Data Principal or required by law. Erasure before then
        breaches the First Schedule.
      operationId: erasePrincipalData
      parameters:
        - name: principal_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '204':
          description: Records erased (if retention period ended)
        '403':
          description: Erasure denied - 7-year retention period not yet ended
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "Erasure denied. Minimum 7-year retention required per DPDP Rules 2025, First Schedule, Part B, Item 4(c)."
                  earliest_erasure_date:
                    type: string
                    format: date
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2: [principal:delete]

  /grievances:
    post:
      tags:
        - Grievance Redressal
      summary: Submit grievance (Section 13 + Rule 14(3))
      description: |
        Data Principal submits a grievance about the Consent Manager's operations.

        **Section 13(1):** Data Principals have the right to readily available grievance
        redressal from the Consent Manager. **Rule 14(3)** (from 13 May 2027): respond
        within the published period, which may not exceed 90 days.
      operationId: submitGrievance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GrievanceSubmission'
      responses:
        '201':
          description: Grievance submitted; acknowledgement sent within 24 hours
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Grievance'
        '400':
          $ref: '#/components/responses/BadRequest'
      security:
        - OAuth2: [grievance:submit]

    get:
      tags:
        - Grievance Redressal
      summary: List grievances (Data Principal or CM admin)
      operationId: listGrievances
      parameters:
        - name: principal_id
          in: query
          schema:
            type: string
          description: Filter by Data Principal (for DP querying their own grievances)
        - name: status
          in: query
          schema:
            type: string
            enum: [SUBMITTED, IN_REVIEW, RESOLVED, CLOSED]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: List of grievances
          content:
            application/json:
              schema:
                type: object
                properties:
                  grievances:
                    type: array
                    items:
                      $ref: '#/components/schemas/Grievance'
                  total:
                    type: integer
      security:
        - OAuth2: [grievance:read]

  /grievances/{grievance_id}:
    get:
      tags:
        - Grievance Redressal
      summary: Track grievance status
      operationId: getGrievance
      parameters:
        - name: grievance_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Grievance details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Grievance'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2: [grievance:read]

    patch:
      tags:
        - Grievance Redressal
      summary: Update grievance status (CM admin only)
      operationId: updateGrievance
      parameters:
        - name: grievance_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                status:
                  type: string
                  enum: [IN_REVIEW, RESOLVED, CLOSED]
                resolution_notes:
                  type: string
      responses:
        '200':
          description: Grievance updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Grievance'
        '404':
          $ref: '#/components/responses/NotFound'
      security:
        - OAuth2: [grievance:admin]

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://auth.consent-manager.example.com/oauth/token
          scopes:
            fiduciary:read: Read Data Fiduciary metadata
            fiduciary:write: Register and update Data Fiduciaries
            consent:grant: Grant consent on behalf of Data Principal
            consent:withdraw: Withdraw consent
            consent:read: Read consent history
            data:share: Route encrypted personal data
            export:read: Export machine-readable consent records
            principal:delete: Erase Data Principal records (after 7 years)
            grievance:submit: Submit grievance
            grievance:read: Read grievance status
            grievance:admin: Update grievance status (CM staff only)

  parameters:
    FiduciaryId:
      name: fiduciary_id
      in: path
      required: true
      schema:
        type: string
      description: Unique identifier for the Data Fiduciary

  schemas:
    FiduciaryRegistration:
      type: object
      required:
        - company_name
        - gstin
        - cin
        - webhook_url
        - public_key
      properties:
        company_name:
          type: string
          example: "ACME Data Services Pvt Ltd"
        gstin:
          type: string
          pattern: '^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$'
          example: "29ABCDE1234F1Z5"
        cin:
          type: string
          pattern: '^[UL][0-9]{5}[A-Z]{2}[0-9]{4}[A-Z]{3}[0-9]{6}$'
          example: "U72900KA2020PTC123456"
        pan:
          type: string
          pattern: '^[A-Z]{5}[0-9]{4}[A-Z]{1}$'
        webhook_url:
          type: string
          format: uri
          description: HTTPS endpoint for consent event notifications
          example: "https://api.acme.com/consent-webhooks"
        public_key:
          type: string
          description: PEM-encoded RSA-2048+ or ECDSA-256+ public key for E2E encryption
        contact_email:
          type: string
          format: email
        dpo_name:
          type: string
        dpo_email:
          type: string
          format: email

    Fiduciary:
      allOf:
        - $ref: '#/components/schemas/FiduciaryRegistration'
        - type: object
          properties:
            fiduciary_id:
              type: string
              format: uuid
            status:
              type: string
              enum: [PENDING, ACTIVE, SUSPENDED]
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time

    ConsentGrant:
      type: object
      required:
        - principal_id
        - fiduciary_id
        - purpose_id
        - encrypted_consent_token
      properties:
        principal_id:
          type: string
          description: Pseudonymised Data Principal identifier
          example: "DP_a1b2c3d4e5f6"
        fiduciary_id:
          type: string
          format: uuid
        purpose_id:
          type: string
          description: Consent purpose identifier (e.g., "marketing", "credit_assessment")
          example: "marketing_communications"
        encrypted_consent_token:
          type: string
          format: byte
          description: Base64-encoded encrypted consent token (CM cannot decrypt)
        notice_hash:
          type: string
          description: SHA-256 hash of the consent notice served to Data Principal
        fiduciary_webhook_url:
          type: string
          format: uri
          description: Override fiduciary's registered webhook URL for this event

    ConsentWithdrawal:
      type: object
      required:
        - principal_id
        - fiduciary_id
        - purpose_id
      properties:
        principal_id:
          type: string
        fiduciary_id:
          type: string
          format: uuid
        purpose_id:
          type: string
        reason:
          type: string
          description: Optional withdrawal reason
        fiduciary_webhook_url:
          type: string
          format: uri

    ConsentRecord:
      type: object
      properties:
        event_id:
          type: string
          format: uuid
        principal_id:
          type: string
        fiduciary_id:
          type: string
        event_type:
          type: string
          enum: [CONSENT_GRANT, CONSENT_DENY, CONSENT_WITHDRAW, NOTICE_SERVED]
        purpose_id:
          type: string
        notice_hash:
          type: string
          description: SHA-256 hash of consent notice
        payload_hash:
          type: string
          description: SHA-256 hash of encrypted consent token (NOT the payload itself)
        created_at:
          type: string
          format: date-time
        metadata:
          type: object
          additionalProperties: true
          description: Additional structured context (CM never stores personal data here)

    DataShare:
      type: object
      required:
        - principal_id
        - source_fiduciary_id
        - target_fiduciary_id
        - encrypted_payload
      properties:
        principal_id:
          type: string
        source_fiduciary_id:
          type: string
          format: uuid
        target_fiduciary_id:
          type: string
          format: uuid
        encrypted_payload:
          type: string
          format: byte
          description: Base64-encoded E2E encrypted personal data (CM cannot decrypt)
        purpose_id:
          type: string

    ShareRecord:
      type: object
      properties:
        share_id:
          type: string
          format: uuid
        principal_id:
          type: string
        source_fiduciary_id:
          type: string
        target_fiduciary_id:
          type: string
        payload_hash:
          type: string
          description: SHA-256 hash of encrypted payload (NOT the payload itself)
        created_at:
          type: string
          format: date-time

    ConsentExport:
      type: object
      description: Machine-readable consent export per Part B, Item 4(b)
      properties:
        format:
          type: string
          const: "application/json"
        schema_version:
          type: string
          const: "1.0"
        data_principal:
          type: object
          properties:
            identifier:
              type: string
              description: Pseudonymised principal ID (NOT Aadhaar or PAN)
        records:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
                enum: [CONSENT_GRANT, CONSENT_DENY, CONSENT_WITHDRAW, NOTICE_SERVED, DATA_SHARE]
              fiduciary_id:
                type: string
              purpose:
                type: string
              timestamp:
                type: string
                format: date-time
              notice_reference:
                type: string
                description: SHA-256 hash of consent notice
              payload_hash:
                type: string
                description: SHA-256 hash (for DATA_SHARE events)
        retention:
          type: object
          properties:
            minimum_years:
              type: integer
              const: 7
            policy:
              type: string
              const: "immutable_append_only"

    ConsentWebhook:
      type: object
      description: Webhook payload sent to Data Fiduciary on consent events
      properties:
        event_type:
          type: string
          enum: [consent.granted, consent.withdrawn, data_share.completed]
        event_id:
          type: string
          format: uuid
        principal_id:
          type: string
        fiduciary_id:
          type: string
        purpose_id:
          type: string
        timestamp:
          type: string
          format: date-time
        encrypted_token:
          type: string
          format: byte
          description: For consent.granted events only
        payload_hash:
          type: string

    GrievanceSubmission:
      type: object
      required:
        - principal_id
        - grievance_type
        - description
        - contact_email
      properties:
        principal_id:
          type: string
        grievance_type:
          type: string
          enum: [DATA_ACCESS_ISSUE, CONSENT_ROUTING_FAILURE, PRIVACY_CONCERN, OTHER]
        description:
          type: string
          maxLength: 5000
        contact_email:
          type: string
          format: email
        contact_phone:
          type: string

    Grievance:
      allOf:
        - $ref: '#/components/schemas/GrievanceSubmission'
        - type: object
          properties:
            grievance_id:
              type: string
              format: uuid
            status:
              type: string
              enum: [SUBMITTED, IN_REVIEW, RESOLVED, CLOSED]
            submitted_at:
              type: string
              format: date-time
            acknowledged_at:
              type: string
              format: date-time
              description: Auto-acknowledgement within 24 hours (internal SLA; Rule 14(3) sets only the overall response period, not exceeding 90 days)
            resolved_at:
              type: string
              format: date-time
            resolution_notes:
              type: string
            assigned_to:
              type: string
              description: CM staff member handling the grievance

  responses:
    BadRequest:
      description: Invalid request payload
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
              details:
                type: object
    Unauthorized:
      description: Invalid or missing OAuth token
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                const: "Unauthorized"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
                const: "Not Found"
