Idempotency
The POST and PATCH endpoints on prompts, tags, urls,
competitor-tags and brand-facts, and the POST on competitor-tags/{tag_id}/members, support
the optional Idempotency-Key header. When provided, the API caches a
successful response for 24 hours and returns the same response for identical
retries - even if the underlying operation has already completed. DELETE is
already idempotent by HTTP semantics and does not read the header.
Use idempotency keys to make your write operations safe to retry on network failures or timeouts without risking duplicate resource creation.
Header format
Idempotency-Key: <your-key>The key is passed through as an opaque string. UUIDs are the recommended format:
curl -X POST https://api.ziptie.ai/v1/projects/PROJECT_ID/prompts \
-H "Authorization: Bearer zt_live_<token>" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{"text": "What is the best AI monitoring tool?"}'Replay semantics
When an idempotency key is seen again:
| Scenario | Behaviour |
|---|---|
| First request, 200 or 201 | Normal - execute and cache |
| First request, 4xx or 5xx | Execute; nothing is cached |
| Retry with identical body | Return cached response, status code, and headers |
| Retry with different body | 409 IDEMPOTENCY_KEY_REUSED |
| Retry after 24h cache expiry | Normal - treat as a new request |
A replayed response includes the X-Idempotent-Replay: true header so you
can distinguish a cached response from a fresh one.
Only a successful response enters the cache, which matters most for the case
the header exists to cover. A 500 or 503 is never stored, so the retry that
follows it executes for real rather than replaying anything: the key protects
you from a lost response, not from a failed operation. On prompt creation the
duplicate check is what saves you there, so treat a 409
PROMPT_ALREADY_EXISTS on a retry as success and read existing_id from the
body. That check compares prompt text with case and internal spacing ignored
(see working with prompts), so it covers a retry
that rewrote the text as well as one that repeated it. existing_id can be
null even so, because naming the prompt takes a second read the service does
not retry; the 409 still means the prompt is there, so list the prompts to find
it rather than posting again.
A request that times out with no response at all is the case the key does cover, because the first attempt may well have returned a 201 you never saw.
Example: safe prompt creation with retry
import uuid
import requests
def create_prompt_safe(project_id, token, text, max_retries=3):
# Generate the key once; reuse it on every retry attempt.
key = str(uuid.uuid4())
url = f"https://api.ziptie.ai/v1/projects/{project_id}/prompts"
headers = {
"Authorization": f"Bearer {token}",
"Idempotency-Key": key,
"Content-Type": "application/json",
}
for attempt in range(max_retries):
try:
resp = requests.post(url, headers=headers, json={"text": text}, timeout=10)
if resp.status_code in (200, 201):
return resp.json()
if resp.status_code == 409:
# Conflict: either duplicate text or key reuse with different body.
return resp.json() # inspect "code" field
resp.raise_for_status()
except requests.Timeout:
if attempt == max_retries - 1:
raise
continue # retry with the same key
raise RuntimeError("Max retries exceeded")Key handling
The key is not validated or normalised. It is hashed together with the token id, HTTP method, and request path to form the cache entry, and is never logged or included in responses.
Scope
Twelve endpoints accept Idempotency-Key: POST and PATCH on prompts, tags,
urls, competitor-tags and brand-facts, POST on competitor-tags/{tag_id}/members,
and POST on prompts/bulk.
The prompt-suggestions accept and dismiss endpoints are POSTs
but do not read the header, so a retry there is not replayed from cache: once
the suggestion is resolved, a repeat call returns 409 ALREADY_RESOLVED
rather than the original response. GET and DELETE endpoints ignore the header
silently (DELETE is already idempotent by HTTP semantics).
Key uniqueness
Generate a new key for each logically distinct operation. Never reuse a key
from a previous session for a different action. If a key is reused with a
different request body, the API returns 409 IDEMPOTENCY_KEY_REUSED:
{"error": "Idempotency key reused with different body", "code": "IDEMPOTENCY_KEY_REUSED"}