openapi: 3.1.0
info:
  title: Caribou API
  version: 1.0.0-draft
  description: 'Read-only, metro-scoped configuration API for Caribou white-labelled
    iOS apps.

    Clients ship bundled defaults and use this API for updates. This is a design contract,

    not a running service. See the architecture guides for cache and fallback behaviour.

    '
servers:
- url: https://api.example.invalid/v1
  description: Placeholder; no API has been deployed
paths:
  /metros/{metroId}/bootstrap:
    get:
      operationId: getBootstrap
      tags:
      - Bootstrap
      summary: Fetch metro configuration and resource versions
      parameters:
      - $ref: '#/components/parameters/metroId'
      responses:
        '200':
          description: Metro bootstrap configuration
          headers:
            ETag:
              description: Optional HTTP entity tag for conditional requests
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Bootstrap'
              examples:
                mississauga:
                  value:
                    metro:
                      id: mississauga
                      name: Mississauga
                      countryCode: CA
                      subdivisionCode: CA-ON
                      timeZone: America/Toronto
                    features:
                      articles: true
                      events: false
                    resources:
                      areas:
                        version: sample-areas-1
                        url: /v1/metros/mississauga/areas
                      home:
                        version: sample-home-1
                        url: /v1/metros/mississauga/home
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/Unavailable'
  /metros/{metroId}/areas:
    get:
      operationId: getAreas
      tags:
      - Areas
      summary: Fetch current area types and mobile-optimized boundaries
      parameters:
      - $ref: '#/components/parameters/metroId'
      responses:
        '200':
          description: Complete current area dataset
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AreasResponse'
              examples:
                mississauga:
                  value:
                    areaTypes:
                    - id: neighbourhood
                      name: Neighbourhood
                      pluralName: Neighbourhoods
                    - id: ward
                      name: Ward
                      pluralName: Wards
                    areas:
                      type: FeatureCollection
                      features:
                      - type: Feature
                        id: sample-area-portcredit
                        properties:
                          metroId: mississauga
                          typeId: neighbourhood
                          name: Port Credit
                        geometry:
                          type: Polygon
                          coordinates:
                          - - - -79.61
                              - 43.59
                            - - -79.59
                              - 43.59
                            - - -79.59
                              - 43.61
                            - - -79.61
                              - 43.61
                            - - -79.61
                              - 43.59
                      - type: Feature
                        id: sample-area-ward-1
                        properties:
                          metroId: mississauga
                          typeId: ward
                          name: Ward 1
                        geometry:
                          type: Polygon
                          coordinates:
                          - - - -79.62
                              - 43.58
                            - - -79.58
                              - 43.58
                            - - -79.58
                              - 43.62
                            - - -79.62
                              - 43.62
                            - - -79.62
                              - 43.58
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/Unavailable'
  /metros/{metroId}/home:
    get:
      operationId: getHome
      tags:
      - Home
      summary: Fetch all configured scheduled background photographs
      parameters:
      - $ref: '#/components/parameters/metroId'
      responses:
        '200':
          description: Home screen background schedule; gaps and overlaps are permitted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HomeResponse'
              examples:
                mississauga:
                  value:
                    backgrounds:
                    - id: mississauga-sample-w45
                      imageUrl: https://images.example.com/mississauga/w45.jpg
                      caption: Autumn along the Credit River
                      photographer:
                        name: Example Photographer
                        url: null
                      source: null
                      startsAt: '2026-11-02T05:00:00Z'
                      endsAt: '2026-11-09T05:00:00Z'
                    - id: mississauga-sample-w46
                      imageUrl: https://images.example.com/mississauga/w46.jpg
                      caption: City Hall
                      photographer: null
                      source: null
                      startsAt: '2026-11-09T05:00:00Z'
                      endsAt: '2026-11-16T05:00:00Z'
                    - id: mississauga-sample-w47
                      imageUrl: https://images.example.com/mississauga/w47.jpg
                      caption: null
                      photographer: null
                      source:
                        name: Example Source
                        url: https://example.com
                      startsAt: '2026-11-16T05:00:00Z'
                      endsAt: '2026-11-23T05:00:00Z'
                    - id: mississauga-sample-w48
                      imageUrl: https://images.example.com/mississauga/w48.jpg
                      startsAt: '2026-11-23T05:00:00Z'
                      endsAt: '2026-11-30T05:00:00Z'
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          $ref: '#/components/responses/Unavailable'
components:
  parameters:
    metroId:
      name: metroId
      in: path
      required: true
      description: Stable, case-sensitive metro identifier embedded in the application
      schema:
        type: string
        pattern: ^[a-z0-9][a-z0-9-]*$
      example: mississauga
  responses:
    NotModified:
      description: Representation unchanged for a conditional HTTP request
    NotFound:
      description: Metro does not exist or is unavailable for public use
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unavailable:
      description: Temporary service outage
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  schemas:
    Bootstrap:
      type: object
      additionalProperties: false
      required:
      - metro
      - features
      - resources
      properties:
        metro:
          type: object
          additionalProperties: false
          required:
          - id
          - name
          - countryCode
          - subdivisionCode
          - timeZone
          properties:
            id:
              type: string
              description: Stable metro identifier
              example: mississauga
            name:
              type: string
              description: Unicode public display name
            countryCode:
              type: string
              description: ISO 3166-1 alpha-2 code
              pattern: ^[A-Z]{2}$
            subdivisionCode:
              type: string
              description: ISO 3166-2 subdivision code
            timeZone:
              type: string
              description: IANA time zone
              example: America/Toronto
        features:
          type: object
          description: Unknown keys are ignored; missing flags are disabled. Values
            are Boolean.
          additionalProperties:
            type: boolean
        resources:
          type: object
          additionalProperties: false
          required:
          - areas
          - home
          properties:
            areas:
              $ref: '#/components/schemas/Bootstrap/$defs/resource'
            home:
              $ref: '#/components/schemas/Bootstrap/$defs/resource'
      $defs:
        resource:
          type: object
          additionalProperties: false
          required:
          - version
          - url
          properties:
            version:
              type: string
              minLength: 1
              description: Opaque version; compare equality only
            url:
              type: string
              description: Same-origin API path relative to the host
              pattern: ^/v1/
    Metro:
      type: object
      additionalProperties: false
      required:
      - id
      - name
      - countryCode
      - subdivisionCode
      - timeZone
      properties:
        id:
          type: string
          description: Stable metro identifier
          example: mississauga
        name:
          type: string
          description: Unicode public display name
        countryCode:
          type: string
          description: ISO 3166-1 alpha-2 code
          pattern: ^[A-Z]{2}$
        subdivisionCode:
          type: string
          description: ISO 3166-2 subdivision code
        timeZone:
          type: string
          description: IANA time zone
          example: America/Toronto
    AreasResponse:
      type: object
      additionalProperties: false
      required:
      - areaTypes
      - areas
      properties:
        areaTypes:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
            - id
            - name
            - pluralName
            properties:
              id:
                type: string
                minLength: 1
              name:
                type: string
                description: Unicode singular display label
              pluralName:
                type: string
                description: Unicode plural display label
        areas:
          type: object
          description: GeoJSON FeatureCollection in WGS84 longitude-latitude order
          additionalProperties: false
          required:
          - type
          - features
          properties:
            type:
              const: FeatureCollection
            features:
              type: array
              items:
                type: object
                additionalProperties: false
                required:
                - type
                - id
                - properties
                - geometry
                properties:
                  type:
                    const: Feature
                  id:
                    type: string
                    minLength: 1
                    description: Globally unique permanent area ID
                  properties:
                    type: object
                    additionalProperties: false
                    required:
                    - metroId
                    - typeId
                    - name
                    properties:
                      metroId:
                        type: string
                      typeId:
                        type: string
                      name:
                        type: string
                        description: Unicode display name
                  geometry:
                    oneOf:
                    - $ref: '#/components/schemas/AreasResponse/$defs/polygon'
                    - $ref: '#/components/schemas/AreasResponse/$defs/multiPolygon'
      $defs:
        position:
          type: array
          prefixItems:
          - type: number
            minimum: -180
            maximum: 180
          - type: number
            minimum: -90
            maximum: 90
          minItems: 2
          maxItems: 2
        ring:
          type: array
          minItems: 4
          items:
            $ref: '#/components/schemas/AreasResponse/$defs/position'
          description: Closed linear ring; first and last positions must be identical
        polygon:
          type: object
          additionalProperties: false
          required:
          - type
          - coordinates
          properties:
            type:
              const: Polygon
            coordinates:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/AreasResponse/$defs/ring'
        multiPolygon:
          type: object
          additionalProperties: false
          required:
          - type
          - coordinates
          properties:
            type:
              const: MultiPolygon
            coordinates:
              type: array
              minItems: 1
              items:
                type: array
                minItems: 1
                items:
                  $ref: '#/components/schemas/AreasResponse/$defs/ring'
    HomeResponse:
      type: object
      additionalProperties: false
      required:
      - backgrounds
      properties:
        backgrounds:
          type: array
          description: All configured photographs; no maximum and no gap/overlap restrictions
          items:
            type: object
            additionalProperties: false
            required:
            - id
            - imageUrl
            - startsAt
            - endsAt
            properties:
              id:
                type: string
                minLength: 1
                description: Stable photo identity; use new ID if image bytes change
              imageUrl:
                type: string
                format: uri
                pattern: ^https://
              caption:
                type:
                - string
                - 'null'
                description: Optional Unicode caption; currently not displayed
              photographer:
                $ref: '#/components/schemas/HomeResponse/$defs/credit'
              source:
                $ref: '#/components/schemas/HomeResponse/$defs/credit'
              startsAt:
                type: string
                format: date-time
                description: Inclusive start
                UTC timestamp: null
              endsAt:
                type: string
                format: date-time
                description: Exclusive end
                UTC timestamp: null
      $defs:
        credit:
          type:
          - object
          - 'null'
          additionalProperties: false
          required:
          - name
          properties:
            name:
              type: string
            url:
              type:
              - string
              - 'null'
              format: uri
    Problem:
      type: object
      required:
      - type
      - title
      - status
      properties:
        type:
          type: string
          format: uri-reference
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
