{
  "openapi": "3.1.0",
  "info": {
    "title": "CourseProfiler Assistant Actions",
    "version": "0.1.0",
    "summary": "Curated CourseProfiler action profile for AI assistants.",
    "description": "Minimal assistant-facing profile for creating race plans, polling jobs, and retrieving artifact metadata. Optional API keys may be sent with X-API-Key or Authorization: Bearer; absent keys use unauthenticated quotas. Use the canonical public API at /openapi.json for the full composable contract."
  },
  "servers": [
    {
      "url": "https://courseprofiler.com",
      "description": "Production origin"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "BearerAuth": []
    },
    {}
  ],
  "tags": [
    {
      "name": "Jobs"
    },
    {
      "name": "Artifacts"
    },
    {
      "name": "RacePlans"
    }
  ],
  "paths": {
    "/api/race-plans": {
      "post": {
        "tags": [
          "RacePlans"
        ],
        "summary": "Orchestrate course import, segmentation, runner import, estimation, and optional PDF export",
        "description": "Optional convenience workflow for AI agents and simpler API clients. Creates one top-level async job that composes the CRSProf import, segment generation, USRProf import, estimate, and race-plan PDF export capabilities. Atomic endpoints remain the canonical API surface.\n\nPersonalized race plans require runner input. CourseProfiler does not create meaningful pacing estimates from a course alone. Before calling this endpoint, make sure the user provides one runner input: a USRProf artifact ID, a public HTTPS URL to a USRProf file, local GPX/FIT/CRSProf evidence files uploaded through /api/artifact-uploads then imported with /api/usrprof/imports, public HTTPS URLs to runner evidence files, or a USRProf profile created through CourseProfiler's Strava import feature. Strava import requires browser-based user authentication and consent; direct the user to authenticate in the CourseProfiler web app, then use the resulting USRProf artifact/profile when available. If no runner profile or evidence is available, import/analyze the course only and ask the user for runner evidence before creating a race plan.\n\n\nIf official aid-station, water/food/crew, cutoff, or course-point data is available, include it as course.waypoints so CourseProfiler enriches the CRSProf before segment generation and estimation. Course-only plans are pacing-only and may miss critical race-plan information.",
        "operationId": "createRacePlan",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RacePlanCreateRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "$ref": "#/components/responses/AcceptedJob"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/jobs/{job_id}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get async job status",
        "operationId": "getJob",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/artifacts/{artifact_id}": {
      "get": {
        "tags": [
          "Artifacts"
        ],
        "summary": "Get artifact metadata and a private pre-signed download URL",
        "operationId": "getArtifact",
        "x-openai-isConsequential": false,
        "parameters": [
          {
            "$ref": "#/components/parameters/ArtifactId"
          }
        ],
        "responses": {
          "200": {
            "description": "Artifact metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artifact"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "410": {
            "description": "Artifact expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Optional API key. Invalid keys return 401; absent keys use unauthenticated public quotas."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Optional bearer API key. Invalid tokens return 401; absent tokens use unauthenticated public quotas."
      }
    },
    "parameters": {
      "JobId": {
        "name": "job_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^job_[A-Za-z0-9_-]+$"
        }
      },
      "ArtifactId": {
        "name": "artifact_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^art_[A-Za-z0-9_-]+$"
        }
      },
      "UploadId": {
        "name": "upload_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^upl_[A-Za-z0-9_-]+$"
        }
      }
    },
    "responses": {
      "AcceptedJob": {
        "description": "Async job accepted",
        "headers": {
          "Location": {
            "description": "URL of the job status resource",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Job"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Payload too large",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds before retry"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "Job": {
        "type": "object",
        "required": [
          "job_id",
          "status",
          "status_url",
          "created_at",
          "expires_at",
          "result_artifacts",
          "warnings",
          "errors"
        ],
        "properties": {
          "job_id": {
            "type": "string",
            "examples": [
              "job_01JABCDEF"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus"
          },
          "status_url": {
            "type": "string",
            "examples": [
              "/api/jobs/job_01JABCDEF"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "completed_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "result_artifacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Artifact"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          },
          "progress": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JobStatus": {
        "type": "string",
        "enum": [
          "queued",
          "running",
          "succeeded",
          "failed",
          "expired",
          "cancelled"
        ]
      },
      "Artifact": {
        "type": "object",
        "required": [
          "artifact_id",
          "type",
          "content_type",
          "created_at",
          "expires_at"
        ],
        "properties": {
          "artifact_id": {
            "type": "string",
            "examples": [
              "art_01JABCDEF"
            ]
          },
          "type": {
            "$ref": "#/components/schemas/ArtifactType"
          },
          "content_type": {
            "type": "string",
            "examples": [
              "application/vnd.courseprof+json",
              "application/vnd.usrprof+json",
              "application/pdf"
            ]
          },
          "file_name": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 0
          },
          "download_url": {
            "type": "string",
            "format": "uri",
            "description": "Private pre-signed URL. May be omitted for expired artifacts."
          },
          "download_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          }
        }
      },
      "ArtifactType": {
        "type": "string",
        "enum": [
          "source_file",
          "crsprof",
          "usrprof",
          "pdf",
          "json",
          "diagnostics"
        ]
      },
      "ArtifactUploadCreateRequest": {
        "type": "object",
        "required": [
          "file_name",
          "content_type",
          "size_bytes",
          "purpose"
        ],
        "additionalProperties": false,
        "properties": {
          "file_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "examples": [
              "course.gpx"
            ]
          },
          "content_type": {
            "type": "string",
            "examples": [
              "application/gpx+xml",
              "application/octet-stream",
              "application/vnd.courseprof+json"
            ]
          },
          "size_bytes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 26214400
          },
          "purpose": {
            "$ref": "#/components/schemas/ArtifactUploadPurpose",
            "description": "Use `course_source` for course files, `runner_evidence` for activity/evidence files, and `usrprof_source` for local `.usrprof` profile files."
          },
          "checksum": {
            "$ref": "#/components/schemas/ArtifactUploadChecksum"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ArtifactUploadCompleteRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "checksum": {
            "$ref": "#/components/schemas/ArtifactUploadChecksum"
          }
        }
      },
      "ArtifactUploadChecksum": {
        "type": "object",
        "required": [
          "algorithm",
          "value"
        ],
        "additionalProperties": false,
        "properties": {
          "algorithm": {
            "type": "string",
            "enum": [
              "sha256"
            ]
          },
          "value": {
            "type": "string",
            "pattern": "^[a-fA-F0-9]{64}$"
          }
        }
      },
      "ArtifactUploadPurpose": {
        "type": "string",
        "enum": [
          "course_source",
          "runner_evidence",
          "usrprof_source",
          "generic_source"
        ],
        "description": "Upload purpose. Use `usrprof_source` when uploading an existing local `.usrprof` runner profile; completed `source_file` artifacts can then be passed through `runner.sources` or `/api/usrprof/imports`."
      },
      "ArtifactUploadSession": {
        "type": "object",
        "required": [
          "upload_id",
          "status",
          "expires_at"
        ],
        "properties": {
          "upload_id": {
            "type": "string",
            "examples": [
              "upl_01JABCDEF"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ArtifactUploadStatus"
          },
          "upload": {
            "$ref": "#/components/schemas/ArtifactUploadTarget"
          },
          "complete_url": {
            "type": "string",
            "examples": [
              "/api/artifact-uploads/upl_01JABCDEF/complete"
            ]
          },
          "artifact_id": {
            "type": "string",
            "description": "Present after completion."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          }
        }
      },
      "ArtifactUploadStatus": {
        "type": "string",
        "enum": [
          "waiting_for_upload",
          "uploaded",
          "completed",
          "failed",
          "expired",
          "cancelled"
        ]
      },
      "ArtifactUploadTarget": {
        "type": "object",
        "required": [
          "method",
          "url",
          "expires_at",
          "max_size_bytes"
        ],
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "PUT"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Private pre-signed direct-upload URL."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Headers that must be replayed exactly on the direct PUT upload request. Missing or changed headers can invalidate the pre-signed URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "max_size_bytes": {
            "type": "integer",
            "minimum": 1
          }
        }
      },
      "ApiProblem": {
        "type": "object",
        "required": [
          "code",
          "message",
          "severity"
        ],
        "properties": {
          "code": {
            "type": "string",
            "examples": [
              "missing_elevation",
              "payload_too_large",
              "rate_limited"
            ]
          },
          "message": {
            "type": "string"
          },
          "severity": {
            "type": "string",
            "enum": [
              "info",
              "warning",
              "error"
            ]
          },
          "field": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "InputSource": {
        "type": "object",
        "required": [
          "kind"
        ],
        "discriminator": {
          "propertyName": "kind"
        },
        "oneOf": [
          {
            "$ref": "#/components/schemas/ArtifactInputSource"
          },
          {
            "$ref": "#/components/schemas/UrlInputSource"
          },
          {
            "$ref": "#/components/schemas/RawJsonInputSource"
          },
          {
            "$ref": "#/components/schemas/ImportIdInputSource"
          }
        ]
      },
      "ArtifactInputSource": {
        "type": "object",
        "required": [
          "kind",
          "artifact_id"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "artifact"
          },
          "artifact_id": {
            "type": "string"
          }
        }
      },
      "UrlInputSource": {
        "type": "object",
        "required": [
          "kind",
          "url"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "url"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "RawJsonInputSource": {
        "type": "object",
        "required": [
          "kind",
          "payload"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "raw_json"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ImportIdInputSource": {
        "type": "object",
        "required": [
          "kind",
          "import_id"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "import_id"
          },
          "import_id": {
            "type": "string"
          }
        }
      },
      "CrsprofImportOptions": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "profile",
              "execution",
              "preserve"
            ]
          },
          "include_segments": {
            "type": "boolean"
          },
          "analysis_options": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CrsprofSegmentOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "algorithm": {
            "type": "string",
            "enum": [
              "GREEDY",
              "SLOPE_BASED",
              "PACE_BASED"
            ]
          },
          "flat_grade": {
            "type": "number",
            "description": "Flat grade threshold in percent."
          },
          "slope_change_threshold": {
            "type": "number",
            "description": "Slope-change threshold in percent."
          },
          "flat_max_delta": {
            "type": "number",
            "description": "Optional maximum elevation delta for flat segments, in meters."
          },
          "minimum_distance": {
            "type": "number",
            "description": "Minimum segment distance in meters."
          },
          "flexible_minimum_distance": {
            "type": "boolean"
          },
          "flexible_flat_max_delta": {
            "type": "boolean"
          },
          "instant_mode": {
            "type": "boolean"
          },
          "include_routes": {
            "type": "boolean"
          }
        }
      },
      "UsrprofImportOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file_name": {
            "type": "string"
          }
        }
      },
      "EstimateOptions": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "strategy": {
            "type": "string",
            "enum": [
              "segment_evidence"
            ],
            "default": "segment_evidence"
          },
          "gap_algorithm": {
            "type": "string",
            "enum": [
              "POLYNOMIAL",
              "CONSTANT_POWER",
              "MINETTI"
            ]
          },
          "flat_grade": {
            "type": "number",
            "description": "Optional flat-grade threshold passed to segment-evidence locomotion planning."
          }
        }
      },
      "EstimateOutputOptions": {
        "type": "object",
        "properties": {
          "include_summary_artifact": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "RacePlanPdfOptions": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "subtitle": {
            "type": "string"
          },
          "unit_system": {
            "type": "string",
            "enum": [
              "metric",
              "imperial"
            ]
          },
          "file_name": {
            "type": "string"
          }
        }
      },
      "RacePlanCreateRequest": {
        "type": "object",
        "required": [
          "course",
          "runner"
        ],
        "properties": {
          "course": {
            "$ref": "#/components/schemas/RacePlanCourseInput"
          },
          "runner": {
            "$ref": "#/components/schemas/RacePlanRunnerInput"
          },
          "estimate": {
            "$ref": "#/components/schemas/RacePlanEstimateInput"
          },
          "pdf": {
            "$ref": "#/components/schemas/RacePlanPdfInput"
          }
        }
      },
      "RacePlanCourseInput": {
        "type": "object",
        "description": "Exactly one of source or crsprof_artifact_id should be supplied.",
        "oneOf": [
          {
            "required": [
              "source"
            ]
          },
          {
            "required": [
              "crsprof_artifact_id"
            ]
          }
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/InputSource"
          },
          "crsprof_artifact_id": {
            "type": "string"
          },
          "import_options": {
            "$ref": "#/components/schemas/CrsprofImportOptions"
          },
          "waypoints": {
            "description": "Optional waypoint/resource enrichment to apply before segment generation. Use this for official aid stations, water/food/crew resources, cutoffs, and course points.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/StructuredWaypointEnrichment"
              },
              {
                "$ref": "#/components/schemas/TableWaypointEnrichment"
              }
            ]
          },
          "segments": {
            "$ref": "#/components/schemas/RacePlanSegmentsInput"
          }
        }
      },
      "RacePlanSegmentsInput": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "generate",
              "skip"
            ],
            "default": "generate"
          },
          "options": {
            "$ref": "#/components/schemas/CrsprofSegmentOptions"
          }
        }
      },
      "RacePlanRunnerInput": {
        "type": "object",
        "description": "Exactly one of usrprof_artifact_id or sources should be supplied. Use usrprof_artifact_id for an existing USRProf artifact/profile. Use sources for public HTTPS runner evidence/profile URLs or source_file artifacts created via /api/artifact-uploads. Strava-based profiles must be created after browser-based user authentication in the CourseProfiler web app, then supplied as a USRProf artifact/profile.",
        "oneOf": [
          {
            "required": [
              "usrprof_artifact_id"
            ]
          },
          {
            "required": [
              "sources"
            ]
          }
        ],
        "properties": {
          "usrprof_artifact_id": {
            "type": "string"
          },
          "sources": {
            "type": "array",
            "minItems": 1,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ArtifactInputSource"
                },
                {
                  "$ref": "#/components/schemas/UrlInputSource"
                }
              ]
            }
          },
          "import_options": {
            "$ref": "#/components/schemas/UsrprofImportOptions"
          }
        }
      },
      "RacePlanEstimateInput": {
        "type": "object",
        "properties": {
          "options": {
            "$ref": "#/components/schemas/EstimateOptions"
          },
          "output": {
            "$ref": "#/components/schemas/EstimateOutputOptions"
          }
        }
      },
      "RacePlanPdfInput": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "options": {
            "$ref": "#/components/schemas/RacePlanPdfOptions"
          }
        }
      },
      "CrsprofImportRequest": {
        "type": "object",
        "required": [
          "source"
        ],
        "properties": {
          "source": {
            "$ref": "#/components/schemas/InputSource"
          },
          "options": {
            "type": "object",
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "profile",
                  "execution",
                  "preserve"
                ]
              },
              "include_segments": {
                "type": "boolean"
              },
              "analysis_options": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "CrsprofSegmentsRequest": {
        "type": "object",
        "required": [
          "crsprof_artifact_id"
        ],
        "properties": {
          "crsprof_artifact_id": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "generate"
            ],
            "default": "generate"
          },
          "options": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "algorithm": {
                "type": "string",
                "enum": [
                  "GREEDY",
                  "SLOPE_BASED",
                  "PACE_BASED"
                ]
              },
              "flat_grade": {
                "type": "number",
                "description": "Flat grade threshold in percent."
              },
              "slope_change_threshold": {
                "type": "number",
                "description": "Slope-change threshold in percent."
              },
              "flat_max_delta": {
                "type": "number",
                "description": "Optional maximum elevation delta for flat segments, in meters."
              },
              "minimum_distance": {
                "type": "number",
                "description": "Minimum segment distance in meters."
              },
              "flexible_minimum_distance": {
                "type": "boolean"
              },
              "flexible_flat_max_delta": {
                "type": "boolean"
              },
              "instant_mode": {
                "type": "boolean"
              },
              "include_routes": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "CrsprofValidateRequest": {
        "type": "object",
        "oneOf": [
          {
            "required": [
              "crsprof_artifact_id"
            ]
          },
          {
            "required": [
              "crsprof"
            ]
          }
        ],
        "properties": {
          "crsprof_artifact_id": {
            "type": "string"
          },
          "crsprof": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "UsrprofImportRequest": {
        "type": "object",
        "required": [
          "sources"
        ],
        "properties": {
          "sources": {
            "type": "array",
            "minItems": 1,
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ArtifactInputSource"
                },
                {
                  "$ref": "#/components/schemas/UrlInputSource"
                }
              ]
            },
            "description": "First implementation slice supports CRSProf/USRProf artifact IDs and public HTTPS URLs for supported evidence files."
          },
          "options": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "file_name": {
                "type": "string"
              }
            }
          }
        }
      },
      "UsrprofValidateRequest": {
        "type": "object",
        "oneOf": [
          {
            "required": [
              "usrprof_artifact_id"
            ]
          },
          {
            "required": [
              "usrprof"
            ]
          }
        ],
        "properties": {
          "usrprof_artifact_id": {
            "type": "string"
          },
          "usrprof": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "EstimateRequest": {
        "type": "object",
        "required": [
          "crsprof_artifact_id",
          "usrprof_artifact_id"
        ],
        "properties": {
          "crsprof_artifact_id": {
            "type": "string"
          },
          "usrprof_artifact_id": {
            "type": "string"
          },
          "options": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "strategy": {
                "type": "string",
                "enum": [
                  "segment_evidence"
                ],
                "default": "segment_evidence"
              },
              "gap_algorithm": {
                "type": "string",
                "enum": [
                  "POLYNOMIAL",
                  "CONSTANT_POWER",
                  "MINETTI"
                ]
              },
              "flat_grade": {
                "type": "number",
                "description": "Optional flat-grade threshold passed to segment-evidence locomotion planning."
              }
            }
          },
          "output": {
            "type": "object",
            "properties": {
              "include_summary_artifact": {
                "type": "boolean",
                "default": true
              }
            }
          }
        }
      },
      "RacePlanPdfExportRequest": {
        "type": "object",
        "required": [
          "crsprof_artifact_id"
        ],
        "properties": {
          "crsprof_artifact_id": {
            "type": "string"
          },
          "options": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "subtitle": {
                "type": "string"
              },
              "unit_system": {
                "type": "string",
                "enum": [
                  "metric",
                  "imperial"
                ]
              },
              "file_name": {
                "type": "string"
              }
            }
          }
        }
      },
      "ValidationResult": {
        "type": "object",
        "required": [
          "valid",
          "warnings",
          "errors"
        ],
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "artifact": {
            "$ref": "#/components/schemas/Artifact"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiProblem"
            }
          }
        }
      },
      "StructuredWaypointEnrichment": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "structured"
            ],
            "default": "structured"
          },
          "distance_unit": {
            "type": "string",
            "enum": [
              "m",
              "km",
              "mi"
            ],
            "default": "km"
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WaypointItem"
            }
          }
        }
      },
      "TableWaypointEnrichment": {
        "type": "object",
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "table"
            ]
          },
          "text": {
            "type": "string",
            "description": "Copied official aid-station/course-point table text. Provide either text or table_url."
          },
          "table_url": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTPS URL for an official aid-station/course-point table, HTML page, or PDF. Provide either text or table_url."
          },
          "source_ref": {
            "type": "string",
            "description": "Optional source URL/page/table reference."
          },
          "start_time": {
            "type": "string",
            "description": "Optional race start time used when parsing clock-based cutoffs."
          }
        }
      },
      "WaypointItem": {
        "type": "object",
        "required": [
          "name",
          "distance"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "distance": {
            "type": "number",
            "minimum": 0
          },
          "distance_unit": {
            "type": "string",
            "enum": [
              "m",
              "km",
              "mi"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "start",
              "finish",
              "aid",
              "checkpoint",
              "water",
              "crew",
              "other"
            ]
          },
          "resources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "water",
                "water_source",
                "drinks",
                "fruit",
                "food",
                "toilet",
                "medic",
                "chrono",
                "crew",
                "pacer",
                "car",
                "bus",
                "ambulance",
                "shuttle",
                "drop_bag",
                "withdrawal",
                "binoculars",
                "hot_meal",
                "tent",
                "showers",
                "changing_room",
                "physiotherapy",
                "rest_area",
                "start_flag",
                "finish_flag"
              ]
            },
            "uniqueItems": true
          },
          "cutoff": {
            "type": "string",
            "pattern": "^\\\\d{1,3}:\\\\d{2}$",
            "description": "Total elapsed cutoff in HH:MM. HH may be greater than 24."
          },
          "cutoff_kind": {
            "type": "string",
            "enum": [
              "enter",
              "leave"
            ]
          },
          "split_separator": {
            "type": "boolean",
            "description": "Reserved for explicit split separator behavior; current enrichment preserves existing CourseProfiler semantics."
          },
          "notes": {
            "type": "string"
          },
          "source_text": {
            "type": "string"
          },
          "source_ref": {
            "type": "string"
          }
        }
      }
    }
  }
}
