openapi: 3.1.0
info:
  title: Verify Client API Integrations
  version: "0.1"
  description: >
    AI-friendly OpenAPI slice for server-to-server API integrations. This is not the full
    Verify Client API. Source of truth is api/routes/v1 and shared/*.ts.
servers:
  - url: /api/v1
    description: Browser/frontend proxy
  - url: http://localhost:8081/v1
    description: Local API server
security:
  - bearerAuth: []
paths:
  /microapps/system-tokens:
    post:
      operationId: createMicroAppSystemToken
      summary: Exchange API integration client credentials for a scoped system token.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SystemTokenRequest"
            examples:
              caseCreate:
                value:
                  clientId: 00000000-0000-0000-0000-000000000000
                  clientSecret: stored-secret
                  scopes:
                    - cases.create
                  expiresInSeconds: 3600
      responses:
        "200":
          description: System token issued.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemTokenResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /microapps/api/cases/ids:
    get:
      operationId: listMicroAppCaseIds
      summary: List opaque case IDs for remote ledger sync and duplicate-check connectivity.
      description: >
        Returns paginated case IDs for the token's firm. This endpoint is useful for
        remote ledger sync/connectivity checks. It is not a full-text matter reference
        search endpoint. If a duplicate check depends on this endpoint and it returns
        5xx or times out, remote state is unknown and live creation should stay blocked
        unless an operator explicitly accepts local-ledger-only dedupe.
      security:
        - bearerAuth: []
      x-required-scope: cases.ids.list
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 200
          description: Page size. Values are clamped to 1-1000.
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: nextCursor returned by the previous response.
      responses:
        "200":
          description: Page of opaque case IDs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CaseIdsResponse"
        "400":
          description: Invalid cursor or request query.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "500":
          $ref: "#/components/responses/ServerError"
  /cases:
    post:
      operationId: createCase
      summary: Create an onboarding case, optionally with initial parties.
      security:
        - bearerAuth: []
      x-required-scope: cases.create
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCaseRequest"
      responses:
        "200":
          description: Case created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateCaseResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /cases/{caseId}/parties:
    post:
      operationId: addCaseParty
      summary: Add a party to an existing case.
      security:
        - bearerAuth: []
      x-required-scope: parties.manage
      parameters:
        - $ref: "#/components/parameters/CaseId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PartyInput"
      responses:
        "200":
          description: Party created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Party"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /cases/{caseId}/invites:
    post:
      operationId: sendCaseInvite
      summary: Send an onboarding invite to a case party.
      security:
        - bearerAuth: []
      x-required-scope: invites.send
      parameters:
        - $ref: "#/components/parameters/CaseId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendInviteRequest"
      responses:
        "200":
          description: Invite sent. Response includes invite metadata as implemented by api/routes/v1/cases.ts.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  parameters:
    CaseId:
      name: caseId
      in: path
      required: true
      schema:
        type: string
      description: Opaque case identifier returned by POST /cases.
  responses:
    BadRequest:
      description: Missing or invalid payload.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Missing, expired, malformed, or invalid credentials. Refresh or rotate credentials; do not treat this as duplicate uncertainty.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Forbidden:
      description: Valid credentials but missing permission, scope, active status, or source IP allowance.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ServerError:
      description: Server error. If this happens during a required remote duplicate check, remote state is unknown.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    MicroAppScope:
      type: string
      enum:
        - users.list
        - cases.ids.list
        - cases.create
        - cases.read
        - cases.list
        - parties.manage
        - invites.send
    SystemTokenRequest:
      type: object
      required:
        - clientId
        - clientSecret
      properties:
        clientId:
          type: string
          description: Integration appId.
        clientSecret:
          type: string
          description: One-time secret issued at integration creation or rotation.
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/MicroAppScope"
          description: Optional requested scopes. Omitted means all enabled integration scopes.
        expiresInSeconds:
          type: integer
          minimum: 60
          maximum: 2592000
          default: 3600
    SystemTokenResponse:
      type: object
      required:
        - ok
        - accessToken
        - principal
        - expiresAt
      properties:
        ok:
          type: boolean
          const: true
        accessToken:
          type: string
        principal:
          type: object
          required:
            - principalType
            - tokenKind
            - appId
            - orgId
            - identifier
            - scopes
          properties:
            principalType:
              type: string
              const: microApp
            tokenKind:
              type: string
              const: system
            appId:
              type: string
            orgId:
              type: string
            identifier:
              type: string
            scopes:
              type: array
              items:
                $ref: "#/components/schemas/MicroAppScope"
        expiresAt:
          type: integer
          description: Unix timestamp in seconds.
    PropertyAddress:
      type: object
      additionalProperties: false
      properties:
        line1:
          type: string
        city:
          type: string
        postcode:
          type: string
    CompanyInput:
      type: object
      additionalProperties: false
      properties:
        companyName:
          type: string
        companyNumber:
          type: string
        registeredOffice:
          type: string
    PartyInput:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        email:
          type: string
        mobile:
          type: string
        role:
          type: string
        partyType:
          type: string
          enum:
            - individual
            - company
          default: individual
        company:
          $ref: "#/components/schemas/CompanyInput"
        relationshipToCompany:
          type: string
        isPrimaryContact:
          type: boolean
    Party:
      allOf:
        - $ref: "#/components/schemas/PartyInput"
        - type: object
          required:
            - partyId
            - clientAccessStatus
          properties:
            partyId:
              type: string
            clientAccessStatus:
              type: string
    CreateCaseRequest:
      type: object
      required:
        - matterRef
        - matterType
      properties:
        matterRef:
          type: string
        matterType:
          type: string
        propertyAddress:
          $ref: "#/components/schemas/PropertyAddress"
        requireSof:
          type: boolean
        demoMode:
          type: boolean
        expiresAt:
          type: string
          format: date-time
        fees:
          type: object
          additionalProperties: true
        parties:
          type: array
          items:
            $ref: "#/components/schemas/PartyInput"
    CreateCaseResponse:
      type: object
      required:
        - caseId
        - status
        - parties
      properties:
        caseId:
          type: string
        status:
          type: string
        parties:
          type: array
          items:
            $ref: "#/components/schemas/Party"
    CaseIdsResponse:
      type: object
      required:
        - ok
        - items
        - nextCursor
      properties:
        ok:
          type: boolean
          const: true
        items:
          type: array
          items:
            type: string
          description: Opaque case identifiers. Do not parse.
        nextCursor:
          type:
            - string
            - "null"
          description: Pass to cursor for the next page, or null when there are no more pages.
    SendInviteRequest:
      type: object
      required:
        - partyId
      properties:
        partyId:
          type: string
        sendSms:
          type: boolean
        emailSubject:
          type: string
        emailBody:
          type: string
        emailText:
          type: string
        emailHtml:
          type: string
        smsText:
          type: string
    ErrorResponse:
      type: object
      additionalProperties: true
      properties:
        ok:
          type: boolean
          const: false
        error:
          type: object
          additionalProperties: true
          properties:
            message:
              type: string
            code:
              type: string
