---
audience: developer
summary: 'The event model in detail: content types, mentions, attachments, threading,
  edits, redactions.'
title: Send a message via the API
path: tutorials/developer/send-a-message-via-api
status: published
---

# Send a message via the API

The `POST /v1/rooms/{room_id}/send` endpoint is one call but the
event model behind it has a lot of structure. This page covers the
common shapes.

## The basic shape

```json
{
  "content": {
    "msgtype": "swp.text",
    "body": "Hello"
  }
}
```

`content` is a free-form JSON object; ScaiWave doesn't constrain
its shape, only its size (default 64 KB). `msgtype` is conventional;
clients render based on it.

## Standard `msgtype` values

| `msgtype` | Used for |
|---|---|
| `swp.text` | Plain markdown text. |
| `swp.code` | A code block with `lang` field. |
| `swp.media` | A media attachment. `content.media_id` + `content.thumbnail_id`. |
| `swp.system` | System message — joins, leaves, name changes. |
| `swp.audio_transcript` | Live transcription output. |
| `swp.todo_change` | Cross-window todo state broadcast. |

You can introduce custom `msgtype` values for app-specific
features; the client just ignores unknown types in the default
renderer.

## Mentions

To `@mention` a participant, include their participant id in a
mentions array:

```json
{
  "content": {
    "msgtype": "swp.text",
    "body": "Hi @alice, can you review?",
    "mentions": ["5e4d…"]
  }
}
```

The body text contains the human-readable form; the array gives the
server enough info to drive notifications. The client's composer
populates this automatically when you `@` someone.

## Threading

A reply-in-thread sets `reply_to_event_id`:

```json
{
  "content": {
    "msgtype": "swp.text",
    "body": "Yeah, I see the problem.",
    "reply_to_event_id": "evt-abc-…"
  }
}
```

Clients render threaded messages indented or in a separate
sidebar. The server doesn't enforce a tree — replies can fork in
multiple directions if multiple people reply to the same parent.

## Attachments

Two steps. First upload:

```bash
curl -X POST "$BASE/v1/media/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@./photo.jpg"
```

Returns `{data: {media_id, sha256, size, thumbnail_id}}`. Then send
a message referencing it:

```json
{
  "content": {
    "msgtype": "swp.media",
    "body": "Here's the screenshot",
    "media_id": "med-…",
    "thumbnail_id": "med-thumb-…"
  }
}
```

The client fetches the media via `GET /v1/media/download/{media_id}`.
Thumbnails are pre-generated for images and videos.

## Edits

Edit an event you sent:

```bash
curl -X POST "$BASE/v1/rooms/$ROOM_ID/events/$EVENT_ID/edit" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": {"msgtype": "swp.text", "body": "Corrected text"}}'
```

The original is preserved in `event.original_content`. Clients
typically show "edited" with a tooltip of the previous version.

You can only edit your own events. After a configurable window
(default 24h) edits are rejected.

## Redactions

Hard-delete an event (the body is wiped; the event id remains for
audit):

```bash
curl -X POST "$BASE/v1/rooms/$ROOM_ID/events/$EVENT_ID/redact" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Posted by mistake"}'
```

You can redact your own. Redacting someone else's requires
`power_level >= 50` (the `redact_other` action).

## Send-on-behalf (for bots)

A participant with `power_level >= 100` can send on behalf of
another participant (bots, scheduled jobs):

```json
{
  "content": { "msgtype": "swp.text", "body": "Daily standup reminder" },
  "as_participant_id": "<other-participant-id>"
}
```

The event's `sender_id` is the spoofed participant; `actor_id` is
the actual API caller (for audit).

## Idempotency

Pass a client-generated `event_uuid` for idempotency:

```json
{
  "content": { ... },
  "event_uuid": "<your-uuid-v4>"
}
```

If the server sees the same `event_uuid` from the same sender
within a 5-minute window, it returns the existing event instead of
creating a duplicate. Useful for retries on network failures.

## Rate limits

Default tenant config: 60 messages per minute per participant.
Hits return 429 with `Retry-After`.

## Where to go next

- [Subscribe to WebSocket events](/docs/scaiwave/tutorials/developer/subscribe-to-websocket-events) — see your message land.
- API: [Messages and reactions](/docs/scaiwave/reference/api/messages-and-reactions).
- Reference: [WebSocket events](/docs/scaiwave/reference/websocket-events).
