Edit a draft campaign
Parameters
Replays with the same key return the original result. **Key shape.** A non-empty string of at most 255 characters after trimming surrounding whitespace. An empty or oversized key is rejected with 422 `VALIDATION_ERROR` (`idempotency_key_empty` / `idempotency_key_too_long`) rather than silently running the request unprotected. Leading/trailing whitespace is trimmed, so `" k "` and `"k"` are the same key. **Detecting a replay.** A response served from the idempotency cache rather than by re-executing the operation carries the response header `Idempotency-Replayed: true`; a freshly executed response omits the header entirely. **Replay window.** A record is retained for 24 h by default, after which the same key is free again and the operation re-executes. Only a 2xx outcome is recorded — a request that failed can be retried with the same key. **Conflicts.** Reusing a key with a different request is `409 idempotency_key_conflict`. The request identity is the method, path, query string and body, except on `POST /transactional/send`, where only `domain_id` + `to` (case-insensitive) + `template_id` + `variables` + `send_at` are compared (the parsed instant normalised to ISO-8601, and omitted entirely rather than sent as `null` when the request carried no `send_at`); a replay of that operation does **not** carry `Idempotency-Replayed`. **Concurrency.** A key is claimed before the operation executes, so two requests carrying the same key can never both run. The second one is rejected with `409 idempotency_in_progress` while the first is still in flight. This is a *transient* conflict — distinct from `idempotency_key_conflict` above, which is permanent — so retry after a short delay: once the first request completes, the same key replays its result. A claim is released as soon as the first request fails or returns a non-2xx, and expires after 5 min if the process handling it dies, so a key can never be locked indefinitely. **Rate limiting.** A replay short-circuits before the rate limiter, so it consumes no quota and returns no `X-RateLimit-*` headers. This is deliberate: a client retrying after a timeout must not be penalised for work that was already done. **Scope.** Keys are scoped per tenant.
Request body
{
"name": "string",
"domain_id": "00000000-0000-0000-0000-000000000000",
"template_id": "00000000-0000-0000-0000-000000000000",
"audience": {
"include": [
{
"kind": "list",
"id": "00000000-0000-0000-0000-000000000000"
}
],
"exclude": [
{
"kind": "list",
"id": "00000000-0000-0000-0000-000000000000"
}
]
},
"schedule_at": "2024-01-01T00:00:00Z",
"sender_profile_id": "00000000-0000-0000-0000-000000000000",
"from_name": "string",
"reply_to": "user@example.com",
"throttle_per_hour": 0
}
Responses
Example response · 200
{
"status": "success",
"http_code": 0,
"payload": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "string",
"type": "blast",
"status": "draft",
"domain_id": "00000000-0000-0000-0000-000000000000",
"template_id": "00000000-0000-0000-0000-000000000000",
"template_version": 0,
"audience": {
"include": [
{
"kind": "list",
"id": "00000000-0000-0000-0000-000000000000"
}
],
"exclude": [
{
"kind": "list",
"id": "00000000-0000-0000-0000-000000000000"
}
]
},
"schedule_at": "2024-01-01T00:00:00Z",
"sender_profile_id": "00000000-0000-0000-0000-000000000000",
"from_name": "string",
"reply_to": "user@example.com",
"stats": {
"sent": 0,
"delivered": 0,
"opened": 0,
"clicked": 0,
"bounced": 0,
"complained": 0,
"failed": 0
},
"recipient_count": 0,
"throttle_per_hour": 0,
"launched_at": "2024-01-01T00:00:00Z",
"completed_at": "2024-01-01T00:00:00Z",
"pause_reason": "string",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z"
},
"meta": []
}
Example response · 409
{
"status": "error",
"http_code": 0,
"payload": [],
"meta": {
"reason": "VALIDATION_ERROR",
"validation_error": [
{
"field": "string",
"message": "string"
}
],
"details": []
}
}
Example response · 422
{
"status": "error",
"http_code": 0,
"payload": [],
"meta": {
"reason": "VALIDATION_ERROR",
"validation_error": [
{
"field": "string",
"message": "string"
}
],
"details": []
}
}