> ## Documentation Index
> Fetch the complete documentation index at: https://docs.punchrescue.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Post a message to an emergency's conversation

> **Required permission:** `write`.

Accepts optional `attachments`. Each item carries its value under `src` (or the
accepted aliases `content` / `url` / `data`) and, optionally, `type` and
`caption`. A bare string, or a single item outside a list, is accepted too.

`src` takes one of two forms:

* **Inline bytes** — a `data:` URI or bare base64. Decoded, verified against
  their own magic bytes, and written to S3 *before* the message row, so a
  storage failure never leaves a message with a broken image in the chat. A
  declared `type` is ignored here: the bytes decide.
* **A link** — an `https://` URL. Stored as given and **never fetched**: the
  operator's browser loads it directly, so Rescue neither hosts nor guarantees
  it, and a URL that expires will stop rendering. `type` is the only type signal
  a link has; without it the type is guessed from the file extension and
  otherwise falls back to a plain link. Only https, a public host and the
  default port are accepted.

Rejections carry a stable code in `detail.error` (see `AttachmentRejectReason`).

Retries of a message whose attachments are *all inline bytes* are safe: an
identical payload re-posted inside the dedupe window returns the original message
instead of creating a second one. Text-only messages are not deduplicated —
repeated posts each create a message, as they always have. Neither are messages
carrying a link: a link is identified by its URL, and the media behind a URL can
change, so collapsing a re-post would hide a new frame rather than a duplicate.
Retry a link post only if you want a second message.

The conversation is created on demand, so a message posted immediately after the
emergency is declared is accepted rather than rejected with a retry.



## OpenAPI

````yaml https://api.rescue-serverless.dev.back2back.team/api/public/v1/openapi.json post /orgs/{org_id}/emergencies/{emergency_id}/messages
openapi: 3.1.0
info:
  title: Rescue Public API v1
  description: >-
    Stable public API for partner integrations.


    **Authentication:** `Authorization: Bearer rk_live_<8 hex><64 hex>`. Tokens
    are minted via `POST /api/v1/api-keys` (internal admin) and shown exactly
    once at creation. The same plaintext is required for every public-v1 request
    — store it securely.


    **Permissions:** v1 vocabulary is `read` | `write`. Future versions may
    introduce granular `<resource>:<action>` permissions additively.


    **Idempotency:** retries on a 5xx must be assumed to have unknown outcomes —
    check `GET /emergencies/{id}` (optionally via `externalId`) before retrying.
    An `Idempotency-Key` header will be supported in a future revision.


    **Webhook delivery:** payloads may be duplicated and out-of-order. Dedup on
    `eventId`; order events per emergency on `(emergencyId, sequence)`.
  version: 1.0.0
servers:
  - url: https://api.punchrescue.com/api/public/v1
security:
  - BearerAuth: []
paths:
  /orgs/{org_id}/emergencies/{emergency_id}/messages:
    post:
      tags:
        - Emergencies
      summary: Post a message to an emergency's conversation
      description: >-
        **Required permission:** `write`.


        Accepts optional `attachments`. Each item carries its value under `src`
        (or the

        accepted aliases `content` / `url` / `data`) and, optionally, `type` and

        `caption`. A bare string, or a single item outside a list, is accepted
        too.


        `src` takes one of two forms:


        * **Inline bytes** — a `data:` URI or bare base64. Decoded, verified
        against
          their own magic bytes, and written to S3 *before* the message row, so a
          storage failure never leaves a message with a broken image in the chat. A
          declared `type` is ignored here: the bytes decide.
        * **A link** — an `https://` URL. Stored as given and **never fetched**:
        the
          operator's browser loads it directly, so Rescue neither hosts nor guarantees
          it, and a URL that expires will stop rendering. `type` is the only type signal
          a link has; without it the type is guessed from the file extension and
          otherwise falls back to a plain link. Only https, a public host and the
          default port are accepted.

        Rejections carry a stable code in `detail.error` (see
        `AttachmentRejectReason`).


        Retries of a message whose attachments are *all inline bytes* are safe:
        an

        identical payload re-posted inside the dedupe window returns the
        original message

        instead of creating a second one. Text-only messages are not
        deduplicated —

        repeated posts each create a message, as they always have. Neither are
        messages

        carrying a link: a link is identified by its URL, and the media behind a
        URL can

        change, so collapsing a re-post would hide a new frame rather than a
        duplicate.

        Retry a link post only if you want a second message.


        The conversation is created on demand, so a message posted immediately
        after the

        emergency is declared is accepted rather than rejected with a retry.
      operationId: emergencies.messages.create
      parameters:
        - name: org_id
          in: path
          required: true
          schema:
            type: string
            description: Target organization id (from the route path)
            title: Org Id
          description: Target organization id (from the route path)
        - name: emergency_id
          in: path
          required: true
          schema:
            type: string
            description: Emergency id
            title: Emergency Id
          description: Emergency id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateEmergencyMessageRequest'
      responses:
        '201':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicMessageResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
