Exception codes
Every code a service actually returns, grouped by scope and by what you do about it.
error.code is the stable, machine-readable half of a failure. Message text
changes with translations and copy edits; the code does not.
Codes are scoped per service. Each one below is listed under the service that produces it, and only codes that are actually in use appear here.
The same string can exist in more than one source. rate_limit_exceeded from
the gateway and rate_limit_exceeded from authentication are separate
definitions that happen to agree — match on code and on which call you made.
From the SDK
Produced locally, without a round trip.
no_session401A call needing a session ran with none stored.
request_timeout408Your configured timeout elapsed.
network_error0Nothing answered. This is the offline case.
invalid_path400A path parameter was . or ... Ids are URL-encoded into a single segment, but
those two would still resolve to another endpoint, so the call is refused.
unknown_errorA response arrived that did not parse as an error.
Gateway
Every call to a project goes through the gateway at https://<project>.nulvless.co
first. It checks the API key and the state of the project before the request
reaches a service, so these codes can come back from any SDK call.
project_unavailable403The project is in a state that cannot serve requests.
rate_limit_exceeded429Too many requests from your IP. The Retry-After header says when to try
again. An IP that keeps hitting routes that do not exist is blocked for ten
minutes.
internal_error500The gateway failed while handling the request.
service_not_enabled501The service is not installed in this project, or not in this environment.
bad_gateway502The service behind the gateway could not be reached.
service_unavailable503The service failed to set up for this environment.
project_not_ready503The project is still being built or restored. Retry shortly.
upstream_timeout504The service took too long to start answering.
idle_timeout504The service stopped sending data mid-response.
deadline_exceeded504The request ran past the total time allowed for the route.
project_paused540The project is paused. Resume it in the dashboard.
A route the gateway does not know answers 404 with an empty body, so there is
no code to read.
Authentication
The largest scope: sign-up, sign-in, sessions, factors, grants, OAuth.
Not signed in — 401
unauthorized401No valid session token.
invalid_credentials401The password, code or token did not match.
refresh_token_not_found401The refresh token matches no session. Sign the user in again.
refresh_token_already_used401The refresh token was already exchanged and was replayed after the reuse window. The whole session is ended as a precaution. Sign the user in again.
refresh_token_not_issued_here401The refresh token is newer than the session knows about, so it was not issued by this environment. Sign the user in again rather than retrying.
Signed in, but blocked — 403
protected_action_denied403Sudo mode is required for this action.
mfa_required403The environment requires MFA and this session never used a second factor.
mfa_factors_limit_reached403The user already holds the most authenticator apps and SMS factors the environment allows. Remove one before adding another.
mfa_phone_already_enrolled409This phone is already one of the user's SMS factors.
mfa_phone_not_supported400phone was sent while enrolling a factor other than sms_otp.
registration_closed403Sign-up is turned off for this environment.
user_not_confirmed403The account was never confirmed.
user_banned403The account is banned.
forbidden_resource_access403No grant on the resource, or a required scope or role is missing.
forbidden_access_waiting_accept403A grant exists but is still invited or requested.
forbidden_special_role_required403The action needs a specific role.
cannot_leave_owner_access403An owner cannot leave their own resource.
oauth_account_link_required403The provider account must be linked to an existing user first.
unavailable403The method is disabled for this environment.
action_verification_failed403 / 401A challenge could not be confirmed — wrong code, or too many attempts.
not_allowed403 / 401 / 422A general refusal. Read the message.
Rejected input — 400
password_weak400Failed the length or character requirements.
password_leaked400Found in a known breach, and the environment blocks those.
captcha_required400A captcha token was expected and not sent.
captcha_verification_failed400The captcha token was rejected.
bad_oauth_state400The OAuth state did not match.
bad_oauth_callback400The provider callback was malformed.
redirect_uri_mismatch400The redirect URI is not on the allowlist.
provider_unavailable400The social provider is off for this environment.
provider_unsupported_method400That provider does not support the requested method.
oauth_email_missing400The provider returned no email.
oauth_email_not_verified400The provider returned an unverified email.
Conflicts with existing data — 422
email_already_exists422Taken.
phone_already_exists422Taken.
username_already_exists422Taken.
user_missing_email422The environment requires an email and none was given.
user_missing_phone422The environment requires a phone and none was given.
user_already_confirmed422Confirming something already confirmed.
user_already_banned422Banning someone already banned.
user_not_banned422Unbanning someone who is not banned.
permission_already_granted422The grant already exists.
Sign-up can return several of these at once — message becomes an array.
Not now
rate_limit_exceeded429Too many attempts. The body carries a retryAfter the SDK does not surface.
Edge function runtime
A different scope from the platform API. These are produced by the runtime that actually executes your function, not by the API that deploys it — so you see the first group as an HTTP response to an invocation, and the second only in logs.
Returned by an invocation
function_not_found404No function at that path.
function_unavailable403The function exists but the control plane refuses to serve it — deactivated, or its project is not servable. A settled state, not a transient one.
execution_timeout546Wall-clock limit exceeded.
cpu_time_limit_exceeded546CPU time limit exceeded — the function was busy rather than waiting.
memory_limit_exceeded546Memory limit exceeded.
resource_unavailable503The function could not be loaded — metadata or bundle download failed. Retryable.
boot_failed503The worker could not be started. Retryable.
546 is not a typo
The three limit violations answer with 546, a non-standard status chosen so
they are distinguishable from anything your own code returns. Match on it
directly — a generic status >= 500 check will lump them in with real server
errors.
When your own code throws, the runtime answers a plain 500 with no code
field at all. That absence is the signal: a coded failure came from the
platform, an uncoded 500 came from your function.
Recorded in logs
The runtime ships events to the logging service rather than to the caller. Four
carry an error.code, and they mirror the limit violations above — so a 546
your users saw has a matching log line with the limit that was hit:
internal_server_errorAn unhandled error inside the function.
memory_limit_exceededCarries runtime.memory_limit_mb.
execution_timeoutCarries runtime.timeout_ms.
cpu_time_limit_exceededCarries runtime.cpu_time_limit_ms.
Every runtime log also carries a kind, which is how you filter them apart:
log for your own console.* output, boot for a cold start (with
cold_start_ms), invoked for per-request CPU and memory, shutdown for a
worker ending, and one per limit violation.
A shutdown carries a reason. memory_limit_exceeded and
cpu_hard_limit_exceeded are logged as errors, cpu_soft_limit_reached as a
warning, and the routine ones — inactivity, lifetime recycle — as info.
Only kind: "log" is your function's own output. Everything else is
platform-generated and marked internal, which is what keeps runtime bookkeeping
out of your log consumption metrics.
Notifications
Sending email and SMS, and the templates behind them.
unauthorized_platform401Bad or missing key.
rate_limit_exceeded429The send quota for this channel is spent. No retryAfter on this one.
comms.email.send resolving without an error means the message was accepted
for delivery, not delivered. Everything after that point shows up on the
record, not on the call.