Idempotency
A request that times out leaves you unsure whether it ran. Idempotency lets you retry a write and get the original result back instead of a duplicate. The API offers it in two places, and it matters which one you are using.
Intents: idempotency_key in the body
Human-approved actions are proposed through POST /intents (see Intents). The request body accepts an optional idempotency_key:
curl -X POST "https://api.wefunder.com/intents" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action_name": "comments.create",
"resource_type": "Company",
"resource_id": "co_9Xk2mP4qR7sT1uV3wY5zA8bC",
"params": { "target_type": "company", "target": "co_9Xk2mP4qR7sT1uV3wY5zA8bC", "body": "What is your current monthly revenue?" },
"idempotency_key": "ask-revenue-co_9Xk2mP4qR7sT1uV3wY5zA8bC-2026-09"
}'
- Same key, same operation (action, resource, params): the existing intent is returned with
200as long as it ispending,approved,executing, orexecuted. No second intent is minted and the call does not count toward the intent rate limit. - Same key, different operation:
409withtype: "idempotency_conflict". Nothing is created. - If the earlier intent is
rejected,expired, orfailed, the key is released and a new intent is created.
The key is scoped to your application and the authorizing user, so two apps can use the same string without colliding.
On Wefunder-Version: 2026-10-01 only (both were removed in 2026-10-15; closing a deal happens on wefunder.com), the deal lifecycle endpoints (POST /syndicates/{id}/deals/{fundraise_id}/close and /finalize) are idempotent without a key: a repeated call while an intent for that deal is still pending returns the same pending intent.
Partner endpoints: the Idempotency-Key header
The header-based scheme below is honored by the Partner API write endpoints: drafting a syndicate deal and invite links (invite-only preview), and investment sessions (beta). Public endpoints outside the intent system do not read this header today.
Send an Idempotency-Key header on the write. Keys are scoped to your application and user and live for 24 hours.
curl -X POST "https://api.wefunder.com/syndicates/syn_abc123/deals" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Idempotency-Key: acme-series-a-spv-create" \
-H "Content-Type: application/json" \
-d '{ ... }'
| Situation | Result |
|---|---|
| First request with the key | Runs normally; the created resource is recorded against the key. |
| Retry, same path and same parameters | 200 with the originally created resource. Nothing new is created. |
| Retry, same key but a different path or parameters | 422 with type: "idempotency_conflict". |
| The original request failed | Nothing is recorded; a retry runs the write again. Failures are never cached. |
| No header | The request runs with no deduplication. |
Two concurrent requests with the same key are serialized on the server; the loser receives the winner's result.
Choosing keys
Derive the key from the operation you intend, so a retry produces the same key and a new operation produces a new one.
// Good: one key per intended operation
const key = `spv-create-${deal.id}-v${deal.version}`;
const key = `remove-${memberId}-${today}`;
// Bad: a fresh UUID per attempt defeats the purpose
const key = crypto.randomUUID();
// Bad: the same key for different operations conflicts on the second use
const key = "create-spv";
Keep keys under 255 characters and log them alongside the request so a support conversation can find the original call.
Naturally idempotent methods
GET, PUT, and DELETE are safe to repeat; a second DELETE on an already-removed resource returns 404 or 409 rather than doing anything. POST creates, and PATCH may not be idempotent when it appends. Use a key on those.