Guides
Errors and retries
Every refusal has the same shape and a stable code. Match on the code, never on the message.
The error body#
400 Bad Request
{
"error": {
"code": "invalid_request",
"message": "Some fields are missing or not valid.",
"fields": [
{
"field": "durationSeconds",
"code": "out_of_range",
"message": "durationSeconds must be at least 60."
},
{
"field": "type",
"code": "required",
"message": "type is required."
}
]
},
"requestId": "ec65f508-d907-4d78-a263-3dfd95794fa3"
}codeis stable. Build your handling on it.messageis for your logs. It can change at any time.fieldslists each field that failed, with its own code and message. It is empty when no single field is at fault.requestIdis the same as theX-Request-Idheader. Send it to us with any question.
What to retry#
| Status | Retry? |
|---|---|
429 | Yes, after the seconds in Retry-After. |
503 | Yes, after a few seconds (or Retry-After when sent), with the same externalId. |
5xx, timeouts | Yes, with the same externalId. Wait 1, 2, then 4 seconds and so on, up to a few minutes. |
409 daily_limit_reached | Not before error.resetsAt. The workout was not stored. |
Other 4xx | No. The same request will fail the same way. Fix it first. |
Codes any call can get#
| Code | Meaning | What to do |
|---|---|---|
400invalid_request | The body is not valid JSON, or fields are missing or not valid. fields lists each one. | Fix the fields listed. Retrying unchanged will not help. |
400unknown_field | The body has a field we do not know, often a typo. | Remove or rename the field named in fields. |
401missing_api_key | There is no Authorization: Bearer header. | Send the key as Authorization: Bearer <key>. |
401invalid_api_key | The key is wrong, revoked, expired, or meant for the other environment. | Check that you use the right key for the base URL. |
403partner_api_disabled | The partner API is switched off on our side. | Contact us. Nothing you send is processed until it is back on. |
403https_required | The request was sent over plain HTTP. | Call the base URL with https://. |
403partner_paused | Your access is paused on our side. | Contact us. Your keys keep working once access is resumed. |
403team_not_started | Your team has not started yet. | Wait for the start date, then send again. |
403team_ended | Your team has ended. | Stop sending and contact us about a new campaign. |
404not_found | There is no such path in the partner API. | Check the path against this reference. |
405method_not_allowed | The path exists but not with this method, such as GET /activities. | Use the method shown in this reference. |
429rate_limited | More than 60 requests in 60 seconds, or more than 2 at the same time. | Wait for the seconds in Retry-After, then send again. |
503service_unavailable | We are unavailable for a moment. | Wait a few seconds, or for Retry-After when it is sent, and send the same request again. For a workout, keep the same externalId. |
Codes from one call#
These come from one endpoint each. The reference lists them with the call.
| Code | Meaning |
|---|---|
409already_in_team | The email belongs to someone in another team. |
409team_full | Your team has reached its maximum number of members. |
409person_unlinked | The person's link to your team ended. It is final: they cannot be added again. |
404user_not_found | No person in your team matches the one you named. |
409duplicate_external_id | A workout with this externalId and different values was sent before. |
409daily_limit_reached | The person reached today's limit. error.resetsAt says when it resets. |
409overlapping_activity | The person already has a workout at the same time. |
422activity_not_available | Your team does not accept this type. |
422started_at_out_of_range | startedAt is before yesterday, or the workout ends more than 5 minutes from now. |
404activity_not_found | You sent no counted workout with this externalId. |