components:
  schemas:
    CreateEmergencyMessageRequest:
      properties:
        content:
          anyOf:
            - type: string
              maxLength: 4000
            - type: 'null'
          title: Content
        author:
          anyOf:
            - $ref: '#/components/schemas/MessageAuthorInput'
            - type: 'null'
        attachments:
          items:
            $ref: '#/components/schemas/AttachmentInput'
          type: array
          maxItems: 5
          title: Attachments
          description: >-
            Attachments to post with the message. Accepts a list or a single
            item; each item is either an object with `src` (plus optional
            `type`, `caption`) or the bare value string. `src` takes a data:
            URI, bare base64, or an https:// URL. At most 5 per message.
            `attachment` is accepted as a singular alias for this field.
      additionalProperties: false
      type: object
      title: CreateEmergencyMessageRequest
    PublicMessageResponse:
      properties:
        messageId:
          type: string
          title: Messageid
        conversationId:
          type: string
          title: Conversationid
        content:
          type: string
          title: Content
        source:
          anyOf:
            - type: string
            - type: 'null'
          title: Source
        createdAt:
          anyOf:
            - type: string
            - type: 'null'
          title: Createdat
        attachments:
          items:
            $ref: '#/components/schemas/PublicAttachmentResponse'
          type: array
          title: Attachments
          description: Stored attachments; empty for text-only messages
      type: object
      required:
        - messageId
        - conversationId
        - content
      title: PublicMessageResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    MessageAuthorInput:
      properties:
        type:
          type: string
          enum:
            - agent
            - system
            - user
          title: Type
          description: Author kind; becomes the message source
        name:
          anyOf:
            - type: string
              maxLength: 128
            - type: 'null'
          title: Name
      additionalProperties: false
      type: object
      required:
        - type
      title: MessageAuthorInput
    AttachmentInput:
      properties:
        src:
          type: string
          minLength: 1
          title: Src
          description: 'Attachment value: a data: URI, bare base64, or an https:// URL'
        type:
          anyOf:
            - type: string
              maxLength: 255
            - type: 'null'
          title: Type
          description: >-
            Declared media type: either a full media type ('image/png',
            'application/vnd.apple.mpegurl') or a bare kind ('image' | 'video' |
            'hls_video' | 'audio' | 'file' | 'link'). Consulted only for
            `https://` attachments, which are never downloaded and so have no
            bytes to read it from; when omitted the type is guessed from the
            URL's file extension (an `.m3u8` becomes a live `hls_video`) and
            otherwise falls back to a plain link. Send `type: "file"` to force a
            download affordance. For inline base64 it is ignored — the type is
            always read from the magic bytes.
        caption:
          anyOf:
            - type: string
              maxLength: 512
            - type: 'null'
          title: Caption
          description: Optional text rendered under the media in chat
      type: object
      required:
        - src
      title: AttachmentInput
      description: >-
        One inline attachment. Only the value is required; everything else is
        optional.


        `src` accepts several names because the engine-side contract was still
        open when

        this shipped: `src` is canonical and the one documented, while
        `content`, `url`

        and `data` are honoured so the integration cannot be blocked by a naming
        choice.

        Aliases resolve first-match-wins in the declared order, so a payload
        carrying more

        than one is still deterministic.


        `type` carries the same set of aliases for the same reason as `src`: the
        engine

        spells it `mimeType`, the workflow catalog spells it `kind`, and neither
        should

        be the reason a snapshot fails to post.


        `extra="ignore"` rather than `forbid`: because `src` is required, a typo
        in the

        value key still fails loudly as a missing field, while a field the
        engine adds

        before we support it does not destroy an otherwise valid message.
    PublicAttachmentResponse:
      properties:
        attachmentId:
          type: string
          title: Attachmentid
        kind:
          type: string
          title: Kind
          description: >-
            image | video | hls_video | audio | file | link. `hls_video` is a
            live HLS stream (play with an HLS player, badge it live); `link` is
            a URL to open in a new tab, not download.
        mimeType:
          anyOf:
            - type: string
            - type: 'null'
          title: Mimetype
          description: >-
            Verified media type for stored bytes, read from the file's bytes;
            the caller's declared type for links
            (`application/vnd.apple.mpegurl` for HLS). Null for a bare link that
            declared no type.
        url:
          anyOf:
            - type: string
            - type: 'null'
          title: Url
          description: >-
            Absolute URL, ready to use as an <img src>. For stored media this is
            the CDN URL; for a link attachment it is the URL you supplied,
            unchanged. Null when no CDN domain is configured and one could not
            be assembled — skip the attachment rather than render it.
        source:
          anyOf:
            - type: string
            - type: 'null'
          title: Source
          description: >-
            How the attachment arrived: data_uri | base64 | url. `url` means
            Rescue does not host it.
        sizeBytes:
          anyOf:
            - type: integer
            - type: 'null'
          title: Sizebytes
          description: >-
            Stored size in bytes; null for link attachments, which are never
            downloaded
        caption:
          anyOf:
            - type: string
            - type: 'null'
          title: Caption
        width:
          anyOf:
            - type: integer
            - type: 'null'
          title: Width
          description: >-
            Pixel width, read from the file header. Null when the header could
            not be parsed.
        height:
          anyOf:
            - type: integer
            - type: 'null'
          title: Height
          description: >-
            Pixel height, read from the file header. Null when the header could
            not be parsed.
      type: object
      required:
        - attachmentId
        - kind
      title: PublicAttachmentResponse
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: rk_live_<8hex><64hex>

````