Errors
Every error from the Logu API (https://api.logu.dev) has the same shape:
{
"error": {
"code": "unknown_field",
"message": "Unknown field \"timeout_seconds\" (did you mean \"timeoutSeconds\"?)",
"hint": "Rename timeout_seconds to timeoutSeconds. Field names are camelCase.",
"docUrl": "https://logu.dev/docs/errors#unknown_field",
"requestId": "8c1f0b2a9d3e4f51-GRU",
"issues": [{ "path": "timeout_seconds", "message": "Unknown field; did you mean \"timeoutSeconds\"?" }]
}
}
codeis stable: branch on it, not onmessage.messageis for people and includes the field problems.hintsays what to do next.docUrllinks to the code's section below.requestIdis also sent as theLogu-Request-Idheader on every response (it's Cloudflare'scf-raywhen there is one). Quote it when reporting a problem.issueslists each invalid field (pathis dotted:options.0.label).askcomes with a409 conflictabout an ask: its current state.
The SDKs raise ValidationError (400), PlanLimitError (402, for plan_limit and ceiling_reached), AuthenticationError (401),
PermissionError / PermissionDeniedError (403), NotFoundError (404), ConflictError (409),
RateLimitError (429) and LoguError for the rest, each with code and issues.
The codes are defined in ERROR_CODES in packages/protocol/src/index.ts; a test checks this page
lists every one.
bad_request#
HTTP 400. The request is malformed (the body isn't JSON) or a field is invalid: too long, the wrong
type, a defaultOption that isn't one of the options, a decision option the ask doesn't have. Also
creating an 11th webhook endpoint (a workspace has at most 10), or a webhook endpoint URL that
isn't a public https: address.
Hint: fix the fields listed in issues and send the request again.
approving_default#
HTTP 400. The ask's defaultOption is an approving option: the one with the id approve, or any
option with style: "primary" (the go-ahead; the default Approve / Decline pair's Approve is both).
The default is what Logu records when nobody answers before the ask expires, so an agent can't make
the go-ahead its own default: nobody would have said yes. Nothing was asked, so don't proceed.
Allowed defaults: any other option (decline, a danger or default styled one) or none (the ask
then ends timed_out). Asks stored before this rule keep the default they have.
Pre-approval is a rule: an Owner decides in advance which asks to approve (Settings → Rules in the app), and
the decision is recorded with decidedVia: "rule". A decision with decidedVia: "timeout" is never
an approval, whatever its optionId: check decidedVia (the SDKs, the CLI, the n8n node, the hooks
and the MCP approved field already do).
Hint: use defaultOption: "decline" (fail closed) or leave it out, or ask an Owner to create a rule
for pre-approval.
unknown_field#
HTTP 400. The body has a field the endpoint doesn't know. Request bodies are strict, so a typo or a
snake_case name (timeout_seconds, default_option) is an error instead of being silently ignored
(an ask meant to fail closed would otherwise never decide). Each unknown field is in issues, with
a "did you mean" suggestion when there is a close match.
Hint: remove or rename the fields listed in issues. Field names are camelCase (timeoutSeconds,
defaultOption, allowText, callbackUrl, idempotencyKey). Query strings are not strict.
plan_limit#
HTTP 402. This is over one of the workspace's plan quotas (docs/billing.md): its asks, events
or metric reports this calendar month (UTC), the image storage it holds, its projects or metric
names (on Free and on Pro, where the quotas are fair use), on Free also what test API keys send,
the emails Logu sends for it and its image uploads, and on Free its agents, inviting members and
webhook endpoints. Nothing was created: an ask over the limit was not asked, so an agent must
not proceed as if a human had answered. These are only returned on servers that enforce plan
limits (BILLING_ENFORCED); the always-on cost ceilings answer ceiling_reached instead.
One more plan_limit is always on, whatever BILLING_ENFORCED says: a Free account (one that
owns no workspace on Pro) that already owns 3 shared workspaces and creates a 4th, or becomes the
Owner of one (POST /v1/workspaces, promoting a member, accepting an Owner invitation). Its
hint says to upgrade one of its workspaces to Pro (then an account may own 25) or write to the Logu
team if it is an agency or consultant. Humans only: an API key can't create workspaces.
Hint: an Owner can upgrade the workspace to Pro in Logu → Settings → Plan (on Pro, ask the Logu team to raise the quota); monthly counts reset on the 1st (UTC). Agents: tell your human, and don't retry until then.
ceiling_reached#
HTTP 402. The workspace reached one of Logu's cost ceilings, which are always on and generous: asks (20,000), events (200,000) or metric reports (1,000,000) in a month, the same for what test API keys send (5,000 / 50,000 / 250,000), image uploads a month (20,000), the image bytes held (25 GB), projects (500), metric names (2,000), or the active API keys of one agent (20) (docs/billing.md has them all); also an account that already owns the most shared workspaces an account may own (25 with a workspace on Pro, or the number set for it). Upgrading doesn't lift a ceiling. Nothing was created: an ask over a ceiling was not asked, so an agent must not proceed as if a human had answered. The workspace's Owners are told once a month per meter (an event from Logu and an activity entry).
Hint: tell your human, don't assume their answer, and don't retry before the 1st (UTC). The Logu team can raise a ceiling for one workspace. WhatsApp and email have ceilings too, but those never fail a request: the channel skips the send and the ask still lands in the app.
unauthorized#
HTTP 401. No Authorization header, or the API key or session is invalid or revoked.
Hint: send Authorization: Bearer lg_… with an active API key. Create an agent in Logu → Agents;
its first API key is shown once.
forbidden#
HTTP 403. The caller is authenticated but may not do this: an API key on a human-only endpoint (deciding an ask, managing agents, creating workspaces), or a member without the needed role (a Viewer deciding, a Decider managing webhook endpoints). On the sign-up, password sign-in and magic-link routes, a 403 means the Cloudflare Turnstile check wasn't completed or was refused (only on servers that use Turnstile): complete it and try again.
Also POST /v1/account/deletion and POST /v1/account/export (deleting an account, exporting
its data; Your account and data) need a session started in the last 15 minutes, and
a sign-in to an account in its 30-day deletion grace by password or passkey is refused with a hint
to ask for a magic link, which restores it.
Hint: agents send asks, events and metrics and read their workspace; only a human Decider or Owner decides asks. Agents can never decide. For the account routes, sign in again (a magic link is quickest) and retry within 15 minutes.
workspace_suspended#
HTTP 403. Logu suspended the workspace the API key (or connector) belongs to, usually for abuse or a violation of the terms. Nothing the agent sends is accepted, and nothing is delivered for the workspace (push, email, Telegram, Slack, WhatsApp, webhooks, callbacks) until it is reactivated. The workspace's members can still sign in, read what is there, export it and delete it. This is not something an agent can fix or retry past.
Hint: tell your human. They can sign in at https://logu.dev to see the workspace and contact Logu support to ask for a review.
email_not_verified#
HTTP 403. The signed-in human's email isn't verified yet, and the request needs it: accepting an invitation (otherwise anyone could register an invited address with a password and join in its place), creating a shared workspace, or becoming the Owner of one (a verified mailbox is what makes an account worth its own workspaces). Signing in with a magic link, or the confirmation link sent after a password sign-up, verifies it.
Hint: confirm your email with the link Logu sends (the app offers it on the invitation page and in Settings → Account), then retry.
not_found#
HTTP 404. No such route, or the ask, metric, agent or workspace doesn't exist in your workspace (an API key never sees another workspace's data, so those are "not found" too).
Hint: check the path and the id; GET /v1/me shows which agent and workspace an API key is.
method_not_allowed#
HTTP 405. The path exists, but not with this method (e.g. DELETE /v1/events or
PUT /v1/asks). The Allow header lists the methods it takes.
Hint: use one of the methods in the Allow header.
conflict#
HTTP 409. The resource isn't in a state that allows this. Most often: the ask is no longer
waiting (it was decided, timed out or withdrawn), so it can't be decided or withdrawn again; the
body's ask is then the ask as it is now. Also: an invitation that is no longer pending, a member
who already belongs to the workspace, removing or demoting the last Owner, redelivering a webhook
delivery that is still pending or to a webhook endpoint that is turned off. And deleting your
account while you are the only Owner of a shared workspace that has other members, or while a
workspace that would be deleted with it has a live Pro subscription (the hint names the
workspaces and what to do); an export asked while another is being prepared.
Hint: read the current state instead of retrying; for an ask, error.ask.status and
error.ask.decision. For account deletion, do what the hint says (transfer ownership or delete
the workspace, cancel the subscription), then ask again.
idempotency_key_in_use#
HTTP 409. Another request with the same idempotency key is still being processed.
Hint: retry in a moment with the same key; you'll get that request's result.
idempotency_key_reused#
HTTP 422. The idempotency key (Idempotency-Key header, or idempotencyKey in an ask) was already
used by this agent in the last 24 hours with a different body or on a different endpoint.
Hint: use a new key for a different request; reuse a key only to retry the exact same request. Keys are per agent and remembered for 24 hours.
rate_limited#
HTTP 429. This API key sent too many requests. Limits are per API key, per minute: 60 asks, 600
events and metrics together, 1200 requests of any kind; and per workspace, all its API keys
together: 3000 requests a minute (more keys don't raise it). A member may also start only 5
Checkout Sessions an hour. The response has Retry-After (seconds)
and RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy headers.
Humans signed in to the app aren't limited, except for data exports (one a day). Unauthenticated endpoints (sign-in, POST /v1/device/code)
are limited per IP address.
Hint: wait Retry-After seconds, then retry. The SDKs do this for you.
authorization_pending#
HTTP 400, from POST /v1/device/token only. The device code is valid but nobody has approved it
in the app yet (logu login and the app's /connect page; see Connecting agents).
Hint: keep polling every interval seconds (5 by default) until the human approves or denies it,
or it expires.
slow_down#
HTTP 400, from POST /v1/device/token only. The client polled sooner than interval seconds after
its last poll. As in RFC 8628, add 5 seconds to the interval from now on.
Hint: wait 5 seconds longer between polls, then poll again.
expired_token#
HTTP 400, from POST /v1/device/token only. The device code expired (it lives 10 minutes), or its
API key was already delivered: a device code gives out its API key exactly once.
Hint: start again with a new device code (logu login).
access_denied#
HTTP 403, from POST /v1/device/token only. The human chose Cancel on the /connect page.
Hint: stop polling. Start again only if the human wants to connect this agent.
internal_error#
HTTP 500. Something went wrong on Logu's side.
Hint: retry with backoff. If it keeps happening, report the requestId.
overloaded#
HTTP 503 with Retry-After. The workspace's realtime hub (the one Durable Object that holds
long-polls and the app's streams) could not take this request, so it was shed instead of hanging:
GET /v1/asks/{id}/wait and MCP waits. Creating asks, sending events and metrics, and deciding
never depend on the hub and never answer this code: if the hub is down they still succeed (only the
live updates are late).
Over the hub's caps the long-poll is not an error: it answers 200 at once with the ask still
waiting and a Retry-After header. Clients keep polling, no sooner than that header says (the
SDKs, the CLI and the Python client already sleep at least one second between polls).
Hint: retry after Retry-After seconds, with jitter. The ask is unchanged.
service_unavailable#
HTTP 503. A dependency is briefly down (e.g. an ask's workflow couldn't start), or a feature isn't configured on this server (browser push, Telegram, Slack).
Hint: retry with backoff (asks: with the same idempotency key). For a feature that isn't configured, ask the server's operator.