# CourseProfiler CourseProfiler provides MCP tools and a public API for trail running and ultramarathons: GPX/FIT activity analysis, CRSProf/USRProf artifacts, runner evidence insights, finish-time goal assessment, pacing strategy comparison, plan/execution review, 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/3/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. Choose the workflow from the user's intent: - Prefer `analyze_activity` for a supplied activity or route: no catalog search or runner profile is required. Accepts `course_file` with content/base64, or `source` with kind=url/artifact/raw_json/raw_file. For hosted clients without byte access, the user can upload at https://courseprofiler.com/agent-upload.html and copy the returned private artifact ID into chat. - `analyze_activity` returns `{job, summary}`. Inspect job.status/errors before using the summary; the complete CRSProf artifact is in job.result_artifacts. Analysis is currently synchronous. Existing CRSProf is preserved and summarized, not recomputed. - Activity options: `moving_threshold` (m/s, default 0.25), `auto_stop_threshold` (seconds, default 30; zero disables), `auto_stop_merge_distance` (metres, default 60), `algorithm` (GREEDY, SLOPE_BASED or PACE_BASED; omitted means PACE_BASED for timestamped input and SLOPE_BASED for untimed routes), and boolean `include_routes` (default true). Unknown options are rejected; options for existing CRSProf trigger warnings. - Summary distinguishes elapsed/owned/moving/nonmoving seconds and detected-stop elapsed/nonmoving seconds. Detected intervals can contain moving portions. Missing/partial timing never supplies a fabricated full-course pace. Known partial timestamps are preserved and flagged as incomplete, intentionally differing from the browser's profile-import fallback that may discard mixed timestamps. Vitals report their averaging/support method; cadence is both-leg spm. Stop details are limited to the first 20 with omitted count; settings/diagnostics expose bounded scalar fields and long names/text are abbreviated. Complete source data remains in the artifact. Use inspect_analysis for detailed climb/range queries. - `inspect_analysis` reads an existing v3 CRSProf `artifact_id` without reanalysis. Queries: segments, splits, climbs, stops, range, quality. Pages default 20/max100: follow `next_offset` and check `total_matches`. Sort by course_order/distance_desc/gain_desc/duration_desc. Climbs group existing UP movement rows. Missing/UNKNOWN terrain uses the browser's (gain-loss)/distance classification with saved flat_grade (default zero); threshold equality is FLAT. No smoothing or new segmentation. Read terrain_source (stored/derived/mixed/unavailable), stored_terrain on segments, terrain_classification_status and unavailable_terrain_segment_count; missing evidence is not proof of no climbs. Stop filters use `duration_metric` actual_elapsed/actual_nonmoving/planned_dwell and optional inclusive `min_duration_seconds`; unavailable durations are explicitly excluded. - Split inspection provides supported actual distance/gain/loss and execution-minus-reference/plan deltas, including `elapsed_delta_seconds`, from the shared browser table projection. Read `metric_coverage` and `limitations`: missing row contributions leave unsupported totals/deltas null. Complete metric contributions do not establish complete source timing/alignment; also consult quality. - If split `execution_timing_complete` is false, `actual_elapsed_basis=quantified_partial_intervals`: stored elapsed is not complete split time. Unsupported elapsed deltas and time-dependent review findings are suppressed with explicit limitations in MCP and the browser. Missing timestamps are never estimated. - Split inspection separates table subtotals, planned/detected stops (`separate_stop_*`), and structural separators (`separate_boundary_*`). Structural separators may be running intervals, not physical stops. Their reference distance/gain/loss and planned/actual/moving time remain separate; missing metrics are null, not invented zeros. - Range inspection requires `from_m` and `to_m` within source coverage, and explicit `axis=reference|execution` for comparisons. Distances are metres; times seconds; `moving_gap_seconds` is moving GAP only. Adjacent ranges own stationary boundary time once, including terminal dwell in the final range. Reference timestamps do not supply planned timing; report limitations rather than inventing missing metrics. - `compare_execution` accepts `plan_artifact_id`, `execution_artifact_id`, optional `stop_association_distance_m` (default60). It uses the complete browser pipeline and does not redetect stops; change detection through `analyze_activity` on original GPX/FIT. Returns `{job, summary}` synchronously: output artifact ID, units, first20 splits/stops, quality and server-owned review shared with the UI. Check job status/errors first; failure has null summary. Split findings exclude separately displayed dwell; matched planned stops are not unplanned. Review references are capped at20 per finding with omitted counts and focus retained; full geometry/chronology/references remain in the artifact. Review segment-N/stop-N maps to artifact:segment:N, split-N to artifact:split:N. Confirm deployed tool availability with `tools/list`. - `import_course` remains an artifact-only alternative: no catalog search, runner profile, or waypoint enrichment is required. Prefer `analyze_activity` when the user wants answers rather than just a converted file. - For a named race without a supplied source, search the catalog first. Runner evidence and waypoint-completeness guidance below concern personalized race planning, not basic activity analysis. - Normal imports and planning require CRSProf v3. Legacy profiles require explicit migration; do not silently reinterpret them. - `generate_runner_profile.profile_intent` is advisory context for the agent. The server does not filter or select evidence from it; submit only user-approved sources. Ask about intent only when it is not already clear from the user's request. - `import_course.options` and `course.import_options` accept only boolean `include_segments` and `include_routes`. Unknown fields (including `file_name`) and non-boolean values are rejected. These options affect GPX/FIT analysis, not an existing CRSProf: such imports preserve the file and return `analysis_options_not_applied` warnings. Omit options when reusing CRSProf; use `generate_course_segments` for segmentation changes. Input model: - Course GPX, FIT and CRSProf URLs are supported. FIT responses are identified by filename or FIT header and use the same converter as inline/uploaded FIT files. - 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. - For GPX/FIT/CRSProf/USRProf files when neither host attachment handoff nor file-byte/direct REST upload access is available, send the user to the fixed https://courseprofiler.com/agent-upload.html page. The page creates the private upload session internally; the user uploads the file and copies the returned artifact ID into chat. MCP does not create a session-specific browser-upload link. Do not fabricate one or require sandbox paths to be remotely accessible. - For attached USRProf/CRSProf, prefer `import_profile_attachment` with explicit user permission to upload: set `upload_authorized=true`, `profile_type=usrprof|crsprof`, and use the host-provided `file` object. Its `openai/fileParams` declaration enables required `download_url` and `file_id`, with optional `file_name` and `mime_type`. Never invent file values or use sandbox paths. The tool stores bytes unchanged (maximum 25 MiB), without validation, regeneration, migration, preparation or source replacement; returns `artifact_id`, not temporary URLs. Pass that ID directly to canonical inspection/planning without asking the user to copy it. File handles and signed URLs remain private. Upload permission does not authorize preparation or strategy application. Host handoff must be verified in the actual connector; if unavailable, immediately offer https://courseprofiler.com/agent-upload.html (now accepts .usrprof). Do not substitute raw-file commentary for canonical inspection. - Clients with file-byte access and network connectivity can upload GPX/FIT/CRSProf/USRProf files via REST: 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 or ZIP/TAR/TAR.GZ/TGZ/TAR.XZ/TXZ archives containing those files. Runner-evidence archives are expanded server-side; nested archives, duplicate contents, unsupported entries, and parse failures are reported by filename/path. - Use the returned source_file artifact ID in race-plan/import requests as `{ "kind": "artifact", "artifact_id": "art_..." }`. - For MCP race-plan creation: 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`. - In `create_race_plan`, `runner.usrprof_artifact_id` means an already-converted USRProf artifact of type `usrprof`; uploaded `.usrprof` sources go through `runner.sources`. In contrast, `inspect_runner_profile`, `assess_finish_time_goal` and `compare_pacing_strategies` accept uploaded `source_file` IDs directly as `usrprof_artifact_id`, without regeneration. - 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. - The following fallback applies only when host attachment handoff is unavailable. If the agent cannot provide inline file content/base64 and cannot upload local file bytes through the documented upload session flow, prefer the fixed browser handoff above for GPX/FIT/CRSProf. Otherwise ask for an existing CourseProfiler artifact ID or an already accessible, user-approved HTTPS URL. Do not require private activities to be made public. - 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. Prefer `build_runner_profile_from_strava`: open its private authorization link, resume the same job as instructed, and use the resulting USRProf artifact. Never request credentials or tokens through MCP. Ask for target and merge/replace choices only when not already supplied. Manual activity selection/export in the web app remains an alternative. - 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: - When resolving a named race without a supplied course, 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 https://courseprofiler.com/mcp. Its complete tool inventory is: - Activity analysis: `analyze_activity`. - Canonical detail queries: `inspect_analysis`. - Runner evidence: `inspect_runner_profile` requires `usrprof_artifact_id` and explicit query `summary|quality|recency|course_fit|coverage|insights`. Strict USRProf/source-file reads only; legacy .userprof needs explicit migration. Stored readiness is not current fitness or a new pacing gate. No reconstruction, model activation, Strava access, writes or profile replacement. - Runner course_artifact_id is required for course_fit, optional for coverage and rejected elsewhere. It uses current v3 reference geometry; evidence selection is a preview, never applied. Paged queries default offset 0/limit 20, maximum 100. insights rejects offset, limit and course context; the server owns inspection time, with no caller override. - insights returns six bounded families: durability, uphill, observed/stored GAP, run/hike, terrain mechanics and terrain-relative strength. Preserve distinct age scopes, fitted/default boundaries, extrapolation and support disclosures. More than 1,000,000 eligible RUN×HIKE pairs returns run/hike not_computed/resource_limited while the other families remain available. Recent trends, form drift, evidence range bars and technicity response are unavailable in MCP, not missing athlete evidence; experimental gates remain unchanged. Invalid numerical results fail explicitly, not as partial metrics. No raw source identities or full graph arrays. - Planning reports: `assess_finish_time_goal` requires `course_artifact_id`, `usrprof_artifact_id` and positive `target_elapsed_seconds` including saved dwell (5% shapes). `compare_pacing_strategies` requires both artifact IDs and accepts `split_percent`, default 5, inclusive range 0–15. Both evaluate all nine canonical alternatives with current v3 reference geometry, saved movement locks, dwell, checkpoint windows, cutoffs and GAP settings. - Planning `estimation_model` defaults to `load_response_evidence_v2`; experimental v3 requires explicit user approval and must match the stored model. `allow_profile_preparation` defaults to false: obtain explicit user approval before in-memory reconstruction, never silently retry with true. Preserve reuse/reconstruction disclosure. No source replacement, automatic evidence selection, Strava access or model migration. - Each planning call creates a private `planning_report` (non-read-only, non-idempotent, non-destructive); strategies are not applied. Preserve confidence, fallback and omission counts in bounded summaries; calculations are complete, not sampled. Retrieve complete diagnostics using `get_artifact` with `report_artifact_id`; reports are not exportable course plans. Supported goals are not guaranteed finishes. - This revision registers 24 tools, not deployment acceptance: confirm deployed availability with tools/list. - Planned-versus-actual comparison: `compare_execution`. - Course discovery/preparation: `search_race_catalog`, `import_course`, `enrich_course_waypoints`, `generate_course_segments`. - Profiles/planning/export: `generate_runner_profile`, `build_runner_profile_from_strava`, `create_race_plan`, `export_race_plan_pdf`. - Explicit catalog submission: `submit_course`. - Validation/status: `validate_course_profile`, `validate_runner_profile`, `get_job`, `get_artifact`. - Guidance: `get_course_submission_requirements`, `get_race_plan_requirements`, `get_runner_profile_requirements`, `get_artifact_upload_requirements`. - MCP action annotations distinguish read-only tools from job/artifact creation and external interactions. They are hints, not consent or authorization. Artifact-creating calls are not advertised as idempotent. - 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 } } ``` 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.