Best practices
Practices for a reliable Clipia API integration — idempotent retries, webhooks over polling, error and 429 handling with backoff, cost estimation, and storing request_id.
To make your Clipia API integration reliable and predictable on cost, follow five practices: make submissions idempotent, prefer webhooks over polling, handle errors and 429 with exponential backoff, estimate cost before submitting, and store the request_id on your side. Each practice is covered below.
Idempotent retries
Send an Idempotency-Key (a unique UUID v4 per logical request) on every submit. If the network drops, retry with the same key and the same parameters — you get the same request_id back, with no duplicate generation and no second charge. The key is stored for 24 hours.
curl -X POST https://api.clipia.ai/v1/models/nano-banana-2 \
-H "Authorization: Bearer clipia_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f3a1c7e-2b41-4d2a-9c0e-1200304cf45b" \
-d '{ "input": { "prompt": "a sunset over mountains, cinematic" } }'| Scenario | Result |
|---|---|
| same key + same parameters | the same request_id, no second charge |
| same key + different parameters | 409 idempotency_key_reuse |
| retry while the first is still processing | 409 request_in_progress |
One key per logical request
Generate a fresh UUID v4 for each new generation, not for each network retry. Never put sensitive data (an email and so on) in the key.
Webhooks over polling
Pass a webhook_url on submit — Clipia sends a POST when the job finishes, so you don't have to poll. Keep polling as a fallback. If you do poll, do it sensibly (say, once every 1–3 seconds), not in a tight loop.
- Respond
2xxwithin 10 seconds, or delivery is retried (up to 6 attempts with exponential backoff). - Verify the
X-Clipia-Signature(HMAC-SHA256) on every delivery. - Handle deliveries idempotently by
request_id— redelivery is possible.
Error and 429 handling with backoff
Errors arrive in a single JSON shape with error.type, error.code, and error.message. React by HTTP status.
| HTTP | code | What to do |
|---|---|---|
400 / 422 | invalid_request / model_input_invalid | fix the body/parameters; don't retry as-is |
401 / 403 | invalid_api_key / insufficient_scope | check the key and scope |
402 | insufficient_credits | top up the balance |
429 | rate_limit_exceeded | wait Retry-After seconds, then retry |
500 / 503 | internal_error / service_unavailable | retry with exponential backoff |
On 429, follow the Retry-After header and the RateLimit-* metrics:
RateLimit-Limit: 120
RateLimit-Remaining: 0
RateLimit-Reset: 37
Retry-After: 37Exponential backoff with jitter
For 429, 500, and 503, retry with a growing pause and random jitter (e.g. 1s, 2s, 4s, 8s… plus a random add-on) to avoid thundering herds. Default limits are 120 RPM and 10 concurrent generations per key.
Estimate cost before submitting
The operation price is fixed and known up front. To learn the cost of a specific parameter set without queuing, use POST /v1/models/{model}/estimate — it returns credits. This lets you show the cost to a user or reject expensive requests early. On submit the price is echoed in the cost field; if credits are short, submit returns 402.
Store the request_id
Persist each submission's request_id in your database right after the submit response. You need it to:
- match an incoming webhook to your entity;
- poll
status_url/response_urlwhen needed; - enforce idempotency on your side (don't process a delivery twice).
Pair request_id with Idempotency-Key
Store the pair Idempotency-Key → request_id: if the submit response is lost, a retry with the same key returns the same request_id, so you never create a duplicate generation.