diff --git a/README.md b/README.md index 4c14a508..ea61abfa 100644 --- a/README.md +++ b/README.md @@ -394,20 +394,22 @@ polymorfa message send text \ --body 'Your order shipped.' --idempotency-key order-123-shipped ``` -For 24 hours after the first request, the API prevents the same key and request +For 1 hour after the first request, the API prevents the same key and request from running the write again: -- A recorded success returns `409 idempotency_completed` with - `Idempotent-Replayed: true`. The CLI reports an error; the response does not - contain the original message or response body. -- A recorded server error returns its status and error code with - `Idempotent-Replayed: true`. The write may have happened. +- A finished request returns its original status and body, success or error, + with `Idempotent-Replayed: true`. A send whose response was lost prints the + original message. +- A recorded error is returned again. Correct the request and use a new key. - A request still running returns `409 idempotency_in_progress` with `Retry-After`. An unrecorded outcome returns `409 idempotency_outcome_unknown` and blocks another attempt with that key. -- Reusing a key for a changed request returns `409 idempotency_conflict`. +- If the recorded response cannot be read right now, the API returns + `503 idempotency_replay_unavailable` with `Retry-After`. Retry with the same + key; the write does not run again. +- Reusing a key for a changed request returns `422 idempotency_conflict`. -Keep the first response when you receive it. If the outcome is uncertain, check +If the outcome is uncertain, check message events or webhooks before deciding whether to use a new key. A new key does not resolve the uncertainty and can send a duplicate. See the [Messaging retry contract](https://docs.polymorfa.com/guides/communicate/overview#retry-sends-safely). @@ -548,8 +550,8 @@ object with `name`, `language`, `category`, and `components` from `--input`, or from stdin when it is `-`. Template names and provider IDs remain strings. Edits read `{ "components": [...] }` and return acceptance, not approval to send. Deletion requires `--yes`. Create, edit and delete send one request and never -retry. Create and delete accept `--idempotency-key`; reuse the same key after an -unknown outcome and the API answers `idempotency_completed` instead of writing +retry. Create and delete accept `--idempotency-key`; reuse the same key after a +lost response and the API returns the original response instead of writing again. List the catalog before resubmitting. The template routes need an active Official API connection, including on a Hybrid Number; a Number with only a Linked Devices connection is rejected. @@ -1716,9 +1718,9 @@ instants. List returns one complete newest-first array with no cursor. Requeue is synchronous and reports only the number moved. Create, launch, pause, resume and stop accept `--idempotency-key` to deduplicate -the same request for 24 hours. A successful request repeated with the same key -returns `409 idempotency_completed`, not the original response. Changed input -returns `409 idempotency_conflict`; an in-progress or uncertain result must be +the same request for 1 hour. A request repeated with the same key returns the +original response, success or error. Changed input returns +`422 idempotency_conflict`; an in-progress or uncertain result must be resolved before trying a new key. The SDK generates a fresh key for create and launch when the flag is omitted. Supply an explicit key from the first attempt to reuse it across CLI invocations with the same input, credential and API diff --git a/docs/command-research/campaigns.md b/docs/command-research/campaigns.md index 7135187f..bb03d10b 100644 --- a/docs/command-research/campaigns.md +++ b/docs/command-research/campaigns.md @@ -99,8 +99,8 @@ server deduplication window. The middleware is mounted after authentication, rate limits and client rules, before the campaign routes under `/messaging`. Its identity includes the credential, project, method, route, API version and key; the fingerprint includes path parameters and the canonical request body. -A repeated successful request returns `409 idempotency_completed`, not the -original response body. Changed input returns `409 idempotency_conflict`; +A repeated request returns the original response, success or error. Changed +input returns `422 idempotency_conflict`; in-flight requests return `idempotency_in_progress`, and uncertain outcomes remain fenced as `idempotency_outcome_unknown`. Do not switch keys merely to bypass an uncertain result; inspect the campaign or operation first. The SDK diff --git a/src/commands/campaign/pause.ts b/src/commands/campaign/pause.ts index 232c3685..5396e315 100644 --- a/src/commands/campaign/pause.ts +++ b/src/commands/campaign/pause.ts @@ -37,7 +37,7 @@ function lifecycleFlags() { return { 'idempotency-key': Flags.string({ description: - 'Deduplicate this request for 24 hours. A completed key returns 409 idempotency_completed.', + 'Deduplicate this request for 1 hour. A repeat with the same key returns the original response.', }), 'project-slug': Flags.string({ description: 'Project slug in the Messaging API path.', diff --git a/src/commands/campaign/resume.ts b/src/commands/campaign/resume.ts index 1f01de72..9814f735 100644 --- a/src/commands/campaign/resume.ts +++ b/src/commands/campaign/resume.ts @@ -16,7 +16,7 @@ export default class CampaignResume extends BaseCommand { static override flags = { 'idempotency-key': Flags.string({ description: - 'Deduplicate this request for 24 hours. A completed key returns 409 idempotency_completed.', + 'Deduplicate this request for 1 hour. A repeat with the same key returns the original response.', }), 'project-slug': Flags.string({ description: 'Project slug in the Messaging API path.', diff --git a/src/commands/campaign/stop.ts b/src/commands/campaign/stop.ts index a9bfeb68..245db7c0 100644 --- a/src/commands/campaign/stop.ts +++ b/src/commands/campaign/stop.ts @@ -14,7 +14,7 @@ export default class CampaignStop extends BaseCommand { static override flags = { 'idempotency-key': Flags.string({ description: - 'Deduplicate this request for 24 hours. A completed key returns 409 idempotency_completed.', + 'Deduplicate this request for 1 hour. A repeat with the same key returns the original response.', }), 'project-slug': Flags.string({ description: 'Project slug in the Messaging API path.', diff --git a/src/commands/channel/message/react.ts b/src/commands/channel/message/react.ts index 1a85d3f3..8d725018 100644 --- a/src/commands/channel/message/react.ts +++ b/src/commands/channel/message/react.ts @@ -19,7 +19,7 @@ export default class ChannelMessageReact extends BaseCommand { static override flags = { 'idempotency-key': Flags.string({ description: - 'Key that makes retries of this write safe for 24 hours. Generated when omitted.', + 'Key that makes retries of this write safe for 1 hour. Generated when omitted.', }), reaction: Flags.string({ description: 'Reaction text, at most 32 UTF-8 bytes; empty removes it.', diff --git a/src/commands/cloud/template/create.ts b/src/commands/cloud/template/create.ts index 273a3d39..c71a2312 100644 --- a/src/commands/cloud/template/create.ts +++ b/src/commands/cloud/template/create.ts @@ -20,7 +20,7 @@ export default class CloudTemplateCreate extends BaseCommand { }), 'idempotency-key': Flags.string({ description: - 'Key that fences a manual retry: reuse it after an unknown outcome and the API returns idempotency_completed instead of writing again.', + 'Key that fences a manual retry: reuse it after a lost response and the API returns the original response instead of writing again.', }), } async run(): Promise { diff --git a/src/commands/cloud/template/delete.ts b/src/commands/cloud/template/delete.ts index d49a6020..3ac1e1e0 100644 --- a/src/commands/cloud/template/delete.ts +++ b/src/commands/cloud/template/delete.ts @@ -23,7 +23,7 @@ export default class CloudTemplateDelete extends BaseCommand { }), 'idempotency-key': Flags.string({ description: - 'Key that fences a manual retry: reuse it after an unknown outcome and the API returns idempotency_completed instead of writing again.', + 'Key that fences a manual retry: reuse it after a lost response and the API returns the original response instead of writing again.', }), } async run(): Promise {