---
title: "Records the one presenter a Slideshow user chose - character, placement and voice."
method: PUT
path: "/api/script-assistant/sessions/{sessionId}/presenter"
tags: ["ScriptAssistant"]
---

# Records the one presenter a Slideshow user chose - character, placement and voice.

`PUT /api/script-assistant/sessions/{sessionId}/presenter`

## Request

- Base URL: `https://api.wearelearning.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.wearelearning.io/api/script-assistant/sessions/{sessionId}/presenter`
- Auth: OAuth 2
  - Implicit: `https://login.wearelearning.io/authorize?audience=https://api.wearelearning.io`

PUT and not PATCH: the whole choice replaces the previous one, the same way
`UpdateOutlineScene` replaces a scene's editable state. 404 also covers "this session
is not a slideshow session you may write a presenter to" only in the sense that the FEATURE
is off - a Context session that exists answers 422, because refusing it explicitly is the
point (see SetSlideshowPresenter's mode guard).
            
422 also covers the write the STORE refused - the session was deleted, or stopped being a
Slideshow, between the handler's read and the patch. One status for both because the
repository reports one bool for both, and 404 would be a claim about which.
            
GATED ON `DeckToSlideshow` AT THE ENDPOINT. The controller used to carry a
class-level `ScriptAssistant` gate as well (removed once that flag was fully rolled
out); `UnleashFeatureGateFilter` requires ALL gates on an endpoint to pass, so stacking
a controller gate back on top stays safe. Pinned by `UnleashFeatureGateFilterTests`.
            
It has to be here and not only in the handler. An action filter short-circuits before the
action body runs, so it precedes MediatR entirely; the handler's check runs INSIDE the
pipeline, after `ValidatorBehavior` has already rejected a malformed body with a 422
and run the validator's tenant and character lookups. With the flag off this endpoint must
not exist, and "does not exist" cannot be something a caller discovers only after passing
validation. The handler keeps its own check all the same - it is the backstop for any
non-HTTP caller of the command, and the only one a handler unit test can see.

## Path parameters

- `sessionId` string, required

## Request body

- SetSlideshowPresenterRequestDto: PUT replace: the whole choice is applied verbatim and replaces any earlier one. There is no partial update - a placement without a character, or a voice for somebody else's character, is not a state anything downstream could act on.
  - `characterId` string, required
  - `position` SetSlideshowPresenterPositionDto: A position as a REQUEST may state it: every coordinate optional, so an omitted one is absent rather than zero.
    - `x` number, double, nullable: Required. Absent is refused, not read as 0.
    - `y` number, double, nullable: Required. Absent is refused, not read as 0.
    - `z` number, double, nullable: Required. Absent is refused, not read as 0.
  - `rotation` number, float, nullable: Required, and nullable here for the same reason WeAreLearning.R2D2.Api.DTO.ScriptAssistant.SetSlideshowPresenterRequestDto.Position is. As a plain `float` an omitted `rotation` bound to 0f and was written as a real 0-degree rotation, so a malformed or stale client silently reoriented the presenter. Absent and zero have to be different answers; the validator refuses the first and accepts the second. Must be in [0, 360) - out of range is refused, not wrapped.
  - `voiceName` string, nullable: Omit entirely for "no voice chosen". An empty string is not the same answer and is refused, and so is any name the voice catalogue does not know - see the validator for why existence is checked here while locale fitness is not. Matched case-insensitively, and STORED in the catalogue's own spelling - the response echoes back what was persisted, which may differ in case from what was sent.
  - `lighting` SlideshowPresenterLightingDto: The presenter's lighting, in the units of a node's lighting override: `TimeOfDay` is the light's angle as a fraction of a turn, and the colour's channels and brightness are in [0, 1]. Components are nullable for the REQUEST's sake - an omitted one is refused, not read as 0; a response always carries all of them.
    - `timeOfDay` number, float, nullable
    - `indoorLighting` SlideshowPresenterColorDto: The lighting's colour: the same wire shape as WeAreLearning.R2D2.Dto.Locations.ColorDto, WITHOUT its defaults.
      - `r` number, float, nullable
      - `g` number, float, nullable
      - `b` number, float, nullable
      - `brightness` number, float, nullable

## Response `200`

OK

- SlideshowPresenterDto: The presenter a Slideshow user chose: who presents, where they stand, and what they sound like.
  - `characterId` string, required
  - `position` ThreeDimensionalPositionDto, required
    - `x` number, double, required
    - `y` number, double, required
    - `z` number, double, required
  - `rotation` number, float, required: Degrees, positive clockwise. Always in [0, 360).
  - `voiceName` string, nullable: Null means no voice was chosen, which is a legal answer - it is resolved from the character's gender, the story language and the tenant's defaults when the story is built. It is NOT the same as an empty string, which the write refuses.
  - `lighting` SlideshowPresenterLightingDto: The presenter's lighting, in the units of a node's lighting override: `TimeOfDay` is the light's angle as a fraction of a turn, and the colour's channels and brightness are in [0, 1]. Components are nullable for the REQUEST's sake - an omitted one is refused, not read as 0; a response always carries all of them.
    - `timeOfDay` number, float, nullable
    - `indoorLighting` SlideshowPresenterColorDto: The lighting's colour: the same wire shape as WeAreLearning.R2D2.Dto.Locations.ColorDto, WITHOUT its defaults.
      - `r` number, float, nullable
      - `g` number, float, nullable
      - `b` number, float, nullable
      - `brightness` number, float, nullable

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `422`: Unprocessable Content

---

[API](https://skmtc.dev/wearelearning/apis/we-are-learning-api.md) - [All operations](https://skmtc.dev/wearelearning/apis/we-are-learning-api/llms.txt) - [OpenAPI document](https://skmtc.dev/wearelearning/apis/we-are-learning-api/revisions/e2ca5265db77?raw)
