openapi: 3.0.1
info:
  title: Personal Account Data (PAD) APIs for touchpoints and Service Engine
  description: |-
    APIs for touchpoints and Service Engine to manage Personal Account Data (PAD) on OVpay tokens (xTATs).\
    These APIs connect directly to the PADP APIs in GBO APIM and are implemented in Logic Apps 
    in the Integration Layer.
  version: '1.0'
servers:
  - url: https://api.integratielaag.nl/abt/touchpoint/1.0
tags:
  - name: Personal Data APIs for touchpoints
    description: Personal Data APIs for touchpoints, no Service Engine in between!
  - name: Personal Data APIs for Service Engine
    description: Personal Data APIs for Service Engine, not to be exposed to touchpoints!
paths:
  /personal-data/{xtat}:
    post:
      tags:
        - Personal Data APIs for touchpoints
      summary: Add the supplied Personal Data to the given xTAT (that should not contain any Personal Data yet).
      description: |-
       - The given xTAT should not contain any Personal Data yet (the PATCH endpoint should be used in that case) - if the given xTAT already contains Personal Data, an error is thrown;
       - The given e-mail address will be used for future OTP challenges to manage the Personal Data in the future - this e-mail adress should therefore be validated;
       - If the e-mail address is not yet validated by other means (e.g. the e-mail address is used for login, or is entered twice to prevent typos), an OTP challenge for the e-mail address should be triggered and supplied to this endpoint;
       - xTAT and e-mail address are always required, for the Personal Data it is allowed to supply any subset, or all three data attributes;
       - Each of the three data attributes is validated - for the requirements per attribute, see the descriptions in the request details below;
       - If any attribute fails validation, none of the attributes will be added to the xTAT.
      operationId: CreatePersonalData
      parameters:
        - name: xtat
          in: path
          required: true
          example: 'c3a6c0f2-3b6a-4b9a-9c5d-5d9c6b3a4c5d'
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  description: Email address to be used for OTP challenges to prove ownership and manage the Personal Data in the future
                  example: 8Z9dG@example.com
                otp:
                  type: string
                  pattern: ^[0-9]{6}$
                  description: OTP is optional, should be supplied if the e-mail address is not yet validated by other means (e.g. the e-mail address is used for login, or is entered twice to prevent typo's)  
                  example: "053395"
                name:
                  type: string
                  description: Should consist of at least two words (first name and last name)
                  example: John Doe
                birthDate:
                  type: string
                  format: date
                  description: Should be a date between 1900-01-01 and today, in the format `YYYY-MM-DD`
                  example: 2000-01-01
                photo:
                  type: string
                  description: Should be a JPG image, with a filesize of max. 512KB and resolution between 520x520 and 720x720 pixels
                  format: binary
            encoding: 
              photo:
                contentType: image/jpeg
      responses:
        '201':
          description: Created
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    get: 
      tags:
        - Personal Data APIs for touchpoints
      summary: Retrieve the decrypted Personal Data for the given xTAT, using the OTP for verification of ownership.
      description: OTP challenge is required to retrieve the Personal Data.
      operationId: GetDecryptedPersonalData
      parameters:
        - name: xtat
          in: path
          required: true
          schema:
            type: string
            format: uuid
            example: 'c3a6c0f2-3b6a-4b9a-9c5d-5d9c6b3a4c5d'
        - name: otp
          in: query
          required: true
          description: OTP challenge code that the token owner received in their e-mail inbox. OTP is always required for managing existing Personal Data.
          schema:
            type: string
            pattern: ^[0-9]{6}$
            example: "053395"
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DecryptedPersonalData'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Personal Data APIs for touchpoints
      summary: Update the supplied personal data for the given xTAT, using the OTP for verification of ownership.
      description: This is a PATCH call, so only the personal data that the user desires to change need to be supplied. Integration layer supplements with any other existing personal data to be able to call GBO (PUT call).
      operationId: UpdatePersonalData
      parameters:
        - name: xtat
          in: path
          required: true          
          schema:
            type: string
            format: uuid
            example: 'c3a6c0f2-3b6a-4b9a-9c5d-5d9c6b3a4c5d'
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - otp
              properties:
                otp:
                  type: string
                  pattern: ^[0-9]{6}$
                  description: OTP challenge code that the token owner received in their e-mail inbox. OTP is always required for managing existing Personal Data.
                  example: "053395"
                name:
                  type: string
                  description: Should consist of at least two words (first name and last name)
                  example: John Doe
                birthDate:
                  type: string
                  format: date
                  description: Should be a date between 1900-01-01 and today, in the format `YYYY-MM-DD`
                  example: 2000-01-01
                photo:
                  type: string
                  description: Should be a JPG image, with a filesize of max. 512KB and resolution between 520x520 and 720x720 pixels
                  format: binary
            encoding: 
              photo:
                contentType: image/jpeg
      responses:
        '200':
          description: OK
  /personal-data/generate-otp:
    get:
      tags:
        - Personal Data APIs for touchpoints
      summary: Trigger OTP email for the given xTAT or e-mail address, to prove ownership
      operationId: GenerateOtp
      description: |-
        Generate an OTP challenge e-mail to prove ownership of the given e-mail address or xTAT.\
        Only one type of parameter can be supplied, either xtat or email. When both are supplied, an error is thrown.
      parameters:
        - name: xtat
          in: query
          required: false          
          schema:
            type: string
            format: uuid
            example: 'c3a6c0f2-3b6a-4b9a-9c5d-5d9c6b3a4c5d'
        - name: email
          in: query
          required: false
          schema:
            type: string
            format: email
            example: 'sV4yj@example.com'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OtpResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /personal-data/{xtat}/administrative-data:
    get:
      tags:
        - Personal Data APIs for Service Engine
      summary: API 1211 - Get Administrative Data
      description: Integration Layer utilizes PAD management V2 in GBO APIM (`/pad-management/v2/..`)
      operationId: GetAdministrativeData
      parameters:
        - name: xtat
          description: xTAT to get administrative data for
          in: path
          required: true          
          schema:
            type: string
            format: uuid
            example: 'c3a6c0f2-3b6a-4b9a-9c5d-5d9c6b3a4c5d'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdministrativeData"
              examples:
                Valid and complete PAD:
                  summary: Valid and complete PAD
                  value:
                    name:
                      inaccuracyFlag: false
                      inaccuracyFlagReason: null
                      inaccuracyFlagCounter: 0
                      changeCounter: 0
                      maxUpdatesVerificationCount: 1
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                    photo:
                      inaccuracyFlag: false
                      inaccuracyFlagReason: null
                      inaccuracyFlagCounter: 0
                      changeCounter: 0
                      maxUpdatesVerificationCount: 5
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                    birthdate:
                      inaccuracyFlag: false
                      inaccuracyFlagReason: null
                      inaccuracyFlagCounter: 0
                      changeCounter: 0
                      maxUpdatesVerificationCount: 3
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                Partially filled PAD (no photo):
                  summary: Partially filled PAD (no photo)
                  value:
                    name:
                      inaccuracyFlag: false
                      inaccuracyFlagReason: null
                      inaccuracyFlagCounter: 0
                      changeCounter: 0
                      maxUpdatesVerificationCount: 1
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                    photo: null
                    birthdate:
                      inaccuracyFlag: false
                      inaccuracyFlagReason: null
                      inaccuracyFlagCounter: 0
                      changeCounter: 0
                      maxUpdatesVerificationCount: 3
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                Flagged PAD:
                  summary: Flagged PAD
                  value:
                    name:
                      inaccuracyFlag: true
                      inaccuracyFlagReason: "Invalid name"
                      inaccuracyFlagCounter: 1
                      changeCounter: 1
                      maxUpdatesVerificationCount: 1
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                    photo:
                      inaccuracyFlag: true
                      inaccuracyFlagReason: "Invalid photo"
                      inaccuracyFlagCounter: 1
                      changeCounter: 0
                      maxUpdatesVerificationCount: 5
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
                    birthdate:
                      inaccuracyFlag: true
                      inaccuracyFlagReason: "Invalid birthdate"
                      inaccuracyFlagCounter: 1
                      changeCounter: 2
                      maxUpdatesVerificationCount: 3
                      lastChangeDate: "2025-03-26T10:18:42.947"
                      isValidated: false
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                Invalid UUID:
                  summary: Invalid UUID
                  value:
                    errors:
                      code: "0x03000103"
                      data: [
                        "geen-uuid"
                      ]
                      message: "The provided scTat is not a valid UUID"
                    exceptionClassName: "PadpConstraintViolationException"
                    exceptionStackTrace: "not available because debug mode is turned off"
        "404":
          description: Not Found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                No PAD found for xTAT:
                  summary: No PAD found for xTAT
                  value:
                    errors:
                      code: "0x03000105"
                      data: []
                      message: "Transit account not found"
                    exceptionClassName: "PadpEntityNotFoundException"
                    exceptionStackTrace: "not available because debug mode is turned off"
        "500":
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                Unknown xTAT:
                  summary: Unknown xTAT
                  value:
                    errors:
                      code: "0x00000001"
                      data: null
                      message: "400 : \"{\"errorMessage\":{\"referenceId\":\"076f0de4-df33-42a3-add0-def971ab6679\",\"message\":\"Unknown external transit account token.\"},\"businessExceptions\":[{\"code\":\"TM0207\",\"message\":\"Unknown external transit account token.\"}]}\""
                    exceptionClassName: "BadRequest"
                    exceptionStackTrace: "not available because debug mode is turned off"
components:
  schemas:
    AdministrativeData:
      type: object
      properties:
        name:
          $ref: '#/components/schemas/AdministrativeDataElement'
        photo:
          $ref: '#/components/schemas/AdministrativeDataElement'
        birthdate:
          $ref: '#/components/schemas/AdministrativeDataElement'
      additionalProperties: false
    AdministrativeDataElement:
      type: object
      properties:
        inaccuracyFlag:
          type: boolean
        inaccuracyFlagReason:
          type: string
          nullable: true
        inaccuracyFlagCounter:
          type: integer
          format: int32
        changeCounter:
          type: integer
          format: int32
        maxUpdatesVerificationCount:
          type: integer
          format: int32
        lastChangeDate:
          type: string
          format: date-time
        isValidated:
          type: boolean
      additionalProperties: false
    DecryptedData:
      type: object
      properties:
        name:
          type: string
          nullable: true
        birthdate:
          type: string
          nullable: true
        photo:
          type: string
          description: Base64 encoded photo
          format: byte
          nullable: true
      additionalProperties: false
    DecryptedPersonalData:
      type: object
      properties:
        decryptedData:
          $ref: '#/components/schemas/DecryptedData'        
      additionalProperties: false
    Error:
      type: object
      properties:
        code:
          type: string
          nullable: true
        data:
          type: array
          items:
            type: string
          nullable: true
        message:
          type: string
          nullable: true
      additionalProperties: false
    ErrorResponse:
      type: object
      properties:
        errors:
          type: array
          items:
            $ref: '#/components/schemas/Error'
          nullable: true
        exceptionClassName:
          type: string
          nullable: true
        exceptionStackTrace:
          type: string
          nullable: true
      additionalProperties: false
    Metadata:
      type: object
      properties:
        encryptedEphemeralKey:
          type: string
          nullable: true
      additionalProperties: false
    OtpResponse:
      type: object
      properties:
        maskedEmailAddress:
          type: string
          nullable: true
      additionalProperties: false    