← API reference

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"}