Concepts
Idempotency
Networks drop requests. Send an Idempotency-Key with each check and you can retry as often as you like: the prompt is screened once, logged once and counted once.
How it works
Put a key of your own on POST /v1/check, one per prompt. Your order id or generation id is ideal.
curl -i https://api.getvindex.com/v1/check \
-H "Authorization: Bearer $VINDEX_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1234" \
-d '{"input": {"prompt": "a lighthouse at dusk, oil painting"}}'- The first request with a key screens the prompt and logs the decision.
- A repeat with the same key and the same body returns that first decision, unchanged, with the header
Idempotent-Replayed: true. No second screening, log entry, review item or webhook. - Two at once with the same key make one decision; both get it.
- The same key with a different body gets
422 idempotency_key_reused. The dry-run header counts as part of the body.
{
"type": "https://api.getvindex.com/problems/idempotency_key_reused",
"title": "Idempotency key reused",
"status": 422,
"detail": "This Idempotency-Key was already used with a different request body.",
"code": "idempotency_key_reused"
}Keys are 1 to 255 printable ASCII characters, scoped to your workspace, and remembered as long as the decision they made.
When to retry
Retry on 429, 500, 503, or when no response came back at all. Wait for Retry-After when there is one. Don't retry other 4xx errors: the same request will fail the same way.
// retry.mjs · Node 18+ · VINDEX_KEY=vx_test_... node retry.mjs
import { randomUUID } from "node:crypto";
async function check(prompt, idempotencyKey = randomUUID()) {
for (let attempt = 1; ; attempt++) {
let res;
try {
res = await fetch("https://api.getvindex.com/v1/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VINDEX_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({ input: { prompt } }),
signal: AbortSignal.timeout(10_000),
});
} catch (err) {
if (attempt >= 4) throw err; // no response at all: safe to retry with the same key
await new Promise((r) => setTimeout(r, attempt * 1000));
continue;
}
if (res.ok) return res.json();
const retryable = [429, 500, 503].includes(res.status);
if (!retryable || attempt >= 4) {
const problem = await res.json();
throw new Error(`${res.status} ${problem.code}: ${problem.detail ?? problem.title}`);
}
const wait = Number(res.headers.get("retry-after") ?? attempt);
await new Promise((r) => setTimeout(r, wait * 1000));
}
}
const decision = await check("a lighthouse at dusk, oil painting", "order-1234");
console.log(decision.outcome);# retry.py · pip install requests · VINDEX_KEY=vx_test_... python retry.py
import os
import time
import uuid
import requests
def check(prompt, idempotency_key=None):
idempotency_key = idempotency_key or str(uuid.uuid4())
for attempt in range(1, 5):
try:
res = requests.post(
"https://api.getvindex.com/v1/check",
headers={
"Authorization": f"Bearer {os.environ['VINDEX_KEY']}",
"Idempotency-Key": idempotency_key,
},
json={"input": {"prompt": prompt}},
timeout=10,
)
except requests.ConnectionError:
if attempt == 4:
raise
time.sleep(attempt) # no response at all: safe to retry with the same key
continue
if res.ok:
return res.json()
if res.status_code not in (429, 500, 503) or attempt == 4:
problem = res.json()
raise RuntimeError(f"{res.status_code} {problem.get('code')}: {problem.get('detail', problem['title'])}")
time.sleep(float(res.headers.get("Retry-After", attempt)))
decision = check("a lighthouse at dusk, oil painting", "order-1234")
print(decision["outcome"])