openapi: 3.1.0
info:
  title: DROP Data Broker API
  version: 1.1.0
  summary: Delete Act - Data Broker Integration API
  description: |
    The DROP (Delete Request and Opt-out Platform) Data Broker API enables
    data brokers to integrate with DROP to programmatically retrieve consumer
    deletion request data and report status updates.

    ## How It Works

    1. **Download** - Request or download a ZIP archive of CSV files containing hashed consumer identifiers.
    2. **Process** - Match hashed identifiers against your records and take the required action: delete, opt out, exempt, or not found.
    3. **Upload** - Submit CSV response files reporting the status for each work item.

    ## Authentication

    All requests require an API key passed in the `X-API-KEY` header. API keys are
    generated in the [Data Broker Portal](https://databroker.drop.privacy.ca.gov/Dashboard)
    after registration and fee payment are complete.

    ## Standardization and Hashing

    DROP provides hashed identifiers. To match DROP identifiers against your own records,
    standardize and hash the identifiers in your records using the same rules.

    All hashes use **SHA-256** with UTF-8 input encoding. Hash output is **Base64**.

    ### Standardization Rules

    Apply the identifier-specific standardization rules before hashing.

    - Email: remove all whitespace, then lowercase. Do not remove dots, plus signs, or other characters.
    - DOB: format as `YYYYMMDD` with no separators.
    - Phone: keep digits only, then retain the last 10 digits, or all digits if fewer than 10 remain.
    - ZIP: keep alphanumeric characters only, remove leading zeros, use the first five remaining characters, then lowercase.
    - Names: normalize Unicode, lowercase, transliterate supported Greek and Cyrillic characters, fold supported Latin characters to plain ASCII where possible, then keep only letters and digits.
    - MAID: keep only hexadecimal characters (`0-9`, `a-f`), then lowercase. The standardized value must be 32 characters.
    - VIN: keep only alphanumeric characters, then lowercase. The standardized value must be 17 characters.
    - CTVID: keep only alphanumeric characters, then lowercase. The standardized value must be 8 to 32 characters.

    ### Composite Hashing

    NDZ and NameVIN use composite hashing. Each field is standardized and hashed first,
    then the resulting Base64 hashes are concatenated in the required order and hashed again.

    - NDZ uses `FirstName + LastName + DOB + ZIP`
    - NameVIN uses `FirstName + LastName + VIN`

    Names are standardized and hashed per field: first name and last name separately.
servers:
- url: https://api.drop.privacy.ca.gov
  description: Production
- url: https://api.drop.privacy.ca.gov/sandbox
  description: Sandbox
externalDocs:
  description: Human-readable integration guide
  url: ./docs.html
tags:
- name: Download
  description: Request or download consumer deletion lists(s)
- name: Upload
  description: Submit new or amended status response files
x-tagGroups:
- name: Core workflow
  tags:
  - Download
  - Upload
security:
- ApiKeyAuth: []
paths:
  /data/download:
    get:
      operationId: downloadData
      summary: Request or download consumer deletion request data
      description: |
        Requests the current ZIP archive.

        If the ZIP is ready, the response returns the ZIP file. If the ZIP is still being prepared,
        the response returns a JSON message and the data broker should call `GET /data/download` again later.

        The ZIP contains one CSV per selected list type. If a selected list has no new records,
        the CSV contains only the header row. If previously delivered identifiers were removed,
        the ZIP also includes one Removed CSV file.
      tags:
      - Download
      x-codeSamples:
      - lang: Shell
        label: curl
        source: |-
          curl -X GET "https://api.drop.privacy.ca.gov/data/download" \
            -H "accept: application/zip, application/json" \
            -H "X-API-KEY: your-api-key-here" \
            --output download.zip
      - lang: PowerShell
        label: Invoke-WebRequest
        source: |-
          $apiKey = "your-api-key-here"
          $url = "https://api.drop.privacy.ca.gov/data/download"
          $headers = @{
              "X-API-KEY" = $apiKey
              "Accept"    = "application/zip, application/json"
          }
          Invoke-WebRequest -Uri $url -Headers $headers -OutFile "download.zip"
      security:
      - ApiKeyAuth: []
      responses:
        '200':
          description: ZIP is ready and returned, or no new consumer request data or removed identifiers are available.
          headers:
            Content-Disposition:
              description: Returned when the response body is a ZIP file.
              schema:
                type: string
              example: attachment; filename="20260312_4821_DROP.zip"
          content:
            application/zip:
              schema:
                type: string
                format: binary
              description: Binary ZIP archive containing CSV files.
            application/json:
              schema:
                $ref: '#/components/schemas/MessageResponse'
              examples:
                noNewData:
                  summary: No new data
                  value:
                    message: No new consumer request data or removed identifiers are available since your last completed download.
        '202':
          description: The request was received and the download package is being prepared.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageResponse'
              example:
                message: No download package is available yet. Your request was received, and the package is being created.
                  Call GET /data/download again later.
          headers:
            Retry-After:
              description: Suggested number of seconds to wait before calling GET /data/download again.
              schema:
                type: integer
                minimum: 1
              example: 60
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Previous download is not complete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageResponse'
              example:
                message: Complete the current batch or contact DROP support if a re-download is needed.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /data/upload:
    post:
      operationId: uploadData
      summary: Upload new status response files
      description: |
        Submit new status responses.

        One or more CSV response files may be uploaded in the same request.

        Each CSV must:
        - Use the header `Id,Status`
        - Use a downloaded file name or allowed suffix pattern
        - Contain status codes `2`, `3`, `4`, or `5`
        - Be uploaded as a CSV file, not a ZIP archive

        The files are sent as `multipart/form-data` with the field name `files`.
        Accepted files are queued for validation. Full row-level validation may continue after the response is returned.
      tags:
      - Upload
      x-codeSamples:
      - lang: Shell
        label: curl
        source: |-
          curl -X POST "https://api.drop.privacy.ca.gov/data/upload" \
            -H "accept: application/json" \
            -H "X-API-KEY: your-api-key-here" \
            -F "files=@20260312_4821_Email.csv;type=text/csv"
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - files
              properties:
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: One or more CSV response files. Each file must use a downloaded file name or allowed suffix
                    pattern and must have `Id,Status` as the header row.
            examples:
              uploadEmail:
                $ref: '#/components/examples/UploadEmailFile'
      responses:
        '202':
          description: At least one file was accepted and queued for validation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
              examples:
                accepted:
                  $ref: '#/components/examples/UploadAcceptedResponse'
                mixed:
                  $ref: '#/components/examples/UploadMixedResponse'
        '400':
          description: Request is malformed or no files were accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
              examples:
                invalidHeader:
                  $ref: '#/components/examples/UploadInvalidHeaderResponse'
                duplicateFile:
                  $ref: '#/components/examples/UploadDuplicateFileResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: No active download is waiting for responses.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageResponse'
              example:
                message: No active download is waiting for responses. Use GET /data/download to request or download a new
                  ZIP file.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /data/amend:
    post:
      operationId: uploadAmend
      summary: Correct or update previously submitted status responses
      description: |
        Correct or update previously submitted status responses.

        One or more amended CSV response files may be uploaded in the same request.

        The request format is identical to `/data/upload`. Use this endpoint when you need to correct or update a response that was already submitted.

        Accepted files are queued for validation. Full row-level validation may continue after the response is returned.
      tags:
      - Upload
      x-codeSamples:
      - lang: Shell
        label: curl
        source: |-
          curl -X POST "https://api.drop.privacy.ca.gov/data/amend" \
            -H "accept: application/json" \
            -H "X-API-KEY: your-api-key-here" \
            -F "files=@20260312_4821_Email.csv;type=text/csv"
      security:
      - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - files
              properties:
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: One or more amended CSV response files. Same naming and schema requirements as the `/data/upload`
                    endpoint.
            examples:
              amendEmail:
                $ref: '#/components/examples/UploadEmailFile'
      responses:
        '202':
          description: At least one file was accepted and queued for validation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
              examples:
                accepted:
                  $ref: '#/components/examples/UploadAmendAcceptedResponse'
                mixed:
                  $ref: '#/components/examples/UploadMixedResponse'
        '400':
          description: Request is malformed or no files were accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadResponse'
              examples:
                invalidHeader:
                  $ref: '#/components/examples/UploadInvalidHeaderResponse'
                duplicateFile:
                  $ref: '#/components/examples/UploadDuplicateFileResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: The amend request cannot be processed in the current state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MessageResponse'
              example:
                message: The amend request cannot be processed in the current state.
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: API key issued through the Data Broker Portal. The key grants access only to the consumer deletion lists
        selected during setup.
  schemas:
    ListType:
      type: string
      enum:
      - NDZ
      - Email
      - Phone
      - MAID
      - NameVIN
      - CTVID
      description: Consumer deletion list type. Removed is a file type, not a consumer deletion list type.
    FileNamingConvention:
      type: object
      description: Structure embedded in DROP CSV file names.
      properties:
        YYYYMMDD:
          type: string
          pattern: ^[0-9]{8}$
          description: File date.
        DataBrokerId:
          type: string
          pattern: ^[0-9]{4}$
          description: Short data broker identifier, currently 4 digits.
        DataType:
          $ref: '#/components/schemas/DataType'
        OptionalSuffix:
          type: string
          maxLength: 10
          description: Optional suffix appended before `.csv` when uploading multiple response files for the same downloaded
            list, for example `part01` in `20260312_4821_Email_part01.csv`.
          pattern: ^[A-Za-z0-9]{1,10}$
    UploadResponse:
      type: object
      description: Response returned after upload or amend files are accepted or rejected.
      required:
      - message
      - acceptedCount
      - rejectedCount
      - accepted
      - rejected
      properties:
        message:
          type: string
          description: Summary of the upload result.
        acceptedCount:
          type: integer
          description: Number of files accepted for validation.
          example: 1
        rejectedCount:
          type: integer
          description: Number of files rejected before validation.
          example: 0
        accepted:
          type: array
          description: Files accepted for validation.
          items:
            $ref: '#/components/schemas/AcceptedFileResult'
        rejected:
          type: array
          description: Files rejected before validation.
          items:
            $ref: '#/components/schemas/RejectedFileResult'
    StatusCode:
      type: integer
      description: |
        Status code reported for each work item in the uploaded CSV.
      enum:
      - 2
      - 3
      - 4
      - 5
      x-enum-descriptions:
      - Exempted - match found and all personal information is exempt
      - Deleted - match found and non-exempt personal information was deleted
      - Opted out - multiple consumers are linked to the same identifier and all were opted out of sale or sharing
      - Not found - no match found after completing the matching process
    DownloadedCSVRow:
      type: object
      description: A single row from a deletion list CSV file. All deletion list files use `ID,Hash`. For NDZ and NameVIN,
        the `Hash` value is the final hash generated through composite hashing.
      properties:
        ID:
          $ref: '#/components/schemas/WorkItemId'
        Hash:
          $ref: '#/components/schemas/HashBase64'
      required:
      - ID
      - Hash
    UploadCSVRow:
      type: object
      description: A single row in the response CSV uploaded by the data broker.
      required:
      - Id
      - Status
      properties:
        Id:
          $ref: '#/components/schemas/WorkItemId'
        Status:
          $ref: '#/components/schemas/StatusCode'
    MessageResponse:
      type: object
      required:
      - message
      properties:
        message:
          type: string
          description: Human-readable response message.
    AcceptedFileResult:
      type: object
      required:
      - fileName
      - fileSizeBytes
      properties:
        fileName:
          type: string
          description: Name of the accepted CSV file.
          example: 20260312_4821_Email.csv
        fileSizeBytes:
          type: integer
          description: Size of the accepted file in bytes.
          example: 2849112
    RejectedFileResult:
      type: object
      required:
      - fileName
      - message
      properties:
        fileName:
          type: string
          description: Name of the rejected file.
          example: notes.txt
        message:
          type: string
          description: Reason the file was rejected.
          example: Only CSV files are accepted.
    RemovedCSVRow:
      type: object
      description: A single row from the Removed CSV file.
      properties:
        ID:
          $ref: '#/components/schemas/WorkItemId'
        Hash:
          $ref: '#/components/schemas/HashBase64'
        ListType:
          $ref: '#/components/schemas/ListType'
      required:
      - ID
      - Hash
      - ListType
    HashBase64:
      type: string
      minLength: 44
      maxLength: 44
      pattern: ^[A-Za-z0-9+/]{43}=$
      description: SHA-256 hash output encoded as Base64.
      example: KA18MT/ph6IHYjzT9zwETySDQyvSh87YuoSBpOQtkhE=
    WorkItemId:
      type: string
      description: DROP work item identifier from the downloaded file.
      example: '679'
    DataType:
      type: string
      enum:
      - NDZ
      - Email
      - Phone
      - MAID
      - NameVIN
      - CTVID
      - Removed
      description: CSV data type used in DROP file names. Removed is used for removed identifiers and is not a consumer deletion
        list type.
  responses:
    BadRequest:
      description: Bad request. Fix the request, file, or CSV format before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MessageResponse'
          example:
            message: Upload request is invalid. Fix the rejected files and try again.
    Unauthorized:
      description: API key is missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MessageResponse'
          example:
            message: API key is missing or invalid. Fix or regenerate the API key and try again.
    Forbidden:
      description: Broker is not eligible to access this operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MessageResponse'
          examples:
            forbidden:
              summary: Not eligible
              value:
                message: Your account is not eligible for this operation. Resolve any account, registration, payment, or access
                  issue and try again.
            noSelectedLists:
              summary: No selected lists
              value:
                message: No identifier list preferences are enabled. Select at least one list and try again.
    NotFound:
      description: Endpoint URL is wrong or resource path does not exist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MessageResponse'
          example:
            message: The requested endpoint was not found. Check the URL/path and try again.
    TooManyRequests:
      description: Too many requests. Wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MessageResponse'
          example:
            message: Too many requests. Wait and try again.
      headers:
        Retry-After:
          description: Suggested number of seconds to wait before retrying.
          schema:
            type: integer
            minimum: 1
          example: 30
    ServerError:
      description: Temporary system error. Retry later.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MessageResponse'
          example:
            message: The system is temporarily unavailable. Try again later.
  examples:
    DownloadArchiveContents:
      summary: ZIP archive contents
      description: The ZIP contains one CSV per selected list type and may include a Removed CSV file.
      value: |
        20260312_4821_Email.csv
        20260312_4821_NDZ.csv
        20260312_4821_Removed.csv
    DownloadEmailCsv:
      summary: Example downloaded Email CSV
      value: |
        ID,Hash
        679,KA18MT/ph6IHYjzT9zwETySDQyvSh87YuoSBpOQtkhE=
    DownloadNdzCsv:
      summary: Example downloaded NDZ CSV
      value: |
        ID,Hash
        679,PQOfn1RffEKmqMmNAzDKKaoZCwxWbQZkQzPWmQo9REA=
    UploadStatusCsv:
      summary: Example upload CSV
      value: |
        Id,Status
        679,2
        680,5
    UploadEmailFile:
      summary: Multipart upload example
      description: Upload one or more CSV response files using a downloaded file name or allowed suffix pattern.
      value:
        files:
        - '@20260312_4821_Email.csv'
        - '@20260312_4821_Email_part01.csv'
    UploadAcceptedResponse:
      summary: Accepted upload
      description: DROP accepted the file and queued it for validation.
      value:
        message: Upload received. Accepted files were queued for validation.
        acceptedCount: 1
        rejectedCount: 0
        accepted:
        - fileName: 20260312_4821_Email.csv
          fileSizeBytes: 2849112
        rejected: []
    UploadInvalidHeaderResponse:
      summary: Rejected upload with invalid CSV header
      description: The CSV header was not exactly `Id,Status`.
      value:
        message: Upload request is invalid. Fix the rejected files and try again.
        acceptedCount: 0
        rejectedCount: 1
        accepted: []
        rejected:
        - fileName: 20260312_4821_Email.csv
          message: 'Invalid CSV header. Expected: Id,Status.'
    UploadDuplicateFileResponse:
      summary: Rejected duplicate file name
      description: The same file name was already uploaded for the current download. Use a unique optional suffix.
      value:
        message: Upload request is invalid. Fix the rejected files and try again.
        acceptedCount: 0
        rejectedCount: 1
        accepted: []
        rejected:
        - fileName: 20260312_4821_Email.csv
          message: A file with this name was already uploaded for the current download. Use a unique suffix and try again
    UploadAmendAcceptedResponse:
      summary: Accepted amendment
      description: DROP accepted the amended file and queued it for validation.
      value:
        message: Upload received. Accepted files were queued for validation.
        acceptedCount: 1
        rejectedCount: 0
        accepted:
        - fileName: 20260312_4821_Email.csv
          fileSizeBytes: 2849112
        rejected: []
    DownloadRemovedCsv:
      summary: Example Removed CSV
      value: |
        ID,Hash,ListType
        679,KA18MT/ph6IHYjzT9zwETySDQyvSh87YuoSBpOQtkhE=,Email
    UploadMixedResponse:
      summary: Accepted and rejected files
      description: One file was accepted and one file was rejected before validation.
      value:
        message: Upload received. Accepted files were queued for validation. Rejected files were not accepted.
        acceptedCount: 1
        rejectedCount: 1
        accepted:
        - fileName: 20260312_4821_Email.csv
          fileSizeBytes: 2849112
        rejected:
        - fileName: notes.txt
          message: Only CSV files are accepted.
