Error codes

Error codes

Transport errors (plain HTTP)

Returned by Echo / the auth middleware before the request reaches GraphQL. Body is {"message":"..."} plain JSON.

HTTP Message Where
400 invalid request body auth endpoints, body parse failure
400 invalid email format /auth/send-otp, /auth/verify-email/*
400 email and code are required /auth/verify-otp, /auth/verify-email/confirm
401 Missing token auth middleware — no Authorization header
401 Invalid token auth middleware — bad signature, expired, session missing/revoked
401 invalid or expired code OTP verification — no active OTP or wrong code
401 invalid session /auth/renew, /auth/verify-email/* — session context missing
429 too many OTP requests more than 3 OTP sends per email in the last hour
429 too many verification requests for this email same limit on /auth/verify-email/send
429 too many verification attempts OTP consumed 5 wrong attempts
500 failed to create session / failed to create token infrastructure failure during login
500 failed to renew session / failed to record email verification infrastructure failure
503 unable to send verification code, please try again email service unreachable

GraphQL errors

Any error from a resolver comes back in the standard GraphQL envelope:

{
  "data": { "user": null },
  "errors": [
    {
      "message": "vault is sealed",
      "path": ["user", "document", "get"],
      "extensions": { "code": "SEALED" }
    }
  ]
}

Common errors[].message values you will see from the User scope:

Message Typical cause
vault is sealed Operation requires unsealed vault — call user.security.unseal first
not a member of this vault Shared-vault access denied
insufficient role Role too low for the operation (e.g. VIEWER trying to create)
schema validation failed: ... DocumentInput.data didn’t match schemaRef / documentSchema
document not found id doesn’t exist or was deleted
unique document already exists uniquePerVault: true and one already present
PIN required / invalid PIN Tier 2 unseal with missing or wrong PIN

Null scope roots

Because every scope field is nullable (user: UserQuery — not UserQuery!), any resolver error inside a scope will propagate up to null out the whole scope in the response:

{ "data": { "user": null }, "errors": [ { "path": ["user","document","create"], "message": "..." } ] }

This is deliberate — it isolates failures per scope. Always check data.user !== null before using data.user.x.