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

# Send a message

> Sends a fan's message to your team, as text, a voice note or a photo. Your team reviews every message, and other fans never see it. Send a new Idempotency-Key with each message, so a retry never sends it twice.



## OpenAPI

````yaml /experiences/api-reference/experiences.openapi.yaml post /fan-voices/{fan_voice_id}/messages
openapi: 3.1.0
info:
  title: 'Dizplai API: Experiences'
  version: 1.0.0
  summary: Read polls, Fan Voices and collections, and let fans vote and send messages.
  description: >
    Show polls and Fan Voices in your own apps and sites, and let fans take
    part: read experiences and collections, send

    fans' votes and messages, and read back a fan's own vote and messages.


    **Headers.** Send `x-api-key`, your organisation's API key, on every
    request. When a fan takes part, or reads

    their own vote or messages, add their token from your own login as
    `Authorization: Bearer <token>`.


    **Names.** An experience is one thing fans take part in: a poll or a Fan
    Voice. A collection groups experiences for a

    match or show, and an experience can be in many collections. Every ID is a
    UUID: store it in a UUID column or as

    text, and never parse it.


    **Conventions.** snake_case fields; `after` and `limit` paging (50 by
    default, 100 at most) with `{items, next}`;

    errors are RFC 9457 problems with a stable `code` and a `request_id`, and
    every response carries `x-request-id`.

    Types, statuses and codes can gain values within v1, so ignore one you don't
    know.


    **Refreshing.** Read an open poll every 10 seconds and lists every 30
    seconds, and wait as long as `Retry-After`

    says after a `429` or `503`. The Experiences guide has the details.
  contact:
    name: Dizplai
servers:
  - url: https://api.dizplai.com/v1
    description: The Dizplai API.
security:
  - apiKey: []
tags:
  - name: Experiences
    description: >-
      Everything fans can take part in, in one list: build a match or show
      screen from it, drawing each experience by its type.
  - name: Collections
    description: >-
      Groups of experiences, one for each match or show, to organise your
      screens. An experience can be in many collections, in its own order in
      each.
  - name: Polls
    description: >-
      A poll asks fans one question with a short list of answers. Read polls and
      their results, send a fan's vote, and read back the vote they made.
  - name: Fan Voices
    description: >-
      A Fan Voice asks fans to reply in their own words, with text, a voice note
      or a photo. Read a Fan Voice, send a fan's message, and show the fan what
      happened to it. Fans never see each other's messages.
paths:
  /fan-voices/{fan_voice_id}/messages:
    parameters:
      - name: fan_voice_id
        in: path
        required: true
        description: The Fan Voice's ID, from a list of experiences or Fan Voices.
        schema:
          $ref: '#/components/schemas/FanVoiceId'
    post:
      tags:
        - Fan Voices
      summary: Send a message
      description: >-
        Sends a fan's message to your team, as text, a voice note or a photo.
        Your team reviews every message, and other fans never see it. Send a new
        Idempotency-Key with each message, so a retry never sends it twice.
      operationId: sendFanVoiceMessage
      parameters:
        - $ref: '#/components/parameters/MessageIdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FanVoiceMessageRequest'
            examples:
              text:
                $ref: '#/components/examples/FanVoiceMessageRequest'
              voice:
                $ref: '#/components/examples/FanVoiceVoiceMessageRequest'
              image:
                $ref: '#/components/examples/FanVoiceImageMessageRequest'
      responses:
        '202':
          description: >-
            Sent, waiting for review. Sending isn't approval: show the fan what
            happens to it with List the fan's messages. It counts towards the
            fan's limit for its kind, approved or not.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FanVoiceMessageAccepted'
              examples:
                example:
                  $ref: '#/components/examples/FanVoiceMessageAccepted'
        '400':
          description: >-
            The message is invalid or has no Idempotency-Key (invalid_request);
            the Fan Voice isn't live (not_open) or doesn't take this kind
            (media_not_accepted); or the upload_id is unknown, already used or
            another fan's, or the file was never uploaded or its type or size
            differs from what you asked for (invalid_upload).
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                invalid_request:
                  summary: invalid_request
                  value:
                    type: https://api.dizplai.com/problems/invalid-request
                    code: invalid_request
                    title: Invalid request
                    status: 400
                    detail: Idempotency-Key is required.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                not_open:
                  summary: not_open
                  value:
                    type: https://api.dizplai.com/problems/not-open
                    code: not_open
                    title: Not open
                    status: 400
                    detail: 'This Fan Voice isn''t taking messages: it has closed.'
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                media_not_accepted:
                  summary: media_not_accepted
                  value:
                    type: https://api.dizplai.com/problems/media-not-accepted
                    code: media_not_accepted
                    title: Media not accepted
                    status: 400
                    detail: This Fan Voice takes text only.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                invalid_upload:
                  summary: invalid_upload
                  value:
                    type: https://api.dizplai.com/problems/invalid-upload
                    code: invalid_upload
                    title: Invalid upload
                    status: 400
                    detail: This upload has already been used in a message.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
        '401':
          $ref: '#/components/responses/FanUnauthorised'
        '403':
          description: >-
            A guest on a Fan Voice that needs a signed-in fan, or a guest's
            voice note or photo (sign_in_required); a fan blocked from this Fan
            Voice (fan_blocked); or a fan who has used up this Fan Voice's
            messages of this kind (message_limit_reached). Retrying won't help.
          headers:
            x-request-id:
              $ref: '#/components/headers/RequestId'
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                sign_in_required:
                  summary: sign_in_required
                  value:
                    type: https://api.dizplai.com/problems/sign-in-required
                    code: sign_in_required
                    title: Sign-in required
                    status: 403
                    detail: This Fan Voice needs a signed-in fan.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                fan_blocked:
                  summary: fan_blocked
                  value:
                    type: https://api.dizplai.com/problems/fan-blocked
                    code: fan_blocked
                    title: Fan blocked
                    status: 403
                    detail: This fan can't send messages to this Fan Voice.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
                message_limit_reached:
                  summary: message_limit_reached
                  value:
                    type: https://api.dizplai.com/problems/message-limit-reached
                    code: message_limit_reached
                    title: Message limit reached
                    status: 403
                    detail: This fan has sent 3 of 3 text messages to this Fan Voice.
                    request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
        '404':
          $ref: '#/components/responses/FanVoiceNotFound'
        '409':
          $ref: '#/components/responses/IdempotencyConflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          $ref: '#/components/responses/MessagesUnavailable'
      security:
        - apiKey: []
          fanToken: []
