Errors
Every error returns JSON with a stable code and a human-readable message:
JSON
{"error": {"code": "insufficient_balance", "message": "Your balance is $0. Contact us to add funds."}}Branch on code, not on message; messages may change.
Status codes
| Status | Code | Meaning | Charged | What to do |
|---|---|---|---|---|
| 400 | invalid_json |
The body isn't a JSON object | No | Fix the body |
| 400 | unknown_model |
model isn't a Simo model |
No | Use simo-1-pro |
| 400 / 422 | varies | A question, media or option is invalid. The body names the bad field |
No | Read the error detail |
| 401 | unauthorized |
No Authorization header |
No | Send Bearer <key> |
| 401 | invalid_api_key |
The key is wrong or revoked | No | Check the key in the console |
| 402 | insufficient_balance |
Your balance is $0 | No | Add credit |
| 403 | account_suspended |
The account is suspended | No | Contact support |
| 403 | model_not_available |
The model isn't open yet | No | Use simo-1-pro |
| 413 | payload_too_large |
The body is over 25 MB | No | Send smaller or fewer images |
| 429 | Too many requests | No | Retry with backoff | |
| 502 | upstream_unavailable |
The model is briefly unavailable | No | Retry with backoff |
| 503 / 504 | The service is briefly unavailable | No | Retry with backoff |
Validation errors from the model (400 / 422) pass through with details naming the field, for example:
JSON
{"detail": [{"loc": ["body", "questions", "team", "criteria"], "msg": "...", "type": "..."}]}Retrying
Retry 429, 502, 503 and 504 with exponential backoff, honoring Retry-After when present. Don't retry 4xx errors other than 429: the same request will fail again. The Python SDK does this for you (3 retries by default).
JavaScript
async function ask(body, attempt = 0) {
const res = await fetch("https://api.temprl.org/v1/ask", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SIMO_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if ([429, 502, 503, 504].includes(res.status) && attempt < 3) {
const wait = Number(res.headers.get("retry-after")) * 1000 || 500 * 2 ** attempt;
await new Promise((r) => setTimeout(r, wait));
return ask(body, attempt + 1);
}
const data = await res.json();
if (!res.ok) throw Object.assign(new Error(data.error?.message ?? res.statusText), { status: res.status, data });
return data;
}SDK exceptions
Every failure raises a SimoError subclass. Each carries status, code, detail and request_id.
| Exception | When |
|---|---|
AuthError |
401: missing, wrong or revoked key |
InsufficientBalance |
402: no balance left (not charged) |
PermissionDenied |
403: account suspended, or the model isn't open to you |
InvalidRequest |
400 / 413 / 422: bad JSON or question, media not matching the state, over 25 MB |
ModelNotFound |
Unknown model |
QueueFull |
429, after the automatic retries |
Unavailable |
502 / 503 / 504, after the automatic retries (not charged) |
ServerError |
Any other 5xx |
ConnectionFailed |
The API is unreachable |
SimoTimeout |
No answer within timeout |
Python
from simoai import InsufficientBalance, InvalidRequest, SimoError
try:
r = c.ask(state, questions)
except InsufficientBalance:
top_up() # 402: not charged
except InvalidRequest as e:
print(e.code, e.detail) # names the bad field
except SimoError as e:
print(e.status, e.code, e, e.request_id)