openapi: 3.1.0
info:
  title: Cycling Coach AI public API
  version: 2.0.0
  summary: Read-only endpoints behind the Cycling Coach AI website.
  description: |-
    Public, unauthenticated, read-only endpoints behind the Cycling Coach AI website.
    It exposes no user accounts, workouts or activity data.

    ## Where product information lives

    Pricing, features, supported integrations, training use cases, supported languages, company details and
    comparisons with competitors are published as text, not through this API:

    - `https://cyclingcoachai.com/llms.txt` and `https://cyclingcoachai.com/llms-full.txt`
    - every page of the site in markdown: send `Accept: text/markdown`

    ## Errors

    Every endpoint returns the same JSON envelope: `{ "error": { "code", "message", "hint", "docs_url" } }`.
    It never returns HTML.
  contact:
    name: Cycling Coach AI support
    email: support@cyclingcoachai.com
    url: https://cyclingcoachai.com/docs/
  license:
    name: Proprietary
    url: https://cyclingcoachai.com/tos/
  termsOfService: https://cyclingcoachai.com/tos/
servers:
  - url: https://cyclingcoachai.com
    description: Production
security: []
externalDocs:
  description: Agent integration guide
  url: https://cyclingcoachai.com/agents/
tags:
  - name: Discovery
    description: Entry points that describe the rest of the API.
  - name: Site
    description: Endpoints backing the public website.
paths:
  /api:
    get:
      operationId: getApiIndex
      summary: API index
      description: >-
        Machine-readable index of every public endpoint and the error format. Start here if you are discovering the API
        without reading this document.
      tags:
        - Discovery
      responses:
        '200':
          description: The API index.
          content:
            application/json:
              schema:
                type: object
                required:
                  - name
                  - documentation
                  - openapi
                  - endpoints
                properties:
                  name:
                    type: string
                    description: API name.
                  description:
                    type: string
                    description: What this API is for.
                  documentation:
                    type: string
                    format: uri
                    description: Human-readable docs.
                  openapi:
                    type: string
                    format: uri
                    description: This document.
                  llms_txt:
                    type: string
                    format: uri
                    description: llms.txt with product summary and when-to-use guidance.
                  endpoints:
                    type: array
                    description: Every public endpoint with its method.
                    items:
                      type: object
                      properties:
                        method:
                          type: string
                          description: HTTP method.
                        path:
                          type: string
                          description: Path.
                        description:
                          type: string
                          description: What it returns.
                  errors:
                    type: object
                    description: Error envelope format and the list of stable codes.
                  contact:
                    type: string
                    description: Support email.
        '406':
          description: The client does not accept application/json.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/ab/status:
    get:
      operationId: getAbTestStatus
      summary: Current A/B test status
      description: >-
        Reports whether a site-wide A/B test is running. Used by the edge middleware to decide whether to split traffic;
        public because it carries no user data.
      tags:
        - Site
      responses:
        '200':
          description: The current test status.
          content:
            application/json:
              schema:
                type: object
                required:
                  - active
                  - test
                properties:
                  active:
                    type: boolean
                    description: True when a test is running.
                  test:
                    description: The running test, or null when none is active.
                    oneOf:
                      - type: object
                        required:
                          - id
                          - name
                        properties:
                          id:
                            type: integer
                            description: Test id.
                          name:
                            type: string
                            description: Test name.
                      - type: 'null'
        '405':
          description: Only GET and HEAD are allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: The status could not be read.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/strava-live:
    get:
      operationId: getStravaLiveActivity
      summary: Latest public Strava activity
      description: >-
        Proxies the public activity feed shown by the homepage widget. Cached for 60 seconds. The response shape is
        owned by the upstream application API and is not part of this contract.
      tags:
        - Site
      responses:
        '200':
          description: The upstream activity payload, passed through unchanged.
          content:
            application/json:
              schema:
                type: object
                description: Opaque upstream payload.
                additionalProperties: true
        '405':
          description: Only GET is allowed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: The upstream service is unreachable or failing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Error:
      type: object
      description: >-
        Every error in this API uses this envelope. `code` is the stable contract; `message` and `hint` are human/agent
        readable and may be reworded.
      required:
        - error
      properties:
        error:
          type: object
          description: The error detail. Always present on a failed request.
          required:
            - code
            - message
            - hint
            - docs_url
          properties:
            code:
              type: string
              description: Stable machine-readable error code.
              enum:
                - bad_request
                - invalid_json
                - unauthorized
                - not_found
                - method_not_allowed
                - not_acceptable
                - unprocessable_entity
                - rate_limited
                - internal_error
            message:
              type: string
              description: What went wrong, in one sentence.
            hint:
              type: string
              description: How to fix the request and retry.
            docs_url:
              type: string
              format: uri
              description: Documentation covering this endpoint.
