Errors and retries
A call to answer a text ends one of three ways:
| Status | Body | Saved |
|---|---|---|
200 |
The result, status: "ok" |
Yes |
502 |
The result, status: "failed" |
Yes |
4xx or 500 |
An error body with a code and a message |
No |
503 |
Service Unavailable, with no code, while a server restarts |
No |
A 502 is not an error body. It is the failed result, in the format you asked for, with answers: null and an error in words:
{ "id": "0192f5c3-2b4e-7d10-8a61-5e0f9c7d2a13", "ref": "TCK-8812", "prism": "support-routing", "version": 3, "status": "failed", "error": "The model took too long to answer, so we stopped after 15 seconds.", "took_ms": 15012, "created_at": "2026-09-23T09:15:40.502Z", "answers": null}Every other failure has the error body, in JSON whatever format you asked for:
{ "error": { "code": "validation_failed", "message": "Missing fields: subject" }}The errors reference lists every status, code and message.
What to retry
Section titled “What to retry”| Status | Retry? | Why |
|---|---|---|
400 |
No | The request is wrong. Fix it first. |
401 |
No | The key is missing, wrong or revoked. |
404 |
No | The prism, version or result does not exist in this account, or is archived. |
409 |
No | The idempotency key or the retry conflicts. Read the message. |
413, 415 |
No | The body is too large or not JSON. Fix it first. |
500 |
Yes | A bug on our side. Retry a few times with a pause, then stop. |
502 |
Yes | The model failed. The result is saved and can be retried. |
503 |
Yes | A server is restarting. Nothing was saved or sent to the model. |
| Timeout or network error | Yes | You can't tell whether the result was saved, so retry with the same key. |
Retry a failed result
Section titled “Retry a failed result”POST /v1/results/{result_id}/retry
The retry endpoint sends a failed result's stored fields to the model again and saves the answer as a new result.
curl -X POST https://api.prismlet.com/v1/results/0192f5c3-2b4e-7d10-8a61-5e0f9c7d2a13/retry \ -H "Authorization: Bearer $PRISMLET_API_KEY"const response = await fetch( 'https://api.prismlet.com/v1/results/0192f5c3-2b4e-7d10-8a61-5e0f9c7d2a13/retry', { method: 'POST', headers: { Authorization: `Bearer ${process.env.PRISMLET_API_KEY}` }, signal: AbortSignal.timeout(45_000), },);const result = await response.json();import os
import requests
response = requests.post( "https://api.prismlet.com/v1/results/0192f5c3-2b4e-7d10-8a61-5e0f9c7d2a13/retry", headers={"Authorization": f"Bearer {os.environ['PRISMLET_API_KEY']}"}, timeout=45,)result = response.json(){ "id": "0192f5c4-9e27-7c55-b3d8-41a6f0e8c9b2", "ref": "TCK-8812", "prism": "support-routing", "version": 3, "status": "ok", "error": null, "took_ms": 640, "created_at": "2026-09-23T09:16:05.931Z", "answers": { "team": "billing", "team_probability": 0.91, "urgent": "yes", "tone": "annoyed", "tone_average": 2.05 }}- The response is the new result, with its own
id, on200or502. - It runs against the exact version and fields of the failed result, even when the prism has newer versions.
- Only a failed result can be retried. Retrying one that succeeded answers
409withOnly a failed result can be retried. - A failed result can be retried once. A second retry answers
409withThis result was already retried as <id>., naming the new result. Read that one, and if it failed too, retry it in turn. - A result of an archived prism can't be retried. It answers
404withThis prism is archived, so it cannot answer. - An idempotency key held by the failed result moves to the new one.
- Send no body. A
Content-Type: application/jsonheader with an empty body answers400withMalformed request.
Idempotency keys
Section titled “Idempotency keys”A timeout on your side doesn't tell you whether Prismlet saved the result. Send an idempotency_key and you can repeat the call and still get one result for it.
{ "ref": "TCK-8812", "fields": { "subject": "Charged twice this month", "text": "Hi, I was billed twice for September. Please refund the second charge before Friday." }, "idempotency_key": "tck-8812-1"}What a repeat with the same key returns depends on what the key already holds:
| The key holds | The repeat |
|---|---|
| Nothing | Answers the text as a normal call. |
| A result that succeeded | Returns that result with 200 without asking the model. fields, version and ref in the new body are ignored. |
| A result that failed | Retries it, as the retry endpoint does, and returns the new result on 200 or 502. The key moves to the new result. |
| A result of another prism | Answers 409 with This idempotency key was already used for another prism. |
- A repeat of a failed result retries the fields stored with it, not the fields in your new body. If you want a changed text answered, use a new key.
- Because the key moves on every retry, repeating a call after a
502until it answers200is safe, and the key ends up on one successful result. - Two calls with the same key at the same moment both get the one stored result, though both may be sent to the model.
- Keys are unique across the whole account and shared with the app, whose file runs use keys of their own. Derive yours from your record, such as
tck-8812-1, and change the suffix when you want the record answered again. - The prism is still checked first, so a repeat against an archived prism answers
404. The body is still validated, so a repeat with a malformed body answers400.
A call you can repeat safely
Section titled “A call you can repeat safely”The JavaScript and Python versions send a call with an idempotency key and repeat it on a timeout, a network error, a 500 or a 502, waiting 1, 2 and then 4 seconds. Every other status comes straight back. The body must carry an idempotency_key, or a repeat can answer the text twice.
curl https://api.prismlet.com/v1/prisms/support-routing/results \ --fail --max-time 45 --retry 3 --retry-connrefused \ -H "Authorization: Bearer $PRISMLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ref": "TCK-8812", "fields": { "subject": "Charged twice this month", "text": "Hi, I was billed twice for September. Please refund the second charge before Friday." }, "idempotency_key": "tck-8812-1" }'--retry repeats after a timeout, a 500 or a 502, among others, and doubles its wait each time from one second. With --fail, curl prints only the successful attempt's body, and for a 4xx or a last 5xx it prints the status on stderr instead. It does not repeat a connection that drops in the middle of a call. --retry-all-errors, in curl 7.71 or later, repeats that too, but it also repeats every 4xx.
const API = 'https://api.prismlet.com/v1';const RETRYABLE = new Set([500, 502, 503]);
async function answer(prism, body, attempts = 4) { for (let attempt = 1; ; attempt += 1) { try { const response = await fetch(`${API}/prisms/${prism}/results`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.PRISMLET_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), signal: AbortSignal.timeout(45_000), }); if (!RETRYABLE.has(response.status) || attempt === attempts) { return { status: response.status, result: await response.json() }; } } catch (error) { // fetch throws on a network error or the timeout. if (attempt === attempts) throw error; } await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1))); }}
const { status, result } = await answer('support-routing', { ref: 'TCK-8812', fields: { subject: 'Charged twice this month', text: 'Hi, I was billed twice for September. Please refund the second charge before Friday.', }, idempotency_key: 'tck-8812-1',});console.log(status, result);import osimport time
import requests
API = "https://api.prismlet.com/v1"RETRYABLE = {500, 502, 503}
def answer(prism, body, attempts=4): for attempt in range(1, attempts + 1): try: response = requests.post( f"{API}/prisms/{prism}/results", headers={"Authorization": f"Bearer {os.environ['PRISMLET_API_KEY']}"}, json=body, timeout=45, ) if response.status_code not in RETRYABLE or attempt == attempts: return response.status_code, response.json() except requests.RequestException: # A network error or the timeout. if attempt == attempts: raise time.sleep(2 ** (attempt - 1))
status, result = answer( "support-routing", { "ref": "TCK-8812", "fields": { "subject": "Charged twice this month", "text": "Hi, I was billed twice for September. Please refund the second charge before Friday.", }, "idempotency_key": "tck-8812-1", },)print(status, result)After the last attempt you may still hold a 502. The failed result is saved, so run the same call later with the same key and it retries the result then.