Docs
Messaging

Cancel a message

POSThttps://api.norreach.app/v1/messages/{id}/cancel

Cancels a message asked for through the API (the id POST /messages or a batch answered) while it hasn't gone out: still waiting in Approvals, or approved and waiting for its send_at. Nothing is sent; the approval leaves the queue; it is audited, and the message.canceled webhook event fires. Once the engine has picked it up to send (within about a minute of it being due and approved) it can't be cancelled any more → 409 conflict. Skipped in Approvals → 409 already_decided. Already cancelled → 200 with changed: false.

⚠ messages:sendRole must allow: inbox.replySupports dry_runOperation id: cancel_message
POST/messages/{id}/cancel
curl -X POST "https://api.norreach.app/v1/messages/{id}/cancel" \
  -H "Authorization: Bearer $NORREACH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
Cancel a message
{
  "object": "send_request",
  "id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
  "kind": "message",
  "channel": "email",
  "contact_id": "7d1c3b2a-5e4f-4a1b-9c8d-0e1f2a3b4c5d",
  "state": "canceled",
  "reason": null,
  "created_at": "2026-11-04T09:12:33.120Z",
  "scheduled_for": "2026-11-20T08:00:00.000Z",
  "changed": true,
  "dry_run": false,
  "note": "Cancelled — nothing will be sent."
}

Authorizations

Authorizationstringheaderrequired

Bearer authentication: send Authorization: Bearer <key>, with an API key or an OAuth access token. The key needs the scope messages:send. API keys and scopes

Path parameters

idstring (uuid)pathrequired

An id (UUID).

Query parameters

dry_runbooleanquery

Run every check and return what would happen, changing nothing.

Headers

Idempotency-Keystringheader

Any unique string (≤ 255). The same key with the same request returns the first answer for 24 hours.

Response

200application/json— Cancel a message

object"send_request"required
idstring (uuid)required

An id (UUID).

kind"message"required
channelstring | nullrequired
contact_idstring (uuid) | nullrequired
stateenumrequired

canceled — or, on a dry run, where it stands now (would.state is what cancelling does).

One of:canceledawaiting_approvalscheduledqueued
reasonnullrequired
created_atstringrequired

RFC 3339 UTC timestamp with milliseconds.

scheduled_forstring | nullrequired
changedbooleanrequired

false: it was already cancelled (or a dry run).

dry_runbooleanrequired
notestringrequired
wouldobject
Show child attributes
would.state"canceled"required

Errors

Errors are application/problem+json with a stable code.

StatusCodeMeaning
400bad_requestThe request couldn't be read.
400unknown_fieldThe request has a field this operation doesn't know.
401unauthorizedA valid API key is required.
403insufficient_scopeThis API key doesn't have the scope this operation needs.
403feature_not_enabledThe API isn't switched on for this workspace.
403role_forbiddenThe key's creator's role can't do this.
404not_foundThat doesn't exist.
409idempotency_in_progressA request with this Idempotency-Key is still running.
409conflictThat conflicts with the current state.
409already_decidedAlready handled.
413too_largeThe request body is too large.
422validation_failedThe request has invalid fields.
422idempotency_mismatchThis Idempotency-Key was used with a different request.
429rate_limitedToo many requests. Slow down and try again.
500server_errorSomething went wrong on our side.
503unavailableThe API isn't available right now. Try again shortly.