Concepts
Errors
How the HubFit API reports errors: RFC 9457 problem details, HTTP status codes, every error code, and which requests are safe to retry.
Errors are RFC 9457 problem details, sent as application/problem+json. Branch on code, which is stable; detail is for people and can change.
title: the HTTP status phrase.status: the HTTP status code.code: what went wrong, from the table below.detail: what went wrong, in words.requestId: this request's id, also in theX-Request-Idheader of every response. Quote it when you contact support.errors: oninvalid_requestwhen the request doesn't match the spec, one entry per problem, with itslocation(query,path,bodyorheader),nameanddetail.requiredScopeoninsufficient_scope, andclientIdonclient_exists.
400 Bad Request400 Bad Request
{
"title": "Bad Request",
"status": 400,
"code": "invalid_request",
"detail": "The request didn't match the API specification. See errors.",
"requestId": "2f8e6c1a-4b7d-4e2a-9c3f-5d1b8a7e6f40",
"errors": [
{
"location": "query",
"name": "limit",
"detail": "must be <= 100"
}
]
}New codes can appear. Handle one you don't recognise by its HTTP status.
| Code | Status | Meaning |
|---|---|---|
invalid_request | 400 | A parameter or the body is invalid; detail says why, and errors lists each problem when the request doesn't match the spec. |
url_not_allowed | 400 | A webhook endpoint's url can't receive webhooks; detail says why. |
unauthenticated | 401 | The API key is missing, invalid, expired or revoked. |
insufficient_scope | 403 | The key lacks a scope; requiredScope names it. |
plan_required | 403 | The workspace isn't on a plan with the API. |
not_available | 403 | The API isn't open to your workspace yet. |
account_suspended | 403 | The workspace or the key's coach is suspended. |
account_restricted | 403 | The workspace has unpaid invoices. |
client_limit_reached | 403 | Your plan has no room for another client. |
role_required | 403 | Only a key belonging to the workspace's owner or an admin can manage webhook endpoints. |
webhook_endpoint_read_only | 403 | Another key or Settings added this webhook endpoint, so this key can't change or delete it. |
not_found | 404 | It doesn't exist, or your key can't reach it. |
method_not_allowed | 405 | The path exists, but not with this method. |
client_archived | 409 | The client is archived. Unarchive them in HubFit to read or change anything of theirs. |
client_exists | 409 | A client with this email is already in your workspace; clientId names them when you can reach them. |
already_reviewed | 409 | The check-in submission was already reviewed. |
note_limit_reached | 409 | The client already has 100 notes. |
messaging_disabled | 409 | Messaging is turned off for this client. |
open_task_limit_reached | 409 | The workspace has 250 open tasks. |
webhook_endpoint_exists | 409 | This key already added an endpoint with this URL. |
webhook_endpoint_limit_reached | 409 | The workspace has 10 webhook endpoints. |
webhook_endpoint_disabled | 409 | The endpoint was turned off for good, and can't be turned back on. |
idempotency_key_in_use | 409 | The first request with this Idempotency-Key is still running. |
payload_too_large | 413 | The request body is too large. |
unsupported_media_type | 415 | The body must be sent as application/json. |
idempotency_key_reused | 422 | The Idempotency-Key was already used with a different request. |
rate_limited | 429 | Too many requests. Retry after the seconds in Retry-After. |
internal_error | 500 | Something failed on our side. Retry, and quote requestId if it keeps happening. |
- 4xx except 409 `idempotency_key_in_use` and 429: don't retry. Fix the request first.
- 429: wait the seconds in
Retry-After, then retry. See Rate limits. - 5xx and network errors: retry with exponential backoff. Send the same
Idempotency-Keyon aPOSTso a write that did go through isn't repeated. See Idempotency.
Retry 429s and 5xx
async function hubfit(path, init = {}, attempt = 0) {
const response = await fetch(`https://api.hubfit.com/v1${path}`, {
...init,
headers: { Authorization: `Bearer ${process.env.HUBFIT_API_KEY}`, ...init.headers },
});
if ((response.status === 429 || response.status >= 500) && attempt < 4) {
const wait = Number(response.headers.get("Retry-After")) || 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return hubfit(path, init, attempt + 1);
}
return response;
}