---
title: "Get one message with its engagement"
method: GET
path: "/discord/messages/{message_id}"
tags: ["Discord"]
---

# Get one message with its engagement

`GET /discord/messages/{message_id}`

## Request

- Base URL: `https://api.reacherapp.com/public/v1`
- URL: `https://api.reacherapp.com/public/v1/discord/messages/{message_id}`
- Auth: API key in header `x-api-key` and API key in header `x-shop-id`

One scheduled or delivered message. For a recurring series' parent, `upcoming_occurrences` lists the next fire times. A message id that isn't this shop's reads as 404. Single shop only.

## Path parameters

- `message_id` integer, required: Message id from a list / create response.

## Response `200`

Successful Response

- DiscordMessageResponse
  - `data` DiscordMessage, required: A posted or scheduled message, with its engagement once delivered. A curated projection of the internal record — pipeline internals (delivery log, retry bookkeeping, rendered body, portal author identity) stay internal.
    - `id` integer, required
    - `channel_id` string, required
    - `channel_name` string, nullable
    - `title` string, nullable
    - `content` string, required
    - `mention` MentionTarget: Who the announcement pings. `role` requires an explicit role_id.
      - `type` 'none' | 'here' | 'everyone' | 'role'
      - `role_id` string, nullable
    - `campaign_id` integer, nullable
    - `attachments` DiscordAttachment[]
      - `filename` string, required
      - `content_type` string, required
      - `size_bytes` integer, required
    - `sender` SenderIdentity: Who the announcement appears to come from. The post is always made by the Reacher webhook; `member` only overrides the webhook's display name + avatar for that one message (the APP badge stays). `brand` = the shop name, which is also what a NULL sender means on the row. On a request only `user_id` is trusted: the controller resolves it against the linked guild and takes `display_name` / `avatar_url` from Discord, so a caller cannot persist a fabricated identity.
      - `type` 'brand' | 'member'
      - `user_id` string, nullable
      - `display_name` string, nullable
      - `avatar_url` string, nullable
    - `status` string, required: `scheduled`, `sending`, `sent`, `failed`, or `canceled`.
    - `scheduled_for` string, nullable
    - `timezone` string, required
    - `recurrence` Recurrence: The parsed ``recurrence`` JSONB column / API contract. Parsed ONCE at the query-layer boundary (``parse_recurrence``) so a malformed shape 400s at create time and can never blow up mid-dispatch. ``paused`` deliberately lives in its own column (the claim predicate must match the partial index literally), so extra keys are ignored rather than rejected — legacy rows carrying ``paused`` in the blob still parse.
      - `frequency` 'daily' | 'weekdays' | 'weekly' | 'biweekly' | 'monthly', required
      - `end_type` 'never' | 'on' | 'after'
      - `until` string, date, nullable
      - `occurrences` integer, nullable
    - `paused` boolean
    - `series_id` integer, nullable: Set on an occurrence of a recurring series.
    - `occurrence_num` integer, nullable
    - `occurrences_sent` integer
    - `discord_message_id` string, nullable
    - `message_url` string, nullable
    - `sent_at` string, nullable
    - `failure_reason` string, nullable
    - `failure_code` string, nullable
    - `audience_count` integer, nullable
    - `reaction_count` integer, nullable
    - `reply_count` integer, nullable
    - `link_click_count` integer, nullable
    - `upcoming_occurrences` string[], nullable: Next fire times, on a recurring series' detail read.
    - `created_at` string, nullable, required
    - `updated_at` string, nullable, required

## Other responses

- `422`: Validation Error

## Changes

> 76 revisions in range; 1 not diffed.

- **2026-09-19** `291230805105`: 4 breaking
  - response property `data/created_at` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - response property `data/updated_at` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - the `data/scheduled_for/anyOf[subschema #1]/` response's property format changed from `date-time` to no format for status `200`
  - the `data/sent_at/anyOf[subschema #1]/` response's property format changed from `date-time` to no format for status `200`
- **2026-09-10** `7dad11366a32`: 1 info
  - added the optional property `data/sender` to the response with the `200` status
- **2026-09-05** `643422c93bd8`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/reacherapp/apis/reacher-data-api/changes/discord/messages/:message_id/get.md)

---

[API](https://skmtc.dev/reacherapp/apis/reacher-data-api.md) - [All operations](https://skmtc.dev/reacherapp/apis/reacher-data-api/llms.txt) - [OpenAPI document](https://skmtc.dev/reacherapp/apis/reacher-data-api/revisions/63ce74b30b5a?raw)
