SMX wraps failures in a shared response format. Integrations should branch on origin and code, then treat extra context as optional per-endpoint detail rather than a universal contract.
This is the generic envelope returned for failures. Optional keys are omitted when the backend has no additional context to send.
1{2 "success": false,3 "origin": "api",4 "codes": ["OTP_INVALID"],5 "message": "Invalid OTP code"6}origin tells you which layer produced the failure. The backend uses three origin values.
| Origin | Meaning | Typical use |
|---|---|---|
| api | A route-level business failure. | Invalid OTP, missing linked device, project disabled, or other contract-aware route errors. |
| validation | The request body or params failed validation before the business flow completed. | Malformed payloads, missing required fields, or invalid field shapes. |
| core | A deeper platform or framework-level failure surfaced through the shared response layer. | Unexpected runtime issues or infrastructure-adjacent failures that are not ordinary route decisions. |
Read the envelope in a fixed order so client handling stays predictable. These are response fields, not request payload fields.
Always false on an error response. It simply confirms you are looking at the failure branch of the shared envelope.
Tells you which layer produced the failure. The backend returns api, validation, or core.
One or more machine-readable error codes. Branch on these first because they are the stable integration contract.
Human-readable summary when the backend provides one. Useful for logs, analytics panels, and support tooling, but not ideal for control flow.
Structured extra context for specific cases only. Treat it as endpoint- and code-specific instead of assuming it exists on every error.
| Code | Typical status | Meaning |
|---|---|---|
| API_KEY_MISSING | 401 | No X-SMX-API-Key header was sent. |
| API_KEY_INVALID | 401 | The project API key is unknown or revoked. |
| PROJECT_DISABLED | 403 | The project exists but is not active for API traffic. |
| DEVICE_NOT_LINKED | 400 | The project does not currently have an active linked Android device. |
| OTP_RATE_LIMITED | 400 | The receiver is still inside the resend cooldown window. |
| OTP_INVALID | 400 | The OTP code does not match the verification challenge. |
| OTP_EXPIRED | 400 | The verification challenge expired before verification completed. |
| OTP_ATTEMPTS_EXCEEDED | 400 | The challenge consumed all verify attempts and is now failed. |
| MESSAGE_NOT_FOUND | 400 | The supplied verificationId does not resolve for this project. |
| VALIDATION_FAILED | 400 | The request body did not satisfy the route contract. |
A small amount of branching discipline keeps the client behavior clean.
| Situation | Recommended handling |
|---|---|
| API_KEY_MISSING, API_KEY_INVALID | Stop retrying automatically and surface an integration or configuration issue to the operator. |
| DEVICE_NOT_LINKED | Tell the user or operator that the project currently has no active linked Android sender. |
| OTP_INVALID | Prompt the user to try again. Verification-specific attempt context is documented on the Verify OTP page, not assumed globally here. |
| OTP_EXPIRED, OTP_ATTEMPTS_EXCEEDED | Start a new verification flow instead of reusing the old challenge. |
| VALIDATION_FAILED | Fix the payload shape before retrying. |