openapi: 3.1.0
info:
  title: GoodPay API reference
  version: 0.0.1
servers:
  - url: https://demo.goodpay.dev
    description: Demo server URL
security:
  - ApiKeyAuth: [ ]
paths:
  "/v1/issuer/uk-domestic-bank-details":
    get:
      summary: Get UK domestic bank details for the issuer
      description: Retrieves issuer's UK domestic bank sort codes. Optionally filter by sort code.
      tags: [ Issuer UK Domestic Bank Details ]
      operationId: Get IssuerUKDomesticBankDetails
      parameters:
        - name: sortCode
          in: query
          required: false
          schema:
            type: string
            pattern: '^([0-9]{6})$|^([0-9]{2}-[0-9]{2}-[0-9]{2})$'
            example: "12-34-56"
      responses:
        '200':
          description: Successfully retrieved bank details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuerUKDomesticBankDetailsV1'
        '400':
          description: Invalid creation request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '404':
          description: Sort code not found.

    post:
      summary: Create UK domestic bank details for the issuer
      description: >
        This endpoint creates UK domestic bank account details for an issuer. It supports both single and bulk creation.
        To bulk create, provide a list of sort codes with their corresponding branch names in the request body.
        Each sort code must follow the format "123456" or "12-34-56".
      tags:
        - Issuer UK Domestic Bank Details
      operationId: createIssuerUKDomesticBankDetails
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssuerUKDomesticBankDetailsV1'
      responses:
        '200':
          description: Bank details created successfully. Returns the created data.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuerUKDomesticBankDetailsV1'
        '400':
          description: Invalid input — check sort code format or required fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'

  "/v1/issuer/sepa-details":
    get:
      summary: Get SEPA bank details for the issuer
      description: Retrieves issuer's SEPA BIC and associated bank branch details. Optionally filter by BIC.
      tags: [ Issuer SEPA Bank Details ]
      operationId: Get IssuerSEPADetails
      parameters:
        - name: bic
          in: query
          required: false
          schema:
            type: string
            pattern: '^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$'
            example: "BNPAFRPPXXX"
      responses:
        '200':
          description: Successfully retrieved SEPA bank details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuerSEPADetailsV1'
        '400':
          description: Invalid request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '404':
          description: BIC not found.

    post:
      summary: Create SEPA bank details for the issuer
      description: >
        Creates SEPA BIC and associated bank information for an issuer. Supports single or bulk creation.
        Each BIC must be 8 or 11 uppercase alphanumeric characters (e.g., "BNPAFRPPXXX").
      tags: [ Issuer SEPA Bank Details ]
      operationId: createIssuerSEPADetails
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IssuerSEPADetailsV1'
      responses:
        '200':
          description: SEPA bank details created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IssuerSEPADetailsV1'
        '400':
          description: Invalid input — check BIC format or required fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'

  "/v1/identifier":
    post:
      description: Creates a new identifier.
      tags: [ Identifier ]
      operationId: Create Identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IdentifierV1Create'
      responses:
        '200':
          description: Identifier created successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentifierV1'
        '400':
          description: Invalid identifier details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '409':
          description: Failed creation because of a conflict on one of the fields.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'

    delete:
      summary: Delete an existing Identifier
      description: Deletes an existing identifier by UUID or goodIdentifier. One of them must be provided, not both.
      tags: [ Identifier ]
      operationId: deleteIdentifier
      parameters:
        - name: uuid
          in: query
          required: false
          description: UUID of the identifier to delete. Must not be set if goodIdentifier is used.
          example: "550e8400-e29b-41d4-a716-446655440000"
          schema:
            type: string
            format: uuid
        - name: goodIdentifier
          in: query
          required: false
          example: "gbp://pixie.orange.cat@fauxbank.gb"
          description: >
            Fully qualified good identifier in the format: `currency://name@issuer.country`.
            - `currency`: ISO 4217 (3-letter) currency code (e.g., `GBP`, `USD`)
            - `country`: ISO 3166-1 alpha-2 (2-letter) country code (e.g., `GB`, `US`)
            Allowed characters in name: a–z, A–Z, 0–9, dash, dot, plus, underscore, apostrophe.
            Either `uuid` or `goodIdentifier` must be provided — not both.
          schema:
            type: string
            pattern: "^[a-z]{3}://[a-zA-Z0-9+._'\\-]+@[a-z][a-z0-9\\-]+\\.[a-z]{2}$"
      responses:
        '204':
          description: Identifier deleted successfully.
        '400':
          description: Invalid request. Provide either uuid or goodIdentifier, not both.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '404':
          description: Identifier not found.

  "/v1/identifier/findByGoodTwoWords":
    post:
      description: Finds an identifier using GoodTwoWords.
      summary: Find By Good Two Words
      tags: [ Identifier ]
      operationId: FindBy GoodTwoWords
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GoodTwoWordsV1'
      responses:
        '200':
          description: Identifier retrieved successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentifierV1'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '404':
          description: Identifier not found.

  "/v1/identifier/findByGoodIdentifier":
    post:
      description: Finds an identifier using a goodIdentifier.
      tags: [ Identifier ]
      operationId: findByGoodIdentifier
      summary: Find By Good Identifier
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GoodIdentifierV1'
      responses:
        '200':
          description: Identifier retrieved successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentifierV1'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '404':
          description: Identifier not found.

  "/v1/identifier/findByAlias":
    post:
      description: Finds an identifier using an alias.
      summary: Find By Alias
      tags: [ Identifier ]
      operationId: FindBy Alias
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AliasV1'
      responses:
        '200':
          description: Identifier retrieved successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentifierV1'
        '400':
          description: Invalid request body.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '404':
          description: Identifier not found.

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: |
        API Key for Auth(X) to determine the issuer and permissions.
        
        **Demo Key:** 
        `Lg2vWofw8_K5RSrCmU_2BlrkIGXxh0rH-p_iCT2Q6Tr-tUPJXAERlJHZCSiKlYu6hJqUEMbctrW0vmq0ebX-wQ`
  schemas:
    ValidationError:
      type: object
      description: >
        Represents a single validation error encountered when processing a bad request.
        Typically used to indicate that a specific field in the request is missing, invalid,
        or in conflict with existing data. Multiple such errors may be returned in a single response.
      properties:
        field:
          type: string
          description: The name of the field that caused the validation error.
          example: "name"
        details:
          type: string
          description: A human-readable message describing what went wrong.
          example: "Name must not contain spaces or special characters."
        errorType:
          type: string
          description: >
            The category of the error:
            - `MISSING`: A required field was not provided.
            - `INVALID`: The value provided is invalid or malformed.
            - `CONFLICT`: The value is valid but causes a conflict (e.g., already exists).
          enum: [ INVALID, MISSING, CONFLICT ]
          example: INVALID

    IssuerUKDomesticBankDetailsV1:
      type: object
      description: A list of UK domestic sort codes and associated branch names for an issuer.
      properties:
        sortCodes:
          type: array
          items:
            $ref: '#/components/schemas/SortCodeV1'
      example:
        sortCodes:
          - sortCode: "12-34-56"
            branchName: "London Branch"
          - sortCode: "654321"
            branchName: "Manchester Branch"

    SortCodeV1:
      type: object
      description: Represents a UK sort code and its associated branch name.
      required:
        - sortCode
        - branchName
      properties:
        sortCode:
          type: string
          description: >
            The 6-digit UK sort code, formatted either as "123456" or "12-34-56".
            Must match one of the two valid formats.
          pattern: '^([0-9]{6})$|^([0-9]{2}-[0-9]{2}-[0-9]{2})$'
          example: "12-34-56"
        branchName:
          type: string
          description: Human-readable name of the bank branch associated with the sort code.
          example: "London Branch"

    BICV1:
      type: object
      description: Represents a SEPA BIC record and associated branch information.
      required:
        - bic
        - branchName
      properties:
        bic:
          type: string
          description: >
            Bank Identifier Code (BIC), also known as a SWIFT code. Must be 8 or 11 uppercase alphanumeric characters 
            identifying the recipient's bank. For example, "BNPAFRPPXXX" for BNP Paribas in France.
          pattern: '^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$'
          example: "BNPAFRPPXXX"
        branchName:
          type: string
          description: Human-readable name of the bank branch.
          example: "Paris Central Branch"
          maxLength: 70
        city:
          type: string
          description: City where the branch is located.
          example: "Paris"
          maxLength: 35
        postCode:
          type: string
          description: Postal code of the branch.
          example: "75008"
          maxLength: 16

    IssuerSEPADetailsV1:
      type: object
      description: A set of SEPA BIC entries for the issuer, representing bank routing information.
      properties:
        bics:
          type: array
          items:
            $ref: '#/components/schemas/BICV1'
      example:
        bics:
          - bic: "BNPAFRPPXXX"
            branchName: "Paris Central Branch"
            city: "Paris"
            postCode: "75008"
          - bic: "SOGEFRPP"
            branchName: "Lyon Main"
            city: "Lyon"
            postCode: "69001"

    ISO4217CurrencyCode:
      type: string
      description: ISO 4217 three-letter currency code for North America, Europe, and Gulf countries and StableCoin ticker code
      enum:
        - AED
        - ALL
        - BAM
        - BBD
        - BGN
        - BHD
        - BSD
        - BYN
        - BZD
        - CAD
        - CHF
        - CRC
        - CUP
        - CZK
        - DKK
        - DOP
        - EUR
        - GBP
        - GTQ
        - HNL
        - HTG
        - HUF
        - ISK
        - JMD
        - KWD
        - MDL
        - MKD
        - MXN
        - NIO
        - NOK
        - OMR
        - PLN
        - QAR
        - RON
        - RSD
        - RUB
        - SAR
        - SEK
        - TTD
        - UAH
        - USD
        - XCD
        - USDC
        - USDT
        - PYUSD
        - EUROE
        - EURT
      example: GBP

    ISO3166Alpha3CountryCode:
      type: string
      description: ISO 3166-1 alpha-3 country code (North America, Europe, Gulf)
      enum:
        - GBR
        - USA
        - ALB
        - AND
        - ARE
        - AUT
        - BEL
        - BGR
        - BHR
        - BHS
        - BIH
        - BLR
        - BLZ
        - BRB
        - CAN
        - CHE
        - CRI
        - CUB
        - CYP
        - CZE
        - DEU
        - DMA
        - DNK
        - DOM
        - ESP
        - EST
        - FIN
        - FRA
        - GRC
        - GRD
        - GTM
        - HND
        - HRV
        - HTI
        - HUN
        - IRL
        - ISL
        - ITA
        - JAM
        - KNA
        - KWT
        - LCA
        - LIE
        - LTU
        - LUX
        - LVA
        - MCO
        - MDA
        - MEX
        - MKD
        - MLT
        - MNE
        - NIC
        - NLD
        - NOR
        - OMN
        - PAN
        - POL
        - PRT
        - QAT
        - ROU
        - RUS
        - SAU
        - SLV
        - SMR
        - SRB
        - SVK
        - SVN
        - SWE
        - TTO
        - UKR
        - VAT
        - VCT
      example: GBR

    IdentifierV1Create:
      type: object
      required:
        - currency
        - country
        - name
      description: >
        An identifier uniquely represents a single bank account's payment details issued by an issuer.
        It defines the account using the country and currency of the account and a human-readable name
        selected by the issuer, ideally derived from the customer's name. It may also include a set of aliases
        for alternate referencing, a memorable "Good Two Words" label, issuer-specific private metadata,
        and structured payment details.
      properties:
        name:
          type: string
          pattern: "^[\\p{L}\\p{N}\\-+._']+$"
          minLength: 1
          maxLength: 255
          example: "pixie.orange.cat"
          description: >
            The identifier name. Must not contain spaces.
            Allows Unicode letters, digits, hyphens, dots, plus, underscores, and apostrophes.
            Maximum length is 255 characters.
        currency:
          $ref: '#/components/schemas/ISO4217CurrencyCode'
        country:
          $ref: '#/components/schemas/ISO3166Alpha3CountryCode'
        goodTwoWords:
          $ref: '#/components/schemas/GoodTwoWordsV1Create'
        aliases:
          type: array
          items:
            $ref: '#/components/schemas/AliasV1'
        paymentDetails:
          $ref: '#/components/schemas/PaymentDetailsV1'
        issuerPrivateData:
          $ref: '#/components/schemas/IssuerPrivateDataV1'

    LanguageTag:
      type: string
      description: Language tag (ISO 639-1) for locales
      enum:
        - en  # English
        - es  # Spanish
        - bg  # Bulgarian
        - cs  # Czech
        - da  # Danish
        - de  # German
        - el  # Greek
        - et  # Estonian
        - fi  # Finnish
        - fr  # French
        - ga  # Irish
        - hr  # Croatian
        - hu  # Hungarian
        - is  # Icelandic
        - it  # Italian
        - lt  # Lithuanian
        - lv  # Latvian
        - mk  # Macedonian
        - mt  # Maltese
        - nl  # Dutch
        - no  # Norwegian
        - pl  # Polish
        - pt  # Portuguese
        - ro  # Romanian
        - ru  # Russian
        - sk  # Slovak
        - sl  # Slovenian
        - sr  # Serbian
        - sv  # Swedish
        - uk  # Ukrainian
      example: en

    GoodTwoWordsV1Create:
      type: object
      description: A unique combination of two words in chosen locale which directly point to the identifier
      properties:
        locale:
          $ref: "#/components/schemas/LanguageTag"

    IdentifierV1:
      type: object
      description: >
        Represents a uniquely identified bank account issued by an issuer, along with its associated metadata.
        An identifier includes a UUID reference, issuer-defined name, country, currency, and optional fields such
        as aliases, a "Good Two Words" mnemonic, private issuer data, and structured payment details.
        This is the full representation of an identifier as persisted and retrieved from the system.
      properties:
        uuid:
          type: string
          format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
          description: >
            Unique id to refer to this identifier.
        name:
          type: string
          pattern: "^[\\p{L}\\p{N}\\-+._']+$"
          minLength: 1
          maxLength: 255
          example: "pixie.orange.cat"
          description: >
            The identifier name. Must not contain spaces.
            Allows Unicode letters, digits, hyphens, dots, plus, underscores, and apostrophes.
            Maximum length is 255 characters.
        currency:
          $ref: '#/components/schemas/ISO4217CurrencyCode'
        country:
          $ref: '#/components/schemas/ISO3166Alpha3CountryCode'
        goodTwoWords:
          $ref: '#/components/schemas/GoodTwoWordsV1'
        aliases:
          type: array
          items:
            $ref: '#/components/schemas/AliasV1'
        paymentDetails:
          $ref: '#/components/schemas/PaymentDetailsV1'
        issuerPrivateData:
          $ref: '#/components/schemas/IssuerPrivateDataV1'

    GoodTwoWordsV1:
      type: object
      description: A unique combination of two words in chosen locale which directly point to the identifier
      required:
        - firstWord
        - secondWord
      properties:
        firstWord:
          type: string
          example: "sideline"
        secondWord:
          type: string
          example: "extender"
        locale:
          $ref: "#/components/schemas/LanguageTag"

    GoodIdentifierV1:
      type: object
      description: >
        This object represents a standardised, transport-friendly format of an identifier,
        designed for usage in QR codes, NFC transmissions, or other external integrations.
        It encodes the core identifying fields — currency, name, issuer, and country —
        into a compact string format.
      required: [ goodIdentifier ]
      properties:
        goodIdentifier:
          type: string
          pattern: "^[a-z]{3}://[a-zA-Z0-9+._'\\-]+@[a-z][a-z0-9\\-]+\\.[a-z]{2}$"
          example: "gbp://pixie.orange.cat@fauxbank.gb"
          description: >
            Fully qualified good identifier in the format: `currency://name@issuer.country`.
            - `currency`: ISO 4217 (3-letter) currency code (e.g., `GBP`, `USD`)
            - `country`: ISO 3166-1 alpha-2 (2-letter) country code (e.g., `GB`, `US`)
            Allowed characters in name: letters, digits, hyphen, dot, plus, underscore, apostrophe.

    IssuerPrivateDataV1:
      type: object
      description: >
        Container for any private data an issuer may wish to associate with an identifier.
        This may include internal metadata, flags, or configuration values.
        The contents of this object are not exposed publicly and are accessible only to the issuer.
      properties:
        details:
          type: object
          additionalProperties: true
          description: >
            A flexible key-value store for issuer-specific private data.
            This field is accessible to the issuer only.
      example:
        details:
          internalReference: "abc123"
          isEnabled: true
          riskLevel: "medium"

    IntraBankDetailsV1:
      type: object
      description: >
        Contains private issuer-defined fields required to support intra-bank transfers.
        This may include internal routing metadata, account flags, or system-specific identifiers
        that help the issuer route or process transfers within their own infrastructure.
        Not exposed publicly.
      properties:
        details:
          type: object
          additionalProperties: true
          description: >
            A flexible key-value store for intra-bank transfer metadata.
            This field is private to the issuer.
      example:
        details:
          internalReference: "abc123"
          isEnabled: true
          riskLevel: "medium"

    PartnerBankDetailsV1:
      type: object
      description: >
        Contains structured metadata to support transfers involving partner banks operating under
        multi-lateral agreements with the issuer. This data enables interoperability, routing, and
        processing of payments between partner institutions while maintaining issuer-specific logic.
      properties:
        details:
          type: object
          additionalProperties: true
          description: >
            A flexible key-value store for partner bank transfer metadata.
            Used in the context of multi-lateral agreements between banks.
      example:
        details:
          externalReference: "abc123"
          isEnabled: true
          riskLevel: "medium"

    UKDomesticBankAccountDetailsV1:
      type: object
      required:
        - accountNumber
        - accountName
        - sortCode
      description: >
        Defines the UK domestic bank account details used for BACS and Faster Payments Service (FPS).
        Includes the account number, account name, and sort code. The provided sort code must match
        one of the sort codes registered by the issuer.
      properties:
        accountNumber:
          type: string
          description: 8-digit UK bank account number.
          pattern: '^[0-9]{8}$'
          example: "12345678"
        accountName:
          type: string
          description: >
            Account name as accepted by the Faster Payments Service (FPS). Should match the intended payee's name.
          example: "Pixie cat"
          maxLength: 140
          minLength: 1
        sortCode:
          type: string
          description: >
            UK sort code in either "123456" or "12-34-56" format. This must match a sort code associated with the issuer.
          pattern: '^([0-9]{6})$|^([0-9]{2}-[0-9]{2}-[0-9]{2})$'
          example: "12-34-56"

    SEPADetailsV1:
      type: object
      required:
        - iban
        - accountName
        - bic
      description: >
        Defines the SEPA Account details used for SEPA Credit Transfer (SCT) and SEPA Instant.
        Includes the iban, bic and account name. The provided bic must match
        one of the bic registered by the issuer.
      properties:
        iban:
          type: string
          description: >
            International Bank Account Number (IBAN) in standard format. Must begin with a two-letter country code, followed by a two-digit checksum and up to 30 alphanumeric characters. Used for SEPA transfers (CT and Instant).
          pattern: '^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$'
          example: "FR7630006000011234567890189"
        accountName:
          type: string
          description: >
            Name of the account holder as expected by the beneficiary bank. Should match the name used for SEPA Credit Transfer or SEPA Instant payments.
          example: "Pixie cat"
          maxLength: 70
          minLength: 1
        bic:
          type: string
          description: >
            Bank Identifier Code (BIC), also known as a SWIFT code. Must be 8 or 11 uppercase alphanumeric characters 
            identifying the recipient's bank. For example, "BNPAFRPPXXX" for BNP Paribas in France.
          pattern: '^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$'
          example: "BNPAFRPPXXX"

    PaymentDetailsV1:
      type: object
      description: >
        Container for specifying various types of payment details associated with the account.
        Supports different schemes such as UK domestic (BACS/FPS), intra-bank, and partner bank transfers.
        This is an extensible structure designed to grow as more payment types are supported.
      properties:
        ukDomesticBankAccountDetails:
          $ref: "#/components/schemas/UKDomesticBankAccountDetailsV1"
        intraBankDetails:
          $ref: "#/components/schemas/IntraBankDetailsV1"
        partnerBankDetails:
          $ref: "#/components/schemas/PartnerBankDetailsV1"
        sepaDetails:
          $ref: '#/components/schemas/SEPADetailsV1'

    AliasV1:
      type: object
      required:
        - value
        - type
      description: >
        Represents an alias used to uniquely refer to an identifier in a more user-friendly way.
        Enables sending or receiving payments using alternative references such as a phone number, email,
        national ID, or any custom value. Aliases are useful for resolving identifiers without requiring
        direct exposure of bank account details.
      properties:
        value:
          type: string
          description: The alias value (e.g., phone number, email, etc.) used to identify the account.
          example: "+447123456789"
        type:
          type: string
          description: The type of alias being used.
          enum: [ PHONE_NUMBER, EMAIL, NATIONAL_IDENTIFIER, OTHER ]
          example: PHONE_NUMBER