components:
  schemas:
    FanVoiceId:
      type: string
      description: A Fan Voice's ID, a UUID.
      format: uuid
      examples:
        - 0192fa7b-a1a5-7ae6-8820-782ce84e0f49
    FanVoiceMessageRequest:
      description: >-
        The fan's message: text, a voice note or a photo, as the Fan Voice's
        kinds allow. For a voice note or photo, upload the file first with `POST
        /fan-voices/{fan_voice_id}/uploads`, then name it here by its upload_id.
      oneOf:
        - title: Text
          type: object
          required:
            - text
          additionalProperties: false
          properties:
            kind:
              type: string
              enum:
                - text
              default: text
            text:
              type: string
              minLength: 1
              maxLength: 500
              description: The message, up to the Fan Voice's limits.max_text_length.
            on_air_consent:
              type: boolean
              default: false
              description: >-
                True only if the fan ticked a separate box, unticked by default,
                agreeing to their message being used on air. Without it, only
                your team sees it.
        - title: Voice note
          type: object
          required:
            - kind
            - upload_id
          additionalProperties: false
          properties:
            kind:
              type: string
              enum:
                - voice
            upload_id:
              $ref: '#/components/schemas/UploadId'
            on_air_consent:
              type: boolean
              default: false
              description: >-
                True only if the fan ticked a separate box, unticked by default,
                agreeing to their message being used on air. Without it, only
                your team sees it.
        - title: Photo
          type: object
          required:
            - kind
            - upload_id
          additionalProperties: false
          properties:
            kind:
              type: string
              enum:
                - image
            upload_id:
              $ref: '#/components/schemas/UploadId'
            text:
              type: string
              minLength: 1
              maxLength: 500
              description: An optional caption.
            on_air_consent:
              type: boolean
              default: false
              description: >-
                True only if the fan ticked a separate box, unticked by default,
                agreeing to their message being used on air. Without it, only
                your team sees it.
    FanVoiceMessageAccepted:
      type: object
      required:
        - message_id
        - status
      description: The message was received, and waits for review.
      properties:
        message_id:
          $ref: '#/components/schemas/MessageId'
        status:
          type: string
          enum:
            - sent
          description: >-
            Received, waiting for your team to review it. Sending isn't
            approval.
    Problem:
      type: object
      required:
        - type
        - code
        - title
        - status
        - request_id
      description: >-
        What went wrong, in the same shape for every error: an RFC 9457 problem
        with a stable code. Act on code, which never changes meaning within a
        version, and on the HTTP status for a code you don't know. Quote
        request_id to support.
      properties:
        type:
          type: string
          format: uri-reference
          description: >-
            One URL per code: https://api.dizplai.com/problems/ followed by the
            code with hyphens, such as
            https://api.dizplai.com/problems/unknown-option for unknown_option.
        code:
          type: string
          x-extensible-enum:
            - invalid_request
            - unknown_option
            - not_open
            - media_not_accepted
            - invalid_upload
            - invalid_api_key
            - invalid_fan_token
            - forbidden
            - fan_blocked
            - sign_in_required
            - message_limit_reached
            - not_found
            - idempotency_conflict
            - rate_limited
            - unavailable
          description: >
            More codes may be added: for one you don't know, act on the HTTP
            status.


            | code | Status | Retry? | Meaning |

            |---|---|---|---|

            | invalid_request | 400 | No | The body or a parameter is invalid,
            or a required header such as Idempotency-Key is missing. |

            | unknown_option | 400 | No; read the poll again | An option ID the
            poll doesn't have. |

            | not_open | 400 | No; read the experience again | The experience
            isn't taking votes or messages: it isn't live yet, or votes have
            stopped. |

            | media_not_accepted | 400 | No | The Fan Voice doesn't take this
            kind of message, or the file's type or size is outside the limits. |

            | invalid_upload | 400 | No; upload again | The upload_id is
            unknown, already used or another fan's, or the file was never
            uploaded or its type or size differs from what you asked for. |

            | invalid_api_key | 401 | No | A missing or invalid x-api-key. |

            | invalid_fan_token | 401 | Once, with a fresh token | A missing,
            invalid or expired fan token, or one from another organisation's
            login. It carries WWW-Authenticate. |

            | forbidden | 403 | No | Your API key isn't allowed to make this
            request. |

            | fan_blocked | 403 | No | The fan is blocked from this Fan Voice. |

            | sign_in_required | 403 | No; ask the fan to sign in | A guest on
            an experience that needs a signed-in fan, or a guest's voice note or
            photo. |

            | message_limit_reached | 403 | No | The fan has used up this Fan
            Voice's messages of this kind. |

            | not_found | 404 | No | Not found, or not yours. |

            | idempotency_conflict | 409 | No | An Idempotency-Key reused with a
            different body while it's kept: 24 hours, or 5 minutes for an
            upload. |

            | rate_limited | 429 | After Retry-After | Too many requests. |

            | unavailable | 503 | After Retry-After | Votes or messages can't be
            taken right now. |
        title:
          type: string
          description: A short title for the code, such as Not open. It doesn't change.
        status:
          type: integer
          description: The HTTP status code.
          minimum: 400
          maximum: 599
        detail:
          type: string
          description: >-
            What went wrong, in plain English. It can change, so don't act on
            it.
        request_id:
          type: string
          description: Matches the x-request-id response header.
        errors:
          type: array
          description: Field-level validation errors, with invalid_request.
          items:
            type: object
            required:
              - pointer
              - message
            properties:
              pointer:
                type: string
                description: A JSON Pointer to the field.
              message:
                type: string
                description: What's wrong with that field.
    UploadId:
      type: string
      description: >-
        A fan's voice note or photo, uploaded for a Fan Voice message. Created
        when the fan asks to upload, used in one message only, and deleted if no
        message uses it within an hour.
      format: uuid
      examples:
        - 0192fa8a-9346-78b7-9664-3cb0e5caf78d
    MessageId:
      type: string
      description: A Fan Voice message's ID, a UUID.
      format: uuid
  parameters:
    MessageIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        format: uuid
      example: 3f2b8c1e-7a4d-4c9e-9b61-2d5e8f0a7c34
      description: >-
        Required. A new UUID for each message, whether text, a voice note or a
        photo. Keys are kept for each fan for 24 hours: the same key and body
        from the same fan answers as the first time, so a retry never sends a
        message twice; with a different body, 409 (idempotency_conflict).
  examples:
    FanVoiceMessageRequest:
      summary: Text
      value:
        kind: text
        text: Brilliant second half. Keep pressing!
        on_air_consent: true
    FanVoiceVoiceMessageRequest:
      summary: A voice note
      value:
        kind: voice
        upload_id: 0192fa8a-9346-78b7-9664-3cb0e5caf78d
        on_air_consent: true
    FanVoiceImageMessageRequest:
      summary: A photo with a caption
      value:
        kind: image
        upload_id: 0192fa8a-9767-7740-8ae4-50627fccf611
        text: Our end before kick-off
        on_air_consent: false
    FanVoiceMessageAccepted:
      value:
        message_id: 0192fa8a-0ae4-728d-a8ba-cafc119ba65f
        status: sent
  headers:
    RequestId:
      description: The request's ID. Quote it to support.
      schema:
        type: string
    WwwAuthenticate:
      description: >-
        With invalid_fan_token only: Bearer error="invalid_token", with an
        error_description.
      schema:
        type: string
    RetryAfter:
      description: >-
        How long to wait before retrying, as a whole number of seconds. Sent
        with every 429 and 503.
      schema:
        type: integer
        minimum: 0
  responses:
    FanUnauthorised:
      description: >-
        A missing or invalid x-api-key (invalid_api_key), or a missing, invalid
        or expired fan token, or one from another organisation's login
        (invalid_fan_token). Only invalid_fan_token carries WWW-Authenticate:
        refresh the fan's token and retry once.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        WWW-Authenticate:
          $ref: '#/components/headers/WwwAuthenticate'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            invalid_fan_token:
              summary: invalid_fan_token
              value:
                type: https://api.dizplai.com/problems/invalid-fan-token
                code: invalid_fan_token
                title: Invalid fan token
                status: 401
                detail: The fan's token has expired.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
            invalid_api_key:
              summary: invalid_api_key
              value:
                type: https://api.dizplai.com/problems/invalid-api-key
                code: invalid_api_key
                title: Invalid API key
                status: 401
                detail: The x-api-key isn't recognised.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    FanVoiceNotFound:
      description: >-
        The Fan Voice doesn't exist, isn't yours, or was taken down or deleted
        (not_found).
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            not_found:
              summary: not_found
              value:
                type: https://api.dizplai.com/problems/not-found
                code: not_found
                title: Not found
                status: 404
                detail: No such Fan Voice.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    IdempotencyConflict:
      description: >-
        The Idempotency-Key was used with a different body while it was kept, 24
        hours or 5 minutes for an upload (idempotency_conflict).
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            idempotency_conflict:
              summary: idempotency_conflict
              value:
                type: https://api.dizplai.com/problems/idempotency-conflict
                code: idempotency_conflict
                title: Idempotency conflict
                status: 409
                detail: This Idempotency-Key was used with a different body.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    TooManyRequests:
      description: >-
        Too many requests (rate_limited). Wait the number of seconds in
        Retry-After, then retry.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            rate_limited:
              summary: rate_limited
              value:
                type: https://api.dizplai.com/problems/rate-limited
                code: rate_limited
                title: Rate limited
                status: 429
                detail: Too many requests. Retry after 2 seconds.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
    MessagesUnavailable:
      description: >-
        Messages can't be taken right now (unavailable). Wait the number of
        seconds in Retry-After, then retry.
      headers:
        x-request-id:
          $ref: '#/components/headers/RequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            unavailable:
              summary: unavailable
              value:
                type: https://api.dizplai.com/problems/unavailable
                code: unavailable
                title: Unavailable
                status: 503
                detail: Messages can't be taken right now. Retry after 5 seconds.
                request_id: 01JBX8P2R4T6V8X0Z2B4D6F8H0
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Your organisation's API key, supplied by Dizplai. Send it on every
        request. It isn't a secret, so it can ship in your app: what a fan does
        is protected by the fan's token.
    fanToken:
      type: http
      scheme: bearer
      description: >-
        The fan's token from your own login, sent as `Authorization: Bearer
        <token>`: a signed-in fan's or a guest's. Dizplai checks it against your
        login's details, agreed when you're set up, and a guest's token carries
        the guest claim agreed at the same time. Needed to vote, upload, send a
        message, and read the fan's own vote or messages.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.