openapi: 3.1.0
info:
  title: ESTCA Registry API
  version: 1.0.0
  description: |
    Public and airline lookup for the ESTCA Register plus private
    server-to-server ingest endpoints for Certified-ESTCA.eu. Certified-ESTCA
    writes the minimal registry copy with scoped ingest keys. Airlines receive
    read-only verification access with scoped verify keys.     Owner personal data is never returned by public verification (ESTCA
    Standard v1.0, Part E.2.2). The canine name may appear in verify responses
    when Certified-ESTCA.eu includes `animalName` on certification upsert.

    ESTCA verification supports assessment of canine travel-readiness evidence.
    It does not grant flight permission and does not replace airline approval,
    animal-health documentation, import/export requirements or aircraft safety
    assessment.
servers:
  - url: https://estca.eu
  - url: http://localhost:3000
paths:
  /api/v1/verify:
    get:
      operationId: verifyByPassport
      summary: Verify certification by pet passport number
      security:
        - PartnerApiKey: []
        - {}
      parameters:
        - in: query
          name: passport
          required: true
          schema:
            type: string
          description: Pet passport as printed; normalised to uppercase alphanumerics
        - in: header
          name: x-api-key
          required: false
          schema:
            type: string
          description: Optional partner API key with verify:read scope (higher rate limits)
        - in: query
          name: api_key
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyResponse"
        "400":
          description: Invalid passport or request
        "401":
          description: Invalid API key or missing verify:read scope
        "429":
          description: Rate limited
  /api/v1/ingest/certifications/upsert:
    post:
      operationId: ingestCertificationUpsert
      summary: Create or update the minimal registry copy from Certified-ESTCA.eu
      description: Requires a Certified-ESTCA ingest client with ingest:certifications scope.
      security:
        - IngestApiKey: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CertificationUpsert"
      responses:
        "200":
          description: Accepted or idempotently replayed
        "401":
          description: Missing API key or missing ingest:certifications scope
        "409":
          description: Stale sourceUpdatedAt rejected and recorded as a registry event
        "422":
          description: Invalid payload
  /api/v1/ingest/certifications/status:
    post:
      operationId: ingestCertificationStatus
      summary: Update Certified-originated certificate status
      description: Requires a Certified-ESTCA ingest client with ingest:certifications scope.
      security:
        - IngestApiKey: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CertificationStatusUpdate"
      responses:
        "200":
          description: Accepted or idempotently replayed
        "401":
          description: Missing API key or missing ingest:certifications scope
        "404":
          description: Source certificate not found
        "409":
          description: Stale sourceUpdatedAt rejected and recorded as a registry event
        "422":
          description: Invalid payload
  /api/v1/ingest/trainers/upsert:
    post:
      operationId: ingestTrainerUpsert
      summary: Create or update a trainer registry snapshot from Certified-ESTCA.eu
      description: Requires a Certified-ESTCA ingest client with ingest:trainers scope.
      security:
        - IngestApiKey: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TrainerUpsert"
      responses:
        "200":
          description: Accepted or idempotently replayed
        "401":
          description: Missing API key or missing ingest:trainers scope
        "422":
          description: Invalid payload
components:
  securitySchemes:
    IngestApiKey:
      type: apiKey
      in: header
      name: X-Ingest-API-Key
      description: Server-to-server key scoped to ingest:certifications and/or ingest:trainers.
    PartnerApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Read-only partner key scoped to verify:read.
  parameters:
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: false
      schema:
        type: string
        maxLength: 128
  schemas:
    RegistryInternalStatus:
      type: string
      enum: [active, expired, under_review, suspended, withdrawn]
    CertificationUpsert:
      type: object
      required:
        - sourceCertificateId
        - publicCertificationNumber
        - passportNumber
        - status
        - certifiedAt
        - expiresAt
        - trainer
        - sourceUpdatedAt
      properties:
        sourceCertificateId:
          type: string
        sourcePlatform:
          type: string
          default: certified-estca.eu
        publicCertificationNumber:
          type: string
        passportNumber:
          type: string
        status:
          $ref: "#/components/schemas/RegistryInternalStatus"
        certifiedAt:
          type: string
          format: date
        expiresAt:
          type: string
          format: date
        microchip:
          type: string
        animalName:
          type: string
          description: Optional canine name for public register/verify (preferred key).
        dogName:
          type: string
          description: Same as animalName if your integration already uses this field name.
        petName:
          type: string
          description: Same as animalName (alias).
        canineName:
          type: string
          description: Same as animalName (alias).
        dog_name:
          type: string
          description: Same as animalName (snake_case alias).
        dog:
          oneOf:
            - type: string
            - type: object
              properties:
                name:
                  type: string
          description: Same as animalName — string, or object with `name` (e.g. nested dog record).
        name:
          type: string
          description: Same as animalName if payload uses a plain `name` field (lowest precedence among name aliases).
        trainer:
          type: object
          required: [accreditationNumber, displayName]
          properties:
            accreditationNumber:
              type: string
            displayName:
              type: string
        sourceUpdatedAt:
          type: string
          format: date-time
    CertificationStatusUpdate:
      type: object
      required: [sourceCertificateId, status, sourceUpdatedAt]
      properties:
        sourceCertificateId:
          type: string
        sourcePlatform:
          type: string
          default: certified-estca.eu
        status:
          $ref: "#/components/schemas/RegistryInternalStatus"
        sourceUpdatedAt:
          type: string
          format: date-time
    TrainerUpsert:
      type: object
      required: [accreditationNumber, displayName]
      properties:
        accreditationNumber:
          type: string
        displayName:
          type: string
        isActive:
          type: boolean
          default: true
    VerifyResponse:
      type: object
      required: [query, result]
      properties:
        query:
          type: object
          properties:
            passport:
              type: string
        result:
          type: object
          required: [registrationStatus]
          properties:
            registrationStatus:
              type: string
              enum:
                [
                  Certified,
                  Expired,
                  "Not Found",
                  "Under Review",
                  Suspended,
                  Withdrawn,
                ]
            certificationNumber:
              type: string
            passportNumber:
              type: string
              description: Normalised passport as used for lookup
            animalName:
              type: string
              description: Present when Certified supplied animalName on ingest
            certifiedAt:
              type: string
            expiresAt:
              type: string
            trainer:
              type: object
              properties:
                name:
                  type: string
                accreditationNumber:
                  type: string
