# CourseProfiler CourseProfiler provides a public API for trail/race course profiling, CRSProf/USRProf artifacts, personalized race-plan estimation, and race-plan PDF export. Use these resources to discover the API and workflow: - API guide: https://courseprofiler.com/public-api.html - MCP / ChatGPT App guide: https://courseprofiler.com/mcp.html - MCP endpoint: https://courseprofiler.com/mcp - Race catalog: https://courseprofiler.com/race-catalog.html - Race catalog manifest: https://courseprofiler.com/assets/catalog/manifest.json - Assistant OpenAPI JSON: https://courseprofiler.com/assistant/openapi.json - Canonical OpenAPI JSON: https://courseprofiler.com/openapi.json - Canonical OpenAPI YAML: https://courseprofiler.com/openapi.yaml - Privacy policy: https://courseprofiler.com/privacy.html - Terms of use: https://courseprofiler.com/terms.html - CRSProf JSON schema: https://courseprofiler.com/api/crsprof/schemas/1/json-schema - CRSProf semantics: https://courseprofiler.com/api/crsprof/interpretations/course_profiler_v1 Consent and safety guidance for AI agents: - If the user explicitly asks you to create a race plan or export a PDF with CourseProfiler, treat that request as consent to perform the required POST workflow. - Do not add extra confirmation loops solely because the required workflow uses POST. - Ask again only before using unexpected source URLs/artifact IDs, deleting artifacts, or sharing private artifact download URLs outside the conversation. - Treat artifact download URLs as private and short-lived. - GET requests for public documentation, job status, and artifact metadata may be used for discovery/status checks. Input model: - The public API is JSON URL/artifact based for analysis workflows; direct multipart upload is not used. - MCP clients that can read local files may pass local file bytes inline inside a file object as `course_file.content` / `runner_profile_file.content` for text files such as GPX/XML/JSON/USRProf, or `course_file.base64` / `runner_profile_file.base64` for binary files such as FIT. Include `name` or `file_name` so CourseProfiler can infer the format. Bare local filesystem paths never work on the hosted MCP server; do not pass a bare local filesystem path or `file://` URL. - To use a local GPX/FIT/CRSProf/USRProf file when inline MCP content/base64 is unavailable, create an upload session with POST /api/artifact-uploads, PUT the bytes to the returned private upload URL while replaying every returned `upload.headers` value exactly, then complete it with POST /api/artifact-uploads/{upload_id}/complete to receive a `source_file` artifact ID. This is a REST upload flow, not a raw-byte MCP upload tool. Use `purpose: "course_source"` for local course files, `purpose: "usrprof_source"` for local `.usrprof` profile files, and `purpose: "runner_evidence"` for local GPX/FIT/CRSProf activity evidence files. - Use the returned source_file artifact ID in race-plan/import requests as `{ "kind": "artifact", "artifact_id": "art_..." }`. - For MCP: if using artifact upload, pass uploaded local GPX/FIT/CRSProf course source artifacts as `course.source: { "kind": "artifact", "artifact_id": "art_..." }` or `import_course.source`; pass uploaded local `.usrprof` source_file artifacts as `runner.sources: [{ "kind": "artifact", "artifact_id": "art_..." }]`, not as `usrprof_artifact_id`. - `usrprof_artifact_id` means an already-converted USRProf artifact of type `usrprof`; uploaded `.usrprof` files first complete as `source_file` artifacts and must be imported through runner sources. - If an official or third-party GPX/FIT/CRSProf/ZIP URL cannot be fetched, is blocked, or returns 403, stop and ask the user to download the official route file and upload it through POST /api/artifact-uploads, then continue with the returned source_file artifact ID. Route hosts such as Wikiloc may block server-side fetches. Official ZIP files are accepted only when they contain exactly one GPX; ZIPs with multiple GPX files must be extracted and the intended GPX selected. Do not reconstruct or synthesize a course from roadbooks, checkpoint tables, elevation profiles, aid-station lists, screenshots, or other non-GPS sources. - If the agent cannot provide inline file content/base64 and cannot upload local file bytes through the documented upload session flow, ask the user for public HTTPS URLs or existing CourseProfiler artifact IDs. - For MCP, call `get_artifact_upload_requirements` when upload instructions are needed. It explains accepted inline file forms, the REST artifact upload flow, purposes, and how to use returned `source_file` artifacts. It is not a raw-byte MCP upload tool; MCP exposes instructions, not local file upload bytes. - Do not pass local filesystem paths or `file://` URLs to hosted MCP clients. - Do not invent multipart field names such as course_file or usrprof_file unless the OpenAPI contract later documents them. - Copy-paste curl, JavaScript, and Python upload helper snippets are published in the API guide: https://courseprofiler.com/public-api.html#upload-helper-snippets Runner profile requirement for race plans: - Personalized race plans require runner input. CourseProfiler does not create meaningful pacing estimates from a course alone. - Before calling POST /api/race-plans or POST /api/estimates/segment-evidence, make sure the user provides one of these runner inputs: 1. A USRProf artifact ID, or a public HTTPS URL to a USRProf file. 2. Local GPX/FIT/CRSProf evidence files that your agent can upload via POST /api/artifact-uploads, then pass to POST /api/usrprof/imports. 3. Public HTTPS URLs to runner evidence files that can be passed to POST /api/usrprof/imports. 4. A USRProf profile created through CourseProfiler's Strava import feature. - Strava import requires browser-based user authentication and consent. If the user wants Strava-based profiling, direct them 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. - For MCP, call `get_runner_profile_requirements` when the user needs help obtaining runner input. It explains how to export a `.usrprof` from CourseProfiler, how GPX/FIT activities can be used as evidence, how many activities are useful, what fields improve quality, and how hosted clients upload artifacts. Preferred AI race-plan workflow: - Before importing a GPX or creating a race plan, first check the race catalog manifest at https://courseprofiler.com/assets/catalog/manifest.json (or the crawlable catalog page at https://courseprofiler.com/race-catalog.html) for the requested race/course. - If a matching catalog race exists, use its `assetPath` CRSProf directly instead of re-importing GPX. For MCP `create_race_plan`, pass it as `course.source: { "kind": "url", "url": "https://courseprofiler.com/assets/catalog/routes/...crsprof" }`; for public API workflows, use the same catalog CRSProf URL as the course source or import it once as CRSProf. - If no catalog match exists, then try to resolve a fetchable official direct GPX/FIT/CRSProf URL or ask the user to upload/provide the course file or URL. If the official/third-party URL is inaccessible, blocked, returns 403, or only a webpage is available, stop and ask the user to download the official GPX/FIT/CRSProf and upload it through the artifact upload flow. Do not fail just because a Wikiloc or similar route URL returns 403 to CourseProfiler, but also do not create a race plan until a real route file or trusted CRSProf is available. - To discover public catalog courses without executing browser JavaScript, fetch the crawlable Race catalog page or the race catalog manifest. Use a catalog entry's `assetPath` as a CRSProf course source when the user chooses that race. - For ChatGPT Apps and MCP-capable clients, prefer the MCP endpoint at https://courseprofiler.com/mcp with tools `create_race_plan`, `search_race_catalog`, `import_course`, `enrich_course_waypoints`, `generate_course_segments`, `get_race_plan_requirements`, `get_runner_profile_requirements`, `get_artifact_upload_requirements`, `get_job`, and `get_artifact`. - For OpenAPI actions/tools, use the curated assistant profile at https://courseprofiler.com/assistant/openapi.json. - Use POST /api/race-plans when available. It orchestrates course import/load, segment generation, runner profile import/load, race estimation, and optional PDF export in one top-level job. Race-plan estimation strips imported course execution/timing data from the planned CRSProf so source GPX timestamps do not appear as comparison deltas in race-plan PDFs. - Race plans are most useful when the CRSProf includes official waypoints/resources. Catalog CRSProf files may already include official waypoints/resources/cutoffs; if they do, use the catalog CRSProf directly. If official aid-station, water/food/crew, cutoff, or course-point data is missing, enrich the CRSProf with POST /api/crsprof/waypoints before segment generation and estimation, or ask the user for structured aid/resource data. Prefer `mode: "structured"` for reliable AI/client workflows; `mode: "table"` with `text` or `table_url` is best-effort convenience parsing. If official pages and regulation PDFs disagree, or exact aid locations are not fully listed in machine-readable form, ask the user to confirm and include only confirmed aid stations; do not invent missing aid stations. Waypoint/checkpoint/roadbook/elevation-profile data is enrichment only, never a substitute for real GPX/FIT/CRSProf route geometry. Waypoint `resources` are canonical values such as `water`, `water_source`, `drinks`, `fruit`, `food`, `hot_meal`, `crew`, `drop_bag`, `medic`, `toilet`, and `rest_area`; unsupported/free-text resources are ignored with warnings, so put non-canonical details in notes/source text. - Route-only plans are incomplete unless the user explicitly accepts missing aid/resource/cutoff details. If no waypoint data is available, ask the user whether a route-only plan is acceptable and label outputs as missing aid/resource details. - `generate_course_segments.options` accepts only segmentation options: `algorithm`, `flat_grade`, `slope_change_threshold`, `flat_max_delta`, `minimum_distance`, `flexible_minimum_distance`, `flexible_flat_max_delta`, `instant_mode`, and `include_routes`. `estimate.options` accepts only `strategy`, `gap_algorithm`, and `flat_grade`; put race metadata/start times/waypoints in course or waypoint fields, not generic options. - Use the composable endpoints below when you need advanced control or when the orchestrator is unavailable. Composable race-plan workflow: 1. Import a course with POST /api/crsprof/imports. 2. Enrich the CRSProf with official aid/resource/cutoff waypoints using POST /api/crsprof/waypoints when waypoint data is available and not already present in a catalog CRSProf. 3. Generate analytical segments with POST /api/crsprof/segments. 4. Import runner evidence/profile with POST /api/usrprof/imports. 5. Estimate the race plan with POST /api/estimates/segment-evidence for whole-course USRProf/evidence-based pacing, or POST /api/estimates/fatigue-form for whole-course CRSProf embedded fatigue/form settings. For interactive estimates of only selected targets, use POST /api/estimates/segment-evidence/selected-segments or POST /api/estimates/fatigue-form/selected-segments. POST /api/estimates remains a compatibility alias for segment-evidence estimation. 6. Export the planned race PDF with POST /api/exports/race-plan-pdf. 7. Poll jobs with GET /api/jobs/{job_id} and download artifacts with GET /api/artifacts/{artifact_id}. Concrete JSON example: 1. Import a GPX course from a public HTTPS URL: ```json { "source": { "kind": "url", "url": "https://example.com/course.gpx" }, "options": { "include_segments": true, "file_name": "course.crsprof" } } ``` POST that JSON to https://courseprofiler.com/api/crsprof/imports. The response is job-shaped, for example: ```json { "job_id": "job_abc", "status": "succeeded", "status_url": "/api/jobs/job_abc", "artifacts": [ { "artifact_id": "art_course", "type": "crsprof" } ] } ``` If official aid/resource/cutoff waypoint data is available and not already present in the CRSProf, enrich the CRSProf first: ```json { "course": { "crsprof_artifact_id": "art_course" }, "waypoints": { "mode": "structured", "distance_unit": "km", "items": [ { "name": "Aid Station 1", "distance": 8.2, "kind": "aid", "resources": ["water", "food"], "cutoff": "02:30" } ] } } ``` POST to /api/crsprof/waypoints and use the enriched CRSProf artifact ID for segmentation. Prefer structured rows extracted from official sources. If only copied table text is available, use `{ "mode": "table", "text": "..." }` under `waypoints`; if an official table/page/PDF URL is available, use `{ "mode": "table", "table_url": "https://example.com/aid-stations" }`, but treat URL parsing as best effort and verify warnings/output. Use the enriched CRSProf artifact ID in the next step: ```json { "crsprof_artifact_id": "art_course_enriched", "mode": "generate", "options": { "algorithm": "SLOPE_BASED", "flat_grade": 2.0, "minimum_distance": 200 } } ``` POST to /api/crsprof/segments and use the returned CRSProf artifact ID, for example art_course_segmented. 2. Import runner evidence/profile from artifacts or public HTTPS URLs: ```json { "sources": [{ "kind": "artifact", "artifact_id": "art_execution_crsprof" }], "options": { "file_name": "runner.usrprof" } } ``` POST to /api/usrprof/imports and use the returned USRProf artifact ID, for example art_runner_profile. 3. Estimate and export: ```json { "crsprof_artifact_id": "art_course_segmented", "usrprof_artifact_id": "art_runner_profile", "options": { "strategy": "segment_evidence" }, "output": { "include_summary_artifact": true } } ``` POST to /api/estimates/segment-evidence and use the returned planned CRSProf artifact ID. To estimate with CRSProf embedded fatigue/form settings instead, POST `{ "crsprof_artifact_id": "art_course_with_options" }` to /api/estimates/fatigue-form. To estimate only selected targets with segment evidence, POST `usrprof` or `usrprof_artifact_id` plus ordered `segments` to /api/estimates/segment-evidence/selected-segments; the response includes `estimates` and `rejected` directly with diagnostics. To estimate only selected targets with fatigue/form settings, POST `crsprof` or `crsprof_artifact_id` plus `segments: [{ "segment_index": 12 }]` to /api/estimates/fatigue-form/selected-segments; the response is CRSProf-shaped with selected segment plan overlays updated. Then export PDF: ```json { "crsprof_artifact_id": "art_course_planned", "options": { "title": "Race Plan", "unit_system": "metric", "file_name": "race-plan.pdf" } } ``` POST to /api/exports/race-plan-pdf. Poll any non-terminal job using status_url. Fetch artifact metadata with GET /api/artifacts/{artifact_id}; the download_url is the final private artifact URL. Artifacts and jobs currently expire after 30 days. Download URLs are pre-signed and short-lived. Authentication and quotas: - Public endpoints remain usable without authentication under conservative unauthenticated quotas. - Optional API keys may be sent as `X-API-Key` or `Authorization: Bearer ...` for higher quotas. - Invalid API keys return HTTP 401 with `invalid_api_key`. - Rate limits return HTTP 429 with `rate_limited`, `Retry-After`, and `X-RateLimit-*` headers.