Errors
The API uses ordinary HTTP status codes. A response in the 200s means success. Anything else, including 401 and 500, carries a JSON body that explains what went wrong.
Error format
{
"error": {
"code": "validation_failed",
"message": "Must be one of: reply, note.",
"fields": { "type": "Must be one of: reply, note." },
"request_id": "req_3f9c0a1b22d4e5f6"
}
}
| Field | Meaning |
|---|---|
code |
A short, stable word for the problem. Branch on this, not on the message. |
message |
A sentence for people. It can change, so do not match on it. |
fields |
Present on validation errors. Maps each field to what is wrong with it. |
request_id |
Identifies the request. It is also in the X-Request-Id response header. Include it when you write to support. |
Status codes
| Status | Code | What it means |
|---|---|---|
| 400 | invalid_json |
The body is not valid JSON. |
| 401 | missing_api_key |
There is no Authorization header. |
| 401 | invalid_api_key |
The key is unknown, revoked or expired. A WWW-Authenticate: Bearer header is sent. |
| 403 | insufficient_scope |
The key is valid but lacks the scope. The message names it. See permissions. |
| 404 | not_found |
No such endpoint or record. Ids from another workspace also give this. |
| 404 | module_disabled |
The workspace has switched that module off. |
| 405 | method_not_allowed |
The path exists but not for this method. The Allow header lists the methods that work. |
| 409 | conflict |
A duplicate slug, or the conversation already has a feedback post. |
| 413 | payload_too_large |
The body is over 256 KB. |
| 422 | validation_failed |
A query parameter or body field is wrong. See fields. Unknown body fields are rejected. Unknown query parameters are ignored. |
| 429 | rate_limited |
Too many requests, or the AI agent is busy. Wait for the time in Retry-After. See rate limits. |
| 500 | internal_error |
Something broke on our side. Retry with backoff. |
| 502 | upstream_error |
The AI backend could not answer an ask request. Retry with backoff. |
Handling errors
Retry only what can succeed on a second try: 429, 500 and 502. Do not retry other 4xx errors, because the same request will fail the same way.
JavaScript
async function tirver(path, init = {}) {
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(`https://app.tirver.com/api/v1${path}`, {
...init,
headers: { Authorization: `Bearer ${process.env.TIRVER_API_KEY}`, 'Content-Type': 'application/json', ...init.headers },
});
if (res.ok) return res.json();
const retryable = res.status === 429 || res.status >= 500;
if (!retryable || attempt === 4) {
const { error } = await res.json().catch(() => ({ error: { code: 'unknown', message: res.statusText } }));
throw Object.assign(new Error(error.message), { status: res.status, code: error.code, requestId: error.request_id });
}
const wait = Number(res.headers.get('Retry-After')) || 2 ** attempt;
await new Promise((r) => setTimeout(r, wait * 1000));
}
}
const open = await tirver('/conversations?status=open');
Python
import os, time, requests
def tirver(method, path, **kwargs):
for attempt in range(5):
res = requests.request(
method,
f"https://app.tirver.com/api/v1{path}",
headers={"Authorization": f"Bearer {os.environ['TIRVER_API_KEY']}"},
timeout=10,
**kwargs,
)
if res.ok:
return res.json()
retryable = res.status_code == 429 or res.status_code >= 500
if not retryable or attempt == 4:
res.raise_for_status()
time.sleep(float(res.headers.get("Retry-After", 2 ** attempt)))
open_conversations = tirver("GET", "/conversations", params={"status": "open"})
When you contact support about an error, include the request_id, the time of the request and the path. Never include your key.