openapi: 3.1.0
info:
  title: Patrol6 Public API
  version: 1.0.0
  description: 'Public integration surface for Patrol6 operational intelligence.


    Use this API to synchronize patrol operations, incident intelligence, and core
    security entities (guards, sites, clients) with strict tenant isolation.


    The MCP endpoint is AI-native and shares the same API key auth, scope checks,
    and rate-limiting controls.'
servers:
- url: https://prod.patrol6.app/api/public/v1
  description: Production
security:
- bearerApiKey: []
- xApiKey: []
tags:
- name: Patrols
  description: Patrol operations timeline and assignments (read-only in V1).
- name: Incidents
  description: Incident intelligence and AI-assisted incident data (read-only in V1).
- name: Guards
  description: Guard directory management with soft-delete semantics.
- name: Sites
  description: Site management APIs with tenant-scoped access.
- name: Clients
  description: Client management APIs with soft-delete semantics.
- name: AI / MCP
  description: Model Context Protocol endpoint for agentic integrations.
paths:
  /patrols:
    get:
      tags:
      - Patrols
      summary: List patrols
      operationId: listPatrols
      parameters:
      - $ref: '#/components/parameters/LimitParam'
      - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          $ref: '#/components/responses/PatrolListResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
  /patrols/{id}:
    get:
      tags:
      - Patrols
      summary: Get patrol
      operationId: getPatrol
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '200':
          $ref: '#/components/responses/PatrolSingleResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
  /incidents:
    get:
      tags:
      - Incidents
      summary: List incidents
      operationId: listIncidents
      parameters:
      - $ref: '#/components/parameters/LimitParam'
      - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          $ref: '#/components/responses/IncidentListResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
      x-codeSamples:
      - lang: curl
        label: Pull recent incidents
        source: "curl -sS \"https://prod.patrol6.app/api/public/v1/incidents?limit=50\"\
          \ \\\n  -H \"Authorization: Bearer <YOUR_API_KEY>\""
      - lang: JavaScript
        label: Nightly incident sync
        source: 'const res = await fetch("https://prod.patrol6.app/api/public/v1/incidents?limit=50",
          {\n  headers: { Authorization: `Bearer ${process.env.PATROL6_API_KEY}` }\n});\nconst
          payload = await res.json();'
  /incidents/{id}:
    get:
      tags:
      - Incidents
      summary: Get incident
      operationId: getIncident
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '200':
          $ref: '#/components/responses/IncidentSingleResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
  /guards:
    get:
      tags:
      - Guards
      summary: List guards
      operationId: listGuards
      parameters:
      - $ref: '#/components/parameters/LimitParam'
      - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          $ref: '#/components/responses/GuardListResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
    post:
      tags:
      - Guards
      summary: Create guard
      operationId: createGuard
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateGuardRequest'
      responses:
        '201':
          $ref: '#/components/responses/GuardSingleResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
  /guards/{id}:
    get:
      tags:
      - Guards
      summary: Get guard
      operationId: getGuard
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '200':
          $ref: '#/components/responses/GuardSingleResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
    patch:
      tags:
      - Guards
      summary: Update guard
      operationId: updateGuard
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateGuardRequest'
      responses:
        '200':
          $ref: '#/components/responses/GuardSingleResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
    delete:
      tags:
      - Guards
      summary: Soft-delete guard
      operationId: deleteGuard
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '204':
          description: Guard soft-deleted.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestIdHeader'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
  /sites:
    get:
      tags:
      - Sites
      summary: List sites
      operationId: listSites
      parameters:
      - $ref: '#/components/parameters/LimitParam'
      - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          $ref: '#/components/responses/SiteListResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
    post:
      tags:
      - Sites
      summary: Create site
      operationId: createSite
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSiteRequest'
      responses:
        '201':
          $ref: '#/components/responses/SiteSingleResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
  /sites/{id}:
    get:
      tags:
      - Sites
      summary: Get site
      operationId: getSite
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '200':
          $ref: '#/components/responses/SiteSingleResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
    patch:
      tags:
      - Sites
      summary: Update site
      operationId: updateSite
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSiteRequest'
      responses:
        '200':
          $ref: '#/components/responses/SiteSingleResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
    delete:
      tags:
      - Sites
      summary: Soft-delete site
      operationId: deleteSite
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '204':
          description: Site soft-deleted.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestIdHeader'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
  /clients:
    get:
      tags:
      - Clients
      summary: List clients
      operationId: listClients
      parameters:
      - $ref: '#/components/parameters/LimitParam'
      - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          $ref: '#/components/responses/ClientListResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
    post:
      tags:
      - Clients
      summary: Create client
      operationId: createClient
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClientRequest'
      responses:
        '201':
          $ref: '#/components/responses/ClientSingleResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '429':
          $ref: '#/components/responses/Error429'
  /clients/{id}:
    get:
      tags:
      - Clients
      summary: Get client
      operationId: getClient
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '200':
          $ref: '#/components/responses/ClientSingleResponse'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
    patch:
      tags:
      - Clients
      summary: Update client
      operationId: updateClient
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateClientRequest'
      responses:
        '200':
          $ref: '#/components/responses/ClientSingleResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
    delete:
      tags:
      - Clients
      summary: Soft-delete client
      operationId: deleteClient
      parameters:
      - $ref: '#/components/parameters/ResourceIdParam'
      responses:
        '204':
          description: Client soft-deleted.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestIdHeader'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
        '401':
          $ref: '#/components/responses/Error401'
        '403':
          $ref: '#/components/responses/Error403'
        '404':
          $ref: '#/components/responses/Error404'
        '429':
          $ref: '#/components/responses/Error429'
  /mcp:
    post:
      tags:
      - AI / MCP
      summary: MCP JSON-RPC endpoint
      description: 'JSON-RPC endpoint for MCP-compatible clients.

        Authenticate with the same API key used for REST requests.

        '
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/McpRequest'
      responses:
        '200':
          description: MCP response payload (success or protocol-level error).
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestIdHeader'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/McpResponse'
        '400':
          $ref: '#/components/responses/Error400'
        '401':
          $ref: '#/components/responses/Error401'
        '429':
          $ref: '#/components/responses/Error429'
      x-codeSamples:
      - lang: Python
        label: MCP initialize request
        source: 'import requests\n\nrequests.post("https://prod.patrol6.app/api/public/v1/mcp",\n  headers={"Authorization":
          "Bearer <YOUR_API_KEY>", "Content-Type": "application/json"},\n  json={"jsonrpc":
          "2.0", "id": "1", "method": "initialize", "params": {}}\n).json()'
components:
  securitySchemes:
    bearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: 'Send `Authorization: Bearer <api-key>`.'
    xApiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: 'Send `x-api-key: <api-key>`.'
  parameters:
    ResourceIdParam:
      name: id
      in: path
      required: true
      schema:
        type: string
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    CursorParam:
      name: cursor
      in: query
      schema:
        type: string
  headers:
    RequestIdHeader:
      schema:
        type: string
      description: Request identifier for support/audit.
    RateLimitLimit:
      schema:
        type: string
      description: Current per-minute request cap.
    RateLimitRemaining:
      schema:
        type: string
      description: Requests remaining in the current minute window.
    RateLimitReset:
      schema:
        type: string
      description: Seconds until the minute window resets.
    RetryAfter:
      schema:
        type: string
      description: Seconds to wait before retrying after a 429 response.
  schemas:
    Pagination:
      type: object
      properties:
        limit:
          type: integer
        nextCursor:
          type: string
          nullable: true
    PublicApiError:
      type: object
      required:
      - code
      - message
      properties:
        code:
          type: string
          example: AUTHENTICATION_FAILED
        message:
          type: string
          example: Missing or invalid API key.
        details:
          type: object
          additionalProperties: true
    ErrorResponse:
      type: object
      required:
      - error
      - requestId
      - timestamp
      properties:
        error:
          $ref: '#/components/schemas/PublicApiError'
        requestId:
          type: string
        timestamp:
          type: string
          format: date-time
    Patrol:
      type: object
      required:
      - id
      - siteId
      - status
      - startedAt
      properties:
        id:
          type: string
          example: pat_92j3f
        tenantId:
          type: string
          example: ten_4318f
        siteId:
          type: string
          example: site_433
        guardId:
          type: string
          example: grd_2391
        status:
          type: string
          example: in_progress
        startedAt:
          type: string
          format: date-time
          example: '2026-02-27T10:15:00Z'
        endedAt:
          type: string
          format: date-time
          nullable: true
          example: null
        checkpointCount:
          type: integer
          example: 9
        incidentCount:
          type: integer
          example: 2
      additionalProperties: true
    Incident:
      type: object
      required:
      - id
      - siteId
      - type
      - status
      - occurredAt
      properties:
        id:
          type: string
          example: inc_92j3f
        tenantId:
          type: string
          example: ten_4318f
        siteId:
          type: string
          example: site_433
        patrolId:
          type: string
          nullable: true
          example: pat_92j3f
        type:
          type: string
          example: Trespass
        severity:
          type: string
          example: high
        status:
          type: string
          example: open
        confidenceScore:
          type: number
          format: float
          example: 0.91
        aiSummary:
          type: string
          example: Subject crossed north fence line, remained on premises for approximately
            4 minutes, then exited east gate.
        occurredAt:
          type: string
          format: date-time
          example: '2026-02-27T09:54:31Z'
        createdAt:
          type: string
          format: date-time
          example: '2026-02-27T09:54:58Z'
      additionalProperties: true
    Guard:
      type: object
      required:
      - id
      - code
      - status
      - defaultSiteId
      properties:
        id:
          type: string
          example: grd_2391
        tenantId:
          type: string
          example: ten_4318f
        code:
          type: string
          example: G-1027
        firstName:
          type: string
          example: Alicia
        lastName:
          type: string
          example: Reyes
        email:
          type: string
          format: email
          example: alicia.reyes@example.com
        phone:
          type: string
          example: +1-206-555-0188
        status:
          type: string
          example: active
        defaultSiteId:
          type: string
          example: site_433
        siteIds:
          type: array
          items:
            type: string
          example:
          - site_433
          - site_551
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example: null
      additionalProperties: true
    Site:
      type: object
      required:
      - id
      - name
      - clientId
      - status
      properties:
        id:
          type: string
          example: site_433
        tenantId:
          type: string
          example: ten_4318f
        name:
          type: string
          example: North Medical Campus
        clientId:
          type: string
          example: cli_120
        address:
          type: string
          example: 1200 Northlake Ave, Seattle, WA
        timezone:
          type: string
          example: America/Los_Angeles
        status:
          type: string
          example: active
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example: null
      additionalProperties: true
    Client:
      type: object
      required:
      - id
      - name
      - status
      properties:
        id:
          type: string
          example: cli_120
        tenantId:
          type: string
          example: ten_4318f
        name:
          type: string
          example: Northlake Health System
        description:
          type: string
          example: Multi-facility healthcare security account
        status:
          type: string
          example: active
        deletedAt:
          type: string
          format: date-time
          nullable: true
          example: null
      additionalProperties: true
    CreateGuardRequest:
      type: object
      required:
      - code
      - defaultSiteId
      properties:
        code:
          type: string
        defaultSiteId:
          type: string
        siteIds:
          type: array
          items:
            type: string
        staffId:
          type: string
          nullable: true
        nfc:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          nullable: true
        status:
          type: string
        isDeviceAdmin:
          type: boolean
        allowLoginByCode:
          type: boolean
        allowLoginByNfc:
          type: boolean
      additionalProperties: false
    UpdateGuardRequest:
      allOf:
      - $ref: '#/components/schemas/CreateGuardRequest'
    CreateSiteRequest:
      type: object
      required:
      - name
      - clientId
      - address
      properties:
        name:
          type: string
        clientId:
          type: string
        address:
          type: string
        description:
          type: string
          nullable: true
        latitude:
          type: number
          nullable: true
        longitude:
          type: number
          nullable: true
        timezone:
          type: string
          nullable: true
        status:
          type: string
      additionalProperties: false
    UpdateSiteRequest:
      allOf:
      - $ref: '#/components/schemas/CreateSiteRequest'
    CreateClientRequest:
      type: object
      required:
      - name
      properties:
        name:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
      additionalProperties: false
    UpdateClientRequest:
      allOf:
      - $ref: '#/components/schemas/CreateClientRequest'
    McpRequest:
      type: object
      properties:
        jsonrpc:
          type: string
          example: '2.0'
        id:
          oneOf:
          - type: string
          - type: number
          - type: 'null'
        method:
          type: string
          example: tools/call
        params:
          type: object
          additionalProperties: true
      required:
      - method
    McpResponse:
      type: object
      properties:
        jsonrpc:
          type: string
        id:
          oneOf:
          - type: string
          - type: number
          - type: 'null'
        result:
          type: object
          additionalProperties: true
        error:
          type: object
          additionalProperties: true
  responses:
    PatrolListResponse:
      description: Paginated patrol list.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                type: array
                items:
                  $ref: '#/components/schemas/Patrol'
              pagination:
                $ref: '#/components/schemas/Pagination'
          example:
            data:
            - &id001
              id: pat_92j3f
              siteId: site_433
              guardId: grd_2391
              status: in_progress
              startedAt: '2026-02-27T10:15:00Z'
              checkpointCount: 9
              incidentCount: 2
            pagination:
              limit: 50
              nextCursor: pat_92j3f
    PatrolSingleResponse:
      description: Patrol response.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                $ref: '#/components/schemas/Patrol'
          example:
            data: *id001
    IncidentListResponse:
      description: Paginated incident list.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                type: array
                items:
                  $ref: '#/components/schemas/Incident'
              pagination:
                $ref: '#/components/schemas/Pagination'
          example:
            data:
            - &id002
              id: inc_92j3f
              siteId: site_433
              type: Trespass
              severity: high
              status: open
              confidenceScore: 0.91
              aiSummary: Subject crossed north fence line and remained on premises
                before exiting east gate.
              occurredAt: '2026-02-27T09:54:31Z'
              createdAt: '2026-02-27T09:54:58Z'
            pagination:
              limit: 50
              nextCursor: inc_92j3f
    IncidentSingleResponse:
      description: Incident response.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                $ref: '#/components/schemas/Incident'
          example:
            data: *id002
    GuardListResponse:
      description: Paginated guard list.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                type: array
                items:
                  $ref: '#/components/schemas/Guard'
              pagination:
                $ref: '#/components/schemas/Pagination'
          example:
            data:
            - &id003
              id: grd_2391
              code: G-1027
              firstName: Alicia
              lastName: Reyes
              email: alicia.reyes@example.com
              status: active
              defaultSiteId: site_433
              siteIds:
              - site_433
              - site_551
            pagination:
              limit: 50
              nextCursor: null
    GuardSingleResponse:
      description: Guard response.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                $ref: '#/components/schemas/Guard'
          example:
            data: *id003
    SiteListResponse:
      description: Paginated site list.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                type: array
                items:
                  $ref: '#/components/schemas/Site'
              pagination:
                $ref: '#/components/schemas/Pagination'
          example:
            data:
            - &id004
              id: site_433
              name: North Medical Campus
              clientId: cli_120
              address: 1200 Northlake Ave, Seattle, WA
              timezone: America/Los_Angeles
              status: active
            pagination:
              limit: 50
              nextCursor: null
    SiteSingleResponse:
      description: Site response.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                $ref: '#/components/schemas/Site'
          example:
            data: *id004
    ClientListResponse:
      description: Paginated client list.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                type: array
                items:
                  $ref: '#/components/schemas/Client'
              pagination:
                $ref: '#/components/schemas/Pagination'
          example:
            data:
            - &id005
              id: cli_120
              name: Northlake Health System
              description: Multi-facility healthcare security account
              status: active
            pagination:
              limit: 50
              nextCursor: null
    ClientSingleResponse:
      description: Client response.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestIdHeader'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            type: object
            properties:
              data:
                $ref: '#/components/schemas/Client'
          example:
            data: *id005
    Error400:
      description: Bad request. Validate payload schema, query params, and enum values.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: BAD_REQUEST
              message: Request validation failed.
              details:
                field: limit
                reason: must be <= 100
            requestId: req_abc123
            timestamp: '2026-02-27T12:00:00Z'
    Error401:
      description: Invalid or missing API key. Confirm Authorization header or x-api-key
        and key activation state.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: AUTHENTICATION_FAILED
              message: 'Invalid or missing API key. Provide Authorization: Bearer
                <key> or x-api-key.'
            requestId: req_abc123
            timestamp: '2026-02-27T12:00:00Z'
    Error403:
      description: Scope denied. Regenerate key with the required scope for this endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: SCOPE_DENIED
              message: Key does not include required scope for this endpoint.
            requestId: req_abc123
            timestamp: '2026-02-27T12:00:00Z'
    Error404:
      description: Resource not found in the current tenant context.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: NOT_FOUND
              message: Resource not found for this tenant.
            requestId: req_abc123
            timestamp: '2026-02-27T12:00:00Z'
    Error429:
      description: Rate limit exceeded. Respect Retry-After and RateLimit-Reset headers
        before retrying.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: RATE_LIMIT_EXCEEDED
              message: Rate limit exceeded. Retry after the interval in Retry-After.
            requestId: req_abc123
            timestamp: '2026-02-27T12:00:00Z'
x-tagGroups:
- name: Patrol Operations
  tags:
  - Patrols
  - Guards
  - Sites
  - Clients
- name: Incident Intelligence
  tags:
  - Incidents
- name: AI Integrations
  tags:
  - AI / MCP
