Idempotent Requests
All mutating POST endpoints require an Idempotency-Key header. This prevents duplicate resource creation when you retry failed requests.
How It Works
- You send a
POSTrequest with anIdempotency-Keyheader - The API processes the request and stores the result keyed by your idempotency key
- If you retry with the same key and same body, you get the original response back (no duplicate created)
- If you retry with the same key but different body, you get a
409 Conflict
curl -X POST https://api.firsthandapi.com/v1/jobs \
-H "Authorization: Bearer fh_live_..." \
-H "Idempotency-Key: job-create-order-1234" \
-H "Content-Type: application/json" \
-d '{
"type": "data_collection",
"description": "Photo of product packaging",
"files_needed": 10,
"accepted_formats": ["image/jpeg"],
"price_per_file_cents": 50
}'Generating Idempotency Keys
Use any unique string. Common patterns:
// UUID v4 (recommended for general use)
const key = crypto.randomUUID();
// Deterministic key (recommended for order-based workflows)
const key = `create-job-order-${orderId}`;
// Timestamp-based (for one-off operations)
const key = `job-${Date.now()}-${Math.random().toString(36).slice(2)}`;Endpoints Requiring Idempotency Keys
| Endpoint | Purpose |
|---|---|
POST /v1/jobs | Create a job |
POST /v1/jobs/:id/cancel | Cancel a job |
POST /v1/jobs/:id/rate | Rate a completed job |
POST /v1/api_keys | Create an API key |
POST /v1/webhook_endpoints | Create a webhook endpoint |
POST /v1/billing/purchase | Purchase credits |
GET, PATCH, and DELETE requests are naturally idempotent and don’t require the header.
Key Expiration
Idempotency keys expire after 24 hours. After expiration, the same key can be reused for a new request.
Error: Conflicting Idempotency Key
If you send the same idempotency key with a different request body:
{
"error": {
"type": "idempotency_conflict",
"message": "A different request was already made with this idempotency key",
"request_id": "req_01HV..."
}
}Status code: 409 Conflict
SDK Support
Both official SDKs generate idempotency keys automatically:
// TypeScript SDK — auto-generates idempotency keys
const job = await client.createJob({
type: 'data_collection',
description: 'Photo of storefront',
files_needed: 5,
accepted_formats: ['image/jpeg'],
price_per_file_cents: 40,
});
// Idempotency-Key header is set automaticallyTo pass your own key:
const job = await client.createJob(body, {
idempotencyKey: 'my-custom-key-123',
});