Reliability
Errors
Dishora uses conventional HTTP status codes and a consistent JSON error envelope. Validation errors return 422 with a per-field breakdown so you can surface them in your UI.
Status codes
| 200 | Success. The request completed. |
| 201 | Created. A resource was stored (e.g., a substitution). |
| 202 | Accepted. A background job was queued (weekly discovery). |
| 204 | Success with no content (delete). |
| 400 | Bad request. Malformed payload. |
| 401 | Unauthenticated. Missing or invalid key. |
| 402 | Payment required. Sandbox trial allowance used up. |
| 403 | Forbidden. Key lacks scope for this action. |
| 404 | Resource not found. |
| 409 | Conflict. The job is already running. |
| 413 | Payload too large. |
| 422 | Validation error. See per-field errors. |
| 429 | Rate limited. Retry after Retry-After. |
| 500 | Server error. Safe to retry. |
Error shapes
The envelope depends on where the request failed. Validation failures on the REST API return Laravel's structured errors object keyed by field, so client-side mapping is trivial:
422 validation (REST API)
{
"message": "The servings must be between 1 and 20. (and 1 more error)",
"errors": {
"servings": ["The servings must be between 1 and 20."],
"instruction_style": ["The selected instruction style is invalid."]
}
}A missing resource returns a plain error string:
404
{
"error": "Recipe not found."
}Rate-limit error (429)
429
// 429 Too Many Requests: back off using Retry-After
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 12 seconds.",
"retry_after": 12
}
}Retries and caching
Recipe generation is not idempotent: each fresh call may produce a new recipe. Identical requests for a specific named dish are served from the tiered cache, which makes retries of the same request cheap and consistent. For 429s, honor
Retry-After and back off with jitter.