openapi: 3.1.0
info:
  title: KYC & Verification
  version: "1"
  description: Upload and list KYC documents and sync legal entities.
tags:
  - name: KYC documents
    description: KYC documents for a merchant.
paths:
  /v1/merchants/{merchantId}/onboarding/documents:
    get:
      operationId: listMerchantKycDocuments
      summary: List KYC documents
      description: Lists the KYC documents uploaded for the merchant's verification.
        This covers the merchant's primary legal entity, its associated legal
        entities (for example owners and signatories) and its transfer
        instruments. Only file names and upload times are returned, never the
        document contents. Returns an empty list when the merchant has no legal
        entity on file, or when the verification service can't be reached. The
        merchant must belong to your reseller, otherwise 404.
      tags:
        - KYC documents
      security:
        - ApiKeyAuth: []
      parameters:
        - name: merchantId
          in: path
          required: true
          description: Numeric merchant id. Must be a positive integer belonging to your
            reseller.
          schema:
            type: integer
            minimum: 1
          example: 123456
      responses:
        "200":
          description: KYC documents on file.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CpMerchantKycDocumentsResponse"
              examples:
                documents:
                  summary: Merchant with uploaded documents
                  value:
                    data:
                      documents:
                        - name: passport.pdf
                          uploaded_at: 2026-02-14T09:30:00.000Z
                        - name: bank-statement.pdf
                          uploaded_at: 2026-02-15T11:00:00.000Z
                none:
                  summary: No legal entity or no documents
                  value:
                    data:
                      documents: []
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: The merchant does not belong to your reseller.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                notFound:
                  summary: Merchant not found or not in your reseller
                  value:
                    error:
                      code: NOT_FOUND
                      message: Merchant not found
        "422":
          description: The merchantId path segment is not a positive integer.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                invalidMerchantId:
                  summary: Non-numeric merchant id
                  value:
                    error:
                      code: VALIDATION_ERROR
                      message: merchantId is required
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
    servers:
      - url: https://sandbox-portal.datmanlabs.com/api
        description: Sandbox
  /api/v1/documents/upload-document/{merchantId}:
    servers:
      - url: https://portal-api-sandbox.datmancrm.com/documents/sandbox
        description: Sandbox
      - url: https://portal-api.datmancrm.com/documents/prod
        description: Production
    post:
      operationId: uploadOnboardingDocument
      summary: Upload a KYC document for a merchant
      description: Uploads one onboarding (KYC) document for a merchant as
        multipart/form-data. The new document replaces any earlier document of
        the same type and is set to need approval. Only merchants created
        through the merchant association API can receive documents.
      tags:
        - KYC documents
      security:
        - DocumentUploadApiKey: []
      parameters:
        - name: merchantId
          in: path
          required: true
          description: Datman merchant ID.
          schema:
            type: string
            pattern: ^\d+$
          example: "1000001"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/DocumentUploadForm"
            examples:
              passport:
                summary: Upload a passport scan
                value:
                  documentTypeId: 1
                  file: (binary PDF, 100 KB to 10 MB)
      responses:
        "200":
          description: 'Document uploaded. The body is JSON-encoded twice: it decodes to
            the JSON string `"success"` (including the quote characters).'
          content:
            application/json:
              schema:
                type: string
              examples:
                success:
                  summary: Upload succeeded
                  value: '"success"'
        "400":
          description: Multipart body could not be parsed, more than one file was sent, or
            the form failed validation (file type, 100 KB to 10 MB size,
            documentTypeId 1-15).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageError"
              examples:
                fileTooSmall:
                  summary: File size out of range
                  value:
                    message: File size must be between 100kb to 10mb.
                tooManyFiles:
                  summary: More than one file
                  value:
                    message: You can only upload one document at a time
        "401":
          description: Missing or wrong `api_key` header.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageError"
              examples:
                unauthorized:
                  summary: Wrong document upload key
                  value:
                    message: UNAUTHORIZED
        "404":
          description: "The merchant was not created through the merchant association API.
            The body is JSON-encoded twice: it decodes to a string that holds a
            JSON object."
          content:
            application/json:
              schema:
                type: string
              examples:
                notAssociated:
                  summary: Merchant not created by the association API
                  value: '{"message":"Document upload is allowed only for merchants created by MID
                    creation API"}'
        "429":
          description: Rate limit exceeded for the client IP. The body is a bare JSON
            string.
          content:
            application/json:
              schema:
                type: string
              examples:
                rateLimited:
                  summary: Rate limit exceeded
                  value: Too many requests with the same reference
        "500":
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerError"
components:
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable SCREAMING_SNAKE error code.
              examples:
                - UNAUTHORIZED
                - FORBIDDEN
                - VALIDATION_ERROR
                - RATE_LIMITED
                - INTERNAL_ERROR
                - DB_TIMEOUT
                - DB_CLIENT_CLOSED
            message:
              type: string
              description: Human-readable error message.
            details:
              description: Optional extra context about the error.
    CpKycDocument:
      type: object
      required:
        - name
        - uploaded_at
      properties:
        name:
          type: string
          description: File name as uploaded.
        uploaded_at:
          type:
            - string
            - "null"
          format: date-time
          description: Last modification time of the document.
    CpMerchantKycDocumentsResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - documents
          properties:
            documents:
              type: array
              items:
                $ref: "#/components/schemas/CpKycDocument"
    ServerError:
      type: object
      properties:
        message:
          type: string
          example: Internal server error
    DocumentUploadForm:
      type: object
      required:
        - file
        - documentTypeId
      properties:
        file:
          type: string
          format: binary
          description: The document (JPG, JPEG, PNG or PDF, 100 KB to 10 MB). The part
            must have a Content-Type of image/jpeg, image/png or
            application/pdf. Send exactly one file, in a form field named
            `file`.
        documentTypeId:
          type: integer
          minimum: 1
          maximum: 15
          description: Datman document type ID.
      additionalProperties: false
    MessageError:
      type: object
      properties:
        message:
          type: string
          description: Error message.
  responses:
    Unauthorized:
      description: Missing, invalid, expired or revoked credentials.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: UNAUTHORIZED
              message: Invalid, expired, or revoked API key
    Forbidden:
      description: The caller may not access this route or reseller (for example the
        API key has no reseller, or the body names a different reseller).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: FORBIDDEN
              message: Reseller not allowed for this caller
    RateLimited:
      description: The per-reseller rate limit was exceeded.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: RATE_LIMITED
              message: Rate limit exceeded
    InternalError:
      description: The query or a required configuration failed. Underlying details
        are not returned.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: INTERNAL_ERROR
              message: Query failed
    ServiceUnavailable:
      description: The database timed out or closed the connection. Safe to retry.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: DB_TIMEOUT
              message: The database did not respond in time. Please retry.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: api_key
      description: Partner API key issued by Datman.
    DocumentUploadApiKey:
      type: apiKey
      in: header
      name: api_key
      description: Document upload API key issued by Datman for the document upload
        endpoint. It is a different key from the partner API key.
