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:
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:
This is deliberate — it isolates failures per scope. Always check
data.user !== null before using data.user.x.