Errors

View as Markdown

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.