Concepts
Idempotency
Two tools keep writes safe: the Idempotency-Key header, which makes any write safe to retry, and dry_run, which reports what a write would do without committing it.
The Idempotency-Key header
A network timeout on a write leaves you not knowing whether it landed — without idempotency you must choose between losing the write and doing it twice. The header removes the dilemma: send a fresh unique value (a UUID is ideal) per logical operation, and reuse that same value on every retry of it.
curl -X POST "https://api.mailneo.co/api/v2/subscribers" \
-H "x-api-key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9f6c2e58-52a3-4a0f-9c1d-1b7f37e3c001" \
-d '{ "email": "ada@example.com" }'
# The same command again — timeout, crash, retry loop — replays the
# original response with Idempotent-Replayed: true, and writes nothing.How a replay is decided
- The key is fingerprinted together with the method, path, parameters and body. Same key and same request within 24 hours replays the original response, marked with
Idempotent-Replayed: true. - Same key with a different request is
409 idempotency_key_reused— that mismatch is a bug in the caller, not something to guess at. - Same key while the first attempt is still running is
409 request_in_flight, withRetry-Aftertelling you when to try again. A stalled attempt is reclaimed after 90 seconds. - Keys are scoped to your team and may be up to 255 characters.
Where the key is required
On most writes the header is optional — strongly recommended, but your risk to take. Where a repeat is not merely wasteful but harmful, the spec marks it required and a request without it is refused with 400 idempotency_key_required:
/contacts/bulk— a re-run bulk upsert would double-apply every row./campaigns/{campaignId}/send-preview— a retried preview must not mint a second confirmation token./campaigns/{campaignId}/send— a retried confirm must replay, never arm twice./newsletters/{newsletterId}/send-preview— a retried preview must not mint a second confirmation token./newsletters/{newsletterId}/send— a retried confirm must replay, never arm twice./webhooks/{webhookId}/deliveries/{deliveryId}/replay— a retried replay that already ran must not deliver twice.The header has no effect on GET and DELETE, which are already safe to repeat.
Dry runs with dry_run
Every write endpoint accepts ?dry_run=true: the request is validated and resolved exactly as a real one, but nothing is committed. The response carries data: null and a meta.impact block describing what would have happened.
curl -X DELETE "https://api.mailneo.co/api/v2/lists/$LIST_ID?dry_run=true" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": null,
"meta": {
"request_id": "req_...",
"impact": {
"object": "impact",
"would_create": 0,
"would_update": 0,
"would_delete": 1,
"affected_count": 1,
"affected_ids": ["..."],
"quota": null
}
}
}would_create/would_update/would_delete— row counts per kind of change;affected_countandaffected_idsidentify the records involved.quota— when the write would consume plan quota (contact slots, email credits), the kind, the amount, and where the team stands against the limit.nullwhen no quota is involved.dry_runmust be exactlytrueorfalse, and it requires the resource's:readscope in addition to the endpoint's own — a dry run reveals state, so it needs permission to read it.
send does not accept dry_run, because send-preview is the dry run — and unlike a dry run its answer is enforced, through the confirmation token. See sending.