Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.clevis.dev/llms.txt

Use this file to discover all available pages before exploring further.

POST /v1/payouts is idempotent. Retry the same logical request as many times as you need — the API will return the original payout instead of creating a new one.

How keys are picked

Two sources, in order:
  1. Idempotency-Key header, if you send one. Any opaque string is fine — a ULID, a UUID, or a deterministic key derived from your payroll run (e.g. payroll-co-2026-05-emp-001).
  2. external_id from the body, if no header is provided. Every payout already requires a unique external_id, so this gives you idempotency “for free” without managing a separate key.
Either way, the key is scoped per API key. Two different tenants can use the same external_id without colliding.

Replay semantics

ScenarioResponse
First call with key K, body B201 Created — payout created
Replay with key K, body B (identical)200 OK — same payout returned
Same key K, different body409 IDEMPOTENCY_KEY_REUSED
Same external_id (no idempotency header match)409 EXTERNAL_ID_CONFLICT
Note the status code: idempotent replays return 200, not 201. This lets your client tell “I just created this” from “I called this same request again”.
Derive an idempotency key from the payroll run that produced the net_salary you’re paying out. A good key is deterministic (so retries match) and unique per logical payout (so unrelated requests don’t collide).
curl -X POST https://api.clevis.dev/v1/payouts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: payroll-co-2026-05-emp-001" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "4600000.00",
    "currency": "COP",
    "country": "CO",
    "external_id": "payroll-co-2026-05-emp-001",
    "beneficiary": { ... }
  }'

What counts as “identical body”?

The whole JSON request body. If you change any field — amount, beneficiary details, metadata — and reuse the same Idempotency-Key, you get 409 IDEMPOTENCY_KEY_REUSED. That is intentional: it surfaces caller bugs (e.g. you re-derived the amount but kept the key) instead of silently paying out something different from the original intent.

Idempotency-Key vs. external_id

Idempotency-Keyexternal_id
Where it livesRequest headerRequest body
Required?OptionalRequired
Visible on the payout resourceNoYes
Used to dedupeYes (preferred)Yes (fallback)
Searchable via GET /v1/payouts?external_id=...NoYes
If you only ever set external_id (and skip the header), you still get full idempotency. The header exists for cases where you want to retry the same logical create with a fresh external_id — uncommon, but supported.

Idempotency window

Keys are remembered for the lifetime of the in-memory store. Since the v1 mock store is process-local and ephemeral, restarting the API drops all idempotency keys. Don’t rely on the mock for cross-process deduplication testing. The real dLocal-backed implementation will use a persistent window (matching dLocal’s own retention).