Error codes
Fidmos errors come in three different shapes — they depend on which endpoint answered. This page shows each shape and its codes.
Response shapes
Section titled “Response shapes”- Embed (
/api/v1/embed/*) — the envelope{ "error": "<code>" }, HTTP 422 (or 401 forinvalid_client). - Token (
POST /oauth/token) — the OAuth 2.0 standard (RFC 6749):{ "error": "...", "error_description": "..." }. - UserInfo and Server-to-Server API — the Laravel shape:
{ "message": "...", "errors": { … } }or401/403for token problems.
Flow initialization — POST /api/v1/embed/init
Section titled “Flow initialization — POST /api/v1/embed/init”This is called by the sign-in widget (sdk.js), not you — the window shows the errors. The
envelope is { "error": "<code>" }.
| Code | HTTP | Reason |
|---|---|---|
invalid_client |
401 | unknown or inactive client_id |
invalid_redirect_uri |
422 | redirect_uri is not in the allow-list |
invalid_origin |
422 | the page origin is not allowed for the client |
Window steps — /api/v1/embed/*
Section titled “Window steps — /api/v1/embed/*”These are called by the sign-in widget (the steps under /api/v1/embed/: login, login/send,
telegram, siwe/*, identity/create, complete) — you, the partner, do not need to call them.
Common to any window step are the flow-check errors:
| Code | Reason |
|---|---|
flow_expired |
the flow was not found or has expired |
parent_origin_mismatch |
the request came from a foreign origin |
Sending the code (/login/send):
| Code | Reason |
|---|---|
code_send_limit |
the code-send limit is exhausted |
flow_too_short |
the flow has too little lifetime left — a new code is not issued |
invalid_flow_state |
the flow is at the wrong step |
Verifying the code (/login):
| Code | Reason |
|---|---|
invalid_code |
the code is wrong |
code_expired |
the code has expired |
code_attempts_exhausted |
attempts are exhausted |
identity_taken |
the binding is taken by another user |
Telegram (/telegram) — the Telegram sign-in flow:
| Code | Reason |
|---|---|
telegram_invalid |
the Telegram data failed the signature check |
Wallet — SIWE (/siwe/verify) — the wallet sign-in flow:
| Code | Reason |
|---|---|
nonce_mismatch |
the nonce in the message did not match the issued one |
Plus the codes for verifying the SIWE message and signature themselves — their full list is on the wallet sign-in page.
Creating a user (/identity/create) — for Telegram and wallet sign-in:
| Code | Reason |
|---|---|
no_pending_identity |
there is no pending sign-in method to create a user from |
identity_taken |
the method being bound is taken by another user |
Completion (/complete):
| Code | HTTP | Reason |
|---|---|---|
invalid_completion |
409 | the flow completion failed |
authorization_failed |
422 | the window could not issue an authorization code |
Token exchange — POST /oauth/token
Section titled “Token exchange — POST /oauth/token”Standard OAuth 2.0 errors in the shape { "error": "...", "error_description": "..." }:
error |
Reason |
|---|---|
invalid_grant |
the code is wrong, expired, already used, or the code_verifier did not match |
invalid_client |
wrong client credentials |
invalid_request |
a required parameter is missing |
unsupported_grant_type |
an unsupported grant_type |
UserInfo and Server-to-Server API
Section titled “UserInfo and Server-to-Server API”GET /oauth/userinfo and /api/v1/* answer in the Laravel shape:
401— the token is missing, expired or revoked.403— only Server-to-Server/api/v1/*: the client token lacks theusers.readscope./oauth/userinfodoes not refuse by scope — it returnssuband the claims the token’s scope allows.422with{ "message": "...", "errors": { "field": ["…"] } }— validation failed.
How to read an error
Section titled “How to read an error”- Look at the shape of the response — it tells you which endpoint answered.
- Take the machine-readable code (
error) — branch your logic on it, not on the text. - The text (
error_description/message) is for logs and humans, not for conditions in code.
Troubleshooting common failures
Section titled “Troubleshooting common failures”| Symptom | Reason | What to do |
|---|---|---|
invalid_redirect_uri, or a redirect_uri error at /oauth/authorize//oauth/token |
redirect_uri did not match the registered one — the match is exact |
Register the exact URL in the application (dashboard → Applications): scheme, host, path, trailing slash; send it unchanged in the request |
invalid_origin or parent_origin_mismatch |
The page with the widget is opened from a non-allowed domain | The allowed origin is derived from the application’s redirect_uris — add the domain to redirect_uris (dashboard → Applications) and open the window from it |
A CORS error on /api/v1/embed/* in the browser |
The request went from a domain that is not among the application’s origins | Add the window’s domain to the application’s redirect_uris (dashboard → Applications) — the origin is taken from there, and the server returns the right CORS headers for it |
invalid_client |
Wrong client_id / client_secret |
Check the pair; client_secret is not the public client_id, and it lives only on the backend |
invalid_grant on the code exchange |
The code expired or was already used, the wrong redirect_uri, or the code_verifier does not match the code_challenge (PKCE mismatch) |
Exchange the code with the same code_verifier that arrived in onSuccess; the code is one-time; redirect_uri is the same one passed to login |
No email in the profile, or email_verified: false |
A user without an email (wallet/Telegram sign-in) or an unverified email | Identify by sub, not by email; do not require it to be present and do not trust email_verified: false |