Guides
Errors
Failed requests return an HTTP status code and a JSON problem-details body with a stable error code you can branch on.
Error format
Errors follow RFC 9457 (Problem Details for HTTP APIs) and are served as application/problem+json:
{
"type": "https://weguard.ai/docs/errors#invalid_parameter",
"title": "Invalid parameter",
"status": 400,
"code": "invalid_parameter",
"detail": "'limit' must be an integer between 1 and 100.",
"instance": "/core/v1/devices",
"requestId": "req_k3j2h1g0f9e8d7c6b5a4z3y2",
"errors": [
{
"field": "limit",
"code": "invalid_parameter",
"message": "'limit' must be an integer between 1 and 100."
}
]
}typestring- URI that identifies the problem:
https://weguard.ai/docs/errors#<code>. It links to the entry for the code on this page. titlestring- Short, human-readable summary of the problem type. Same for every occurrence of a code.
statusinteger- The HTTP status code, repeated for convenience.
codestring- Stable, machine-readable error code. Branch on this value. Codes are never renamed, but new codes can be added.
detailstring- Human-readable explanation of this occurrence. The wording can change, so don’t parse it.
instancestring- The request path that failed.
requestIdstring- Id of the request, also in the
X-Request-Idheader. Quote it when you contact support. errorsarray of objects- Per-parameter problems. Present only when specific parameters are at fault.
errors.fieldstring- The parameter at fault, such as
limit. errors.codestring- Error code for this parameter.
errors.messagestring- Human-readable explanation.
Members without a value are omitted, not sent as null or an empty array. For example, errors appears only when specific parameters are at fault. Error responses are sent with Cache-Control: no-store.
HTTP status codes
| Status | Meaning |
|---|---|
| 200OK | The request succeeded. |
| 304Not Modified | The resource has not changed since the ETag you sent in If-None-Match. The body is empty. |
| 400Bad Request | A parameter is invalid, unknown or repeated, or the cursor is invalid. |
| 401Unauthorized | The API key is missing, malformed, unknown, expired or revoked, or the Authorization header does not use the Bearer scheme. |
| 403Forbidden | The key lacks a required scope, or its account is inactive. |
| 404Not Found | The route, or the resource in your account, does not exist. |
| 405Method Not Allowed | The route does not support this method. See the Allow header. |
| 406Not Acceptable | The Accept header excludes JSON. |
| 422Unprocessable Content | The request is valid, but the device’s platform does not report this resource. |
| 429Too Many Requests | The rate limit was exceeded. Honor Retry-After. |
| 500Internal Server Error | Something went wrong on our side. Retry with backoff. |
| 503Service Unavailable | A dependency is temporarily unavailable. Honor Retry-After, then retry with backoff. |
Error codes
This list covers every code the API returns. Each entry is the target of the type URI in the problem body, so https://weguard.ai/docs/errors#rate_limit_exceeded links straight to it.
A query parameter has an invalid value or was sent more than once. Examples: limit=0, includeTotal=yes, an unknown platform, an actor that is not an email address, a since / until that is not an ISO-8601 timestamp with an offset, a date that is not YYYY-MM-DD on a per-day list, an until date later than today, or a window longer than the endpoint allows.
What to do: Fix the parameter named in errors[].field and send the request again.
The request includes a query parameter the endpoint does not support. Parameters are matched strictly, so a typo such as ?platfrom=ios fails instead of silently returning unfiltered data.
What to do: Remove or correct the parameter. The detail lists the parameters the endpoint accepts. Endpoints that take no parameters reject every query parameter.
The cursor is malformed, or it was issued for a request with different filters.
What to do: Pass nextCursor back exactly as you received it, with the same filters as the request that returned it, or start again without a cursor. See Pagination.
The request has no Authorization header.
What to do: Send your API key as Authorization: Bearer wgk_…. See Authentication.
The key is malformed, does not exist, or its secret does not match. The WWW-Authenticate header carries error="invalid_token".
What to do: Check that you copied the full key (wgk_<keyId>_<secret>) with no extra whitespace and nothing cut off. If you lost the secret, create a new key.
The key is past its expiresAt.
What to do: Create a new key, deploy it, and remove the expired one from your configuration.
An account admin revoked the key. Revocation takes effect immediately and cannot be undone.
What to do: Ask an account admin for a new key.
The key is valid but lacks a scope the endpoint requires. The WWW-Authenticate header names the missing scopes.
What to do: Create a key with the required scopes. You cannot add scopes to an existing key. See Authorization.
The WeGuard account that owns the key has been deleted or disabled.
What to do: Contact your WeGuard account manager or support.
No resource with that id exists in your account. Resources that belong to other accounts, and values that are not well-formed ids, are reported the same way.
What to do: Check the id. Ids are 24-character hex strings returned by list endpoints.
No endpoint matches the request path.
What to do: Check the URL against the API reference, including the /v1 prefix.
The route exists but does not support this HTTP method. The Allow header lists the methods it does support.
What to do: Use one of the methods in the Allow header. Every v1 endpoint is GET.
The Accept header excludes JSON.
What to do: Send Accept: application/json or omit the header.
The device exists, but its platform does not report the data this endpoint returns. Screen time and app usage are available for Windows devices only. The detail names the device’s platform.
What to do: Don’t retry. Call the endpoint only for devices whose platform it supports, and skip the others. An empty list would wrongly suggest the device was not used, so the API returns this error instead.
The key has used up its request budget. See Rate limits.
What to do: Wait for the number of seconds in Retry-After, then retry with exponential backoff and jitter.
An unexpected error occurred on our side.
What to do: Retry with exponential backoff. If it persists, contact support and quote the requestId.
Console-only codes
Only API key management in the WeGuard console returns these codes. The /v1 API never does. They’re listed here so their type links resolve.
The request body is missing or is not valid JSON.
What to do: Send a valid JSON body with Content-Type: application/json.
One or more body fields are invalid. Each problem is listed in errors[].
What to do: Correct the fields listed in errors[].
The web console session is missing, invalid or expired.
What to do: Sign in to the WeGuard console again.
The console user is not an account admin, so they cannot manage API keys.
What to do: Ask an account admin to create or revoke the key.
The account already has the maximum number of active API keys.
What to do: Revoke a key that is no longer used, then create the new one.
The request body is not JSON.
What to do: Send the body as application/json.
Retrying requests
Decide what to do from the status code, then the error code:
- Retry
429,500and503. IfRetry-Afteris present, wait that many seconds. Otherwise, use exponential backoff with jitter, and cap the number of attempts. - Don’t retry other
4xxerrors unchanged, because the same request will fail again. Fix the request, the key or its scopes first. - A
404 resource_not_foundon a single resource usually means it was deleted or never existed in this account. Treat it as gone.404 location_unavailableis different: the device exists but hasn’t reported a location yet. Keep the device and check again later. - A
422 unsupported_platformmeans the endpoint doesn’t serve the device’s platform. Don’t retry it. Skip such devices, for example by checkingplatformon the device first.
const RETRYABLE = new Set([429, 500, 503]);
async function weguardFetch(url, { maxAttempts = 5 } = {}) {
for (let attempt = 1; ; attempt++) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.WEGUARD_API_KEY}` },
});
if (response.ok) return response.json();
const problem = await response.json();
if (!RETRYABLE.has(response.status) || attempt === maxAttempts) {
throw new Error(`${problem.code}: ${problem.detail} (requestId ${problem.requestId})`);
}
// Honor Retry-After when present, else exponential backoff with full jitter.
const retryAfter = Number(response.headers.get("Retry-After"));
const backoff = Math.min(30_000, 500 * 2 ** attempt);
const delay = retryAfter > 0 ? retryAfter * 1000 : Math.random() * backoff;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}import os
import random
import time
import requests
RETRYABLE = {429, 500, 503}
def weguard_get(url, params=None, max_attempts=5):
headers = {"Authorization": f"Bearer {os.environ['WEGUARD_API_KEY']}"}
for attempt in range(1, max_attempts + 1):
response = requests.get(url, params=params, headers=headers, timeout=30)
if response.ok:
return response.json()
problem = response.json()
if response.status_code not in RETRYABLE or attempt == max_attempts:
raise RuntimeError(f"{problem['code']}: {problem['detail']} (requestId {problem['requestId']})")
# Honor Retry-After when present, else exponential backoff with full jitter.
retry_after = response.headers.get("Retry-After")
if retry_after:
delay = float(retry_after)
else:
delay = random.uniform(0, min(30, 0.5 * 2 ** attempt))
time.sleep(delay)All v1 endpoints are read-only GET requests, so retrying them never changes data.
Contacting support
Every response, successful or not, has an X-Request-Id header, and every error body repeats it as requestId. Log it with your errors and include it when you contact WeGuard support.
Bring your own request id
Send your own X-Request-Id (8 to 128 characters: ASCII letters, digits and . _ : -) to match WeGuard requests to your logs. See Request ids.