API reference
Errors
Every error has a stable code you can branch on, a message written for people, and a request ID for support. Messages can change; codes don't.
Error shape
{
"code": "rate_limit_exceeded",
"message": "This app has used its request limit; retry after 412 seconds.",
"request_id": "req_0123456789abcdef0123456789abcdef",
"details": {
"scope": "app",
"per_15_minutes": 100,
"per_day": 10000,
"per_athlete_per_15_minutes": 30,
"per_athlete_per_day": 2000
}
}Branch on code and the HTTP status. Show message to a developer, not to an athlete. details appears on a few codes: validation_ lists fields, rate_ gives the limits and scope, app_ gives max_apps, invalid_scope gives supported_scopes.
Bearer failures also send a WWW-Authenticate header: error="invalid_ for 401, and error="insufficient_ for 403.
Callback errors
If the athlete declines, your callback receives error=access_ and your state instead of a code. It isn't a failure of your app: show them what they'd get and a way to try again. Problems with the authorize link itself never reach your callback; CorrConnect shows the athlete the error instead, with one of the /oauth/authorize codes below.
API errors
Everything except the token endpoint answers with code, message, request_id and sometimes details. invalid_client appears in both shapes: as code on the authorize page and as error from the token endpoint.
| Code | Status | Where | What to do |
|---|---|---|---|
account_exists | 409 | POST / | Sign in instead, or reset the password from the portal. |
activity_not_found | 404 | GET / | Check the ID came from this athlete. Deleted accounts take their activities with them. |
app_limit_reached | 409 | POST / | Delete an app you no longer use, or email support for more. |
app_not_found | 404 | DELETE /Every webhook subscription endpoint | Check the app ID and that you are signed in to the account that owns it. |
app_registration_rate_limited | 429 | POST / | Wait for Retry-After seconds. |
authentication_required | 401 | GET /POST /Every app endpoint Every webhook subscription endpoint | Sign in with POST /v1/account/login and send the session cookie. |
csrf_rejected | 403 | POST /DELETE /POST /DELETE /POST /POST /POST / | Call GET /v1/account/me for a fresh csrf_token and send it as X-CSRF-Token. |
email_verification_required | 403 | GET /POST /POST /Every app endpoint Every webhook subscription endpoint | Open the verification link CorrConnect emailed to the account. |
http_error | 405 | Any endpoint | Another HTTP-level error, such as a method the route does not support; message has the detail. |
insufficient_scope | 403 | Every athlete data endpoint | Send the athlete through /oauth/authorize again asking for the scope you need. |
invalid_access_token | 401 | POST /POST /Every athlete data endpoint | Refresh the access token. If the refresh fails with invalid_grant, the athlete has to approve your app again. |
invalid_authorization_request | 400 | GET / | Send a state of 8 or more characters and a 43 to 128 character S256 challenge. |
invalid_callback_url | 400 | POST / | Use a public https:// URL on port 443. |
invalid_client | 400 | GET /POST / | Check client_id is the app_... value from registration and the app still exists. |
invalid_credentials | 401 | POST / | Check the email and password. |
invalid_cursor | 400 | GET / | Start the listing again without a cursor. |
invalid_event_type | 400 | POST / | Use activity.created and grant.revoked only. |
invalid_limit | 400 | GET / | Use a limit from 1 to 100. |
invalid_redirect_uri | 422 | POST / | Use exact https:// URLs with no fragment, user name, password or *. |
invalid_scope | 400 | GET / | Ask only for activities:read and profile:read, space-separated. |
invalid_session | 401 | GET /POST /Every app endpoint Every webhook subscription endpoint | Sign in again. |
invalid_source | 400 | GET / | Use one of the documented source values, or leave source out. |
not_found | 404 | Any endpoint | The path does not exist, or it is a portal route sent to the API host. |
origin_moved | 404 | Any endpoint | A non-GET request reached an old CorrConnect origin; send it to the current one. |
origin_rejected | 403 | POST /POST /POST /DELETE /POST /DELETE /POST /POST /POST / | Set Origin to exactly your CORRCONNECT_BASE address, with no path or trailing slash. |
rate_limit_exceeded | 429 | POST /POST /Every athlete data endpoint POST /POST /POST / | Wait for Retry-After seconds. details.scope says whether your app or one athlete hit the limit. |
redirect_uri_mismatch | 400 | GET / | Register the exact callback, then send it unchanged (same scheme, host, path and query). |
service_capacity_exceeded | 429 | Every athlete data endpoint | Wait for Retry-After seconds and retry. It does not count against your app. |
session_expired | 401 | GET /POST /Every app endpoint Every webhook subscription endpoint | Sign in again. |
unsupported_authorization_request | 400 | GET / | Use response_type=code and code_challenge_method=S256. |
validation_failed | 422 | POST /POST /POST /POST /GET /GET /POST / | Read details.fields; each entry names the field and the problem. |
webhook_disabled | 409 | POST / | Enable the subscription, then send the test again. |
webhook_limit_reached | 409 | POST / | Delete a subscription first; an app can have 3. |
webhook_not_found | 404 | DELETE /GET /POST /POST / | List the app's subscriptions to find the current ID. |
OAuth token errors
These come back from POST / in the OAuth shape: error, error_ and request_id.
| Error | Status | Where | What to do |
|---|---|---|---|
invalid_grant | 400 | POST / | Do not retry the same code or refresh token. Start a new authorization. |
invalid_request | 400 | POST / | Send every required form field as application/x-www-form-urlencoded. |
slow_down | 429 | POST / | Wait for Retry-After seconds. Fix the failing requests first; failures are what count. |
unsupported_grant_type | 400 | POST / | Use authorization_code or refresh_token. |
CorrConnect's own pages
The portal, sign-in recovery and device-connection screens use these codes. Your app does not call those routes, but an athlete may mention them in a support request.
| Code | Status | Meaning |
|---|---|---|
confirmation_required | 400 | A device disconnect was sent without its confirmation. |
invalid_provider_state | 400 | A device-connection callback did not match a sign-in CorrConnect started. |
invalid_action_token | 400 | An email verification or password-reset link is invalid or expired. |
invalid_workout_file | 400 | An uploaded GPX, TCX or FIT file could not be read; message says why. |
grant_not_found | 404 | The athlete tried to revoke a grant that is not theirs or no longer exists. |
provider_request_not_found | 404 | A device request to withdraw was not found. |
provider_account_in_use | 409 | That device account is already linked to another CorrConnect account. |
provider_already_connected | 409 | The device account is already connected. |
provider_not_connected | 409 | There is no device connection to sync or remove. |
provider_operation_active | 409 | Another connect, sync or disconnect for this device is running. |
provider_revocation_pending | 409 | A previous disconnect is still being completed with the device maker. |
stale_provider_operation | 409 | The connection changed while this request ran; try again. |
file_too_large | 413 | An uploaded workout file is over 2 MB. |
unsupported_media_type | 415 | An upload was not GPX, TCX or FIT. |
password_reused | 422 | A password reset chose the current password. |
provider_sync_failed | 502 | The device maker's API failed during a sync. |
provider_persistence_failed | 502 | Records from a device could not be saved; nothing was half-written. |
provider_unavailable | 503 | That device connection is not available yet. |
mail_unavailable | 503 | CorrConnect could not send email right now. |
launch_gates_incomplete | 503 | Operator readiness check: the service is not qualified for production. |