Errors
The problem format the Developer API answers with, and every code it can return, with its status and what to do about it.
Errors are RFC 9457 problem responses, sent as application/problem+json. Read code, which is stable. title and detail are for people and can change.
{
"type": "https://usehalo.net/problems/insufficient_scope",
"title": "Forbidden",
"status": 403,
"code": "insufficient_scope",
"detail": "This key's scopes don't include data.query.",
"requestId": "4f1c2a90-…"
}| Field | Means |
|---|---|
type | https://usehalo.net/problems/{code}. It identifies the problem, and isn't a page to open |
title | The HTTP status in words |
status | The HTTP status |
code | The stable, machine-readable code |
detail | What went wrong this time, in a sentence. With several problems, the first of errors |
requestId | The request's id, also in the X-Request-Id header. Quote it to Halo |
errors | With validation_failed, bad_request and unsupported_media_type: each problem's location, message and value |
signInMethods | With sign_in_method_required: the methods the organization allows |
feature | With plan_feature_unavailable: the product the plan doesn't include |
trash | With in_trash: the item's kind, id, name and canRestore |
You can send your own X-Request-Id, of 8 to 64 letters, digits, dots, underscores or hyphens. Halo keeps it, and otherwise makes one.
Every code
The request
| Code | Status | Means | What to do |
|---|---|---|---|
key_in_query | 400 | A key was in the query string, as access_token, api_key, apikey, key or token | Send it in the Authorization header. Roll the key if the URL may have been logged |
bad_request | 400 | The body isn't JSON, or a POST has no body | Send a JSON object |
request_entity_too_large | 413 | The body is over 1 MiB | Send fewer filter values |
unsupported_media_type | 415 | The body's Content-Type isn't one Halo reads | Send Content-Type: application/json |
validation_failed | 422 | The request doesn't fit the API's shape: an unknown body field, a limit over 1,000, an id that isn't a UUID, an unknown op or aggregate | Read errors, and see Query data |
These are checked before the key is, so they aren't in the request log and don't use up your limits.
The key and the person
| Code | Status | Means | What to do |
|---|---|---|---|
invalid_api_key | 401 | The key is missing, unknown, expired or revoked. The answer doesn't say which, and carries WWW-Authenticate: Bearer error="invalid_token" | Check it's being sent, then see what stops a key. Don't retry with the same key |
insufficient_scope | 403 | The key's scopes don't include what this operation needs | Make a key with that scope |
forbidden | 403 | The person's role doesn't allow this: it lacks api.use or content.view, has no role in Halo CMS or Halo CRM for their routes, or asks for a draft without edit access | Ask an Admin about the role, or read the published version |
api_disabled | 403 | The Developer API is off for the organization, as it is until an Owner or Admin turns it on | An Owner or Admin can turn it on in Settings, under API access |
api_stopped | 503 | Halo has stopped the Developer API for now, for this organization or everyone | Keep your keys, and try again after Retry-After. Contact Halo if it goes on |
playground_key | 403 | A playground key was used away from the portal's pages | Make an API key |
org_suspended | 403 | The organization is suspended | Its Owners were emailed, and can still use Contact Halo in Halo Data |
member_suspended | 403 | The person's membership is suspended. Suspending also revokes their keys, so you'll usually see invalid_api_key instead | Ask one of its Admins |
mfa_enrollment_required | 403 | The organization requires two-factor, and the person hasn't set it up | Set it up, then make a new key, since that revokes the old one |
sign_in_method_required | 403 | The organization no longer allows how the person signed in when they made the key | Sign in a way signInMethods lists, and make a new key |
What you asked for
| Code | Status | Means | What to do |
|---|---|---|---|
export_not_allowed | 403 | Reading rows through the API counts as exporting, and the role doesn't allow it | Ask an Admin for a role with data.export |
marketplace_data_not_available | 403 | The dataset is Marketplace data. Its fields can be read, and its rows only in Halo | Use it in Halo, or query your own data |
plan_feature_unavailable | 403 | The organization's plan doesn't include Halo CMS or Halo CRM, which websites, listings and businesses read from. feature says which: cms or crm | Contact Halo about the plan |
not_found | 404 | It doesn't exist, or the person doesn't have access | Check the id. The answer never says which |
report_not_published | 409 | The report has no published version | Publish it, or ask for version=draft with edit access |
in_trash | 410 | The item, or the connection a dataset reads, is in the Trash | Restore it if trash.canRestore says you can, or ask someone who can |
hidden_field | 422 | The query names a field hidden in this dataset | Use a field the dataset's description lists |
invalid_query | 422 | The query doesn't fit the dataset, such as an unknown field, a sort on a field that isn't in the query, or an unknown time zone. detail says which | Fix the query |
Limits and the source
| Code | Status | Means | What to do |
|---|---|---|---|
rate_limited | 429 | Too many requests from the key, the person or the address, or too many running at once. Carries Retry-After: 60 | Wait, then retry. See Rate limits |
row_budget_exceeded | 429 | The person's keys have read today's rows. Carries Retry-After: 3600 | Aggregate more, or resume later. See the budget |
query_busy | 429 | The organization is running as many queries as it can | Retry in a moment, with backoff |
internal | 500 | Something went wrong in Halo | Retry a little later. Quote requestId if it keeps happening |
source_error | 502 | The source couldn't run the query. detail holds the source's words only for people who can view connections | Retry later, or aggregate or filter differently |
request_timeout | 503 | A request that isn't a query ran out of time | Retry with backoff |
query_timeout | 504 | The source took too long | Narrow the date range or filters. Retrying the same query rarely helps |
Which to retry
| Retry | Codes |
|---|---|
After Retry-After | rate_limited, api_stopped |
| With backoff, a few times | query_busy, source_error, request_timeout, internal |
| Later, or not at all | row_budget_exceeded, query_timeout |
| Never as they are | Everything else: they need a change to the request, the key or the person's access |
Snippets has this as code.
When managing keys
These come from the portal's key routes, which take a signed-in session rather than a key. You'll see them there, not from your own code.
| Code | Status | Means | What to do |
|---|---|---|---|
step_up_required | 401 | The session didn't finish two-factor | Sign in again, finishing two-factor |
view_only | 403 | Halo staff are viewing as someone | Keys can't be made in this mode |
api_disabled | 403 | The Developer API is off for the organization, as it is until an Owner or Admin turns it on | An Owner or Admin can turn it on in Settings, under API access |
forbidden | 403 | The role lacks api.use, or the change needs org.manage | Ask an Admin |
not_found | 404 | The key isn't there, or isn't yours to change, or has already ended | Check the key |
sign_in_again | 403 | The session signed in more than 15 minutes ago | Sign in again, then make or roll the key. Playground keys and revoking don't need it |
too_many_keys | 409 | The person has 25 live keys in the organization | Revoke one you no longer use |
temporary_key | 409 | A playground key can't be rolled | It ends within the hour. Make another |
key_replaced | 409 | This key was already rolled | Roll the newer key, which has the same name |
validation_failed | 422 | The name is empty, a scope is unknown, or the key would last over 365 days | Read detail, and errors for each problem |
rate_limited | 429 | More than 30 keys made or rolled in an hour, or more than 20 playground keys in an hour | Wait a while, and try again |
The MCP server has its own error format: see The MCP protocol.