Stimuli
Upload images and videos to a STIMULI project and assign them to interview questions.
Stimuli are media assets — images or videos — shown to participants alongside specific interview questions. They are only available on projects created with type: "STIMULI".
All stimuli endpoints accept a project_id. The project_id is returned when you create a project.
To inspect uploaded assets at any time, use GET /v1/stimuli — it returns every stimuli record for a project including its ID, image/video URL, caption, display order, and current question assignments.
The upload workflow is:
- Presign — request a short-lived S3 upload URL for each file.
- Upload — POST the file directly to S3 using the presigned URL.
- Confirm — register the uploaded file with Persona to create the stimuli record.
- List — call
GET /v1/stimulito retrievestimuli_idvalues for your uploaded assets. - Assign — link each asset to the questions it should appear alongside.
All stimuli must have at least one question assigned before the project can be launched.
Text-only stimulus questions
A stimulus doesn't have to carry media. To add a plain question with no image or video — for example a standalone question between two media stimuli — call Assign without a stimuli_id. Persona creates the stimulus record for you and returns its stimuli_id. Skip the presign, upload, and confirm steps entirely.
This is also how you edit questions on a STIMULI project — PATCH /v1/projects/{project_id} rejects questions for stimuli projects.
List stimuli
Returns all uploaded stimuli for a project, ordered by display position. Use this to retrieve stimuli_id values for the assign endpoint.
Authentication
x-api-key headerQuery Parameters
| Name | Type | Description |
|---|---|---|
project_id* | string | The project UUID. |
Headers
| Name | Type | Description |
|---|---|---|
x-api-key | string | — |
Responses
| Field | Type | Description |
|---|---|---|
itemsrequired | object[] | — |
↳ stimuli_idrequired | string | Stimuli Id |
↳ display_orderrequired | integer | Display Order |
↳ image_urlrequired | string | Image Url |
↳ video_urlrequired | string | Video Url |
↳ captionrequired | string | — |
↳ question_itemsrequired | string[] | Question Items |
totalrequired | integer | — |
Example request
curl "https://external-api.prod.insightscompany.io/v1/stimuli?project_id=<project_id>" \
-H "x-api-key: <api_key>"Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
project_id | UUID | Yes | The project UUID |
Example response
{
"items": [
{
"stimuli_id": "a1b2c3d4-...",
"display_order": 1,
"image_url": "https://cdn.example.com/stimuli/...",
"video_url": null,
"caption": "Homepage redesign concept",
"question_items": [
{ "question": "What do you notice first?", "probing_level": "low", "instructions": null },
{ "question": "How does this make you feel?", "probing_level": "medium", "instructions": null }
]
}
],
"total": 1
}| Field | Description |
|---|---|
stimuli_id | Use this in the Assign endpoint |
display_order | The order assets are shown to participants |
question_items | Questions this asset is currently assigned to. Each is { question, probing_level, instructions } — probing_level is low unless set explicitly; instructions is null unless set |
Presign an upload
Returns a presigned S3 POST URL and form fields for a direct upload. Call Confirm after the upload completes.
Authentication
x-api-key headerHeaders
| Name | Type | Description |
|---|---|---|
x-api-key | string | — |
Request Bodyrequired
| Field | Type | Description |
|---|---|---|
project_idrequired | string | The project UUID. |
filenamerequired | string | Original filename including extension. |
content_typerequired | string | MIME type of the file. |
file_size_bytes | integer | File size in bytes, used for pre-validation. |
file_id | string | Optional pre-assigned UUID for the stimuli asset. |
Responses
| Field | Type | Description |
|---|---|---|
upload_urlrequired | string | Upload Url |
upload_fieldsrequired | object | Upload Fields |
file_idrequired | string | File Id |
s3_keyrequired | string | S3 Key |
media_urlrequired | string | Media Url |
expires_inrequired | integer | Expires In |
content_typerequired | string | Content Type |
Example request
curl -X POST "https://external-api.prod.insightscompany.io/v1/stimuli/presign" \
-H "x-api-key: <api_key>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "<project_id>",
"filename": "homepage-concept.png",
"content_type": "image/png"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
project_id | UUID | Yes | The project UUID |
filename | string | Yes | Original filename including extension |
content_type | string | Yes | MIME type of the file (e.g. image/png, video/mp4) |
file_size_bytes | integer | No | File size in bytes — used for pre-validation before the upload |
file_id | UUID | No | Pre-assigned UUID for the stimuli asset. A new UUID is generated if omitted |
Example response
// request sent image/png — the API stores it as webp
{
"upload_url": "https://s3.amazonaws.com/...",
"upload_fields": {
"Content-Type": "image/webp",
"key": "stimuli/prod/<interview_id>/<file_id>.webp",
"...": "..."
},
"file_id": "a1b2c3d4-...",
"s3_key": "stimuli/prod/<interview_id>/<file_id>.webp",
"media_url": "https://cdn.example.com/stimuli/<file_id>.webp",
"expires_in": 3600,
"content_type": "image/webp"
}| Field | Description |
|---|---|
upload_url | The S3 endpoint to POST the file to |
upload_fields | Form fields that must be included in the multipart upload, before the file |
file_id | Save this — required in the Confirm call |
s3_key | The final object key. Its extension is the stored format — see the note below |
media_url | The CDN URL the stored asset will be served from |
content_type | The stored MIME type — may differ from what you sent |
expires_in | Seconds until the presigned URL expires |
JPEG and PNG uploads are stored as WebP. The API transcodes .jpg / .jpeg / .png to .webp (.gif, .webp, and .mp4 are kept as-is). The presign response's s3_key, content_type, and media_url reflect the stored format. When you call Confirm, file_ext must be the extension from the presign s3_key (e.g. .webp), not your original filename's extension — otherwise confirm returns 404 "Uploaded file not found in storage".
Uploading to S3
POST the file to upload_url as multipart/form-data. Include every entry from upload_fields as a form field first (in the order returned), then the file as the last field named file:
curl -X POST "<upload_url>" \
-F "Content-Type=<upload_fields.Content-Type>" \
-F "key=<upload_fields.key>" \
-F "policy=<upload_fields.policy>" \
-F "x-amz-algorithm=<...>" \
-F "x-amz-credential=<...>" \
-F "x-amz-date=<...>" \
-F "x-amz-security-token=<...>" \
-F "x-amz-signature=<...>" \
-F "file=@homepage-concept.png"The file field carries your original bytes; the object is stored under the resolved key from s3_key (e.g. .webp for a JPEG/PNG upload). A successful upload returns HTTP 204 No Content. Then call Confirm.
Confirm an upload
Verifies the file exists in storage and creates the stimuli record.
Authentication
x-api-key headerHeaders
| Name | Type | Description |
|---|---|---|
x-api-key | string | — |
Request Bodyrequired
| Field | Type | Description |
|---|---|---|
project_idrequired | string | The project UUID. |
file_idrequired | string | The file_id returned from the presign step. |
file_extrequired | string | The file extension (e.g. .webp, .mp4). |
caption | string | Optional caption shown below the media. |
Responses
| Field | Type | Description |
|---|---|---|
messagerequired | string | — |
file_idrequired | string | File Id |
media_urlrequired | string | Media Url |
Example request
curl -X POST "https://external-api.prod.insightscompany.io/v1/stimuli/confirm" \
-H "x-api-key: <api_key>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "<project_id>",
"file_id": "<file_id>",
"file_ext": ".webp",
"caption": "Homepage redesign concept"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
project_id | UUID | Yes | The project UUID |
file_id | UUID | Yes | The file_id returned from the presign step |
file_ext | string | Yes | The stored file extension, including the leading dot — take it from the presign response's s3_key (a JPEG/PNG upload becomes .webp). Using your original filename's extension here returns 404 "Uploaded file not found in storage" |
caption | string | No | Caption shown below the media. Can also be set or updated later via the Assign endpoint |
Example response
{
"message": "Upload finalized",
"file_id": "a1b2c3d4-...",
"media_url": "https://cdn.example.com/stimuli/..."
}Assign stimuli to questions
Sets which questions a stimuli asset appears alongside during the interview. Pass an empty question_items list to clear all assignments.
Called without a stimuli_id, it instead creates a new text-only stimulus question (no media) and returns its stimuli_id. Only valid on STIMULI projects.
Authentication
x-api-key headerHeaders
| Name | Type | Description |
|---|---|---|
x-api-key | string | — |
Request Bodyrequired
| Field | Type | Description |
|---|---|---|
project_idrequired | string | The project UUID. |
stimuli_idrequired | string | The stimuli UUID to assign. |
question_itemsrequired | string[] | Question texts this stimuli should appear alongside. Use exact strings from the interview. |
caption | string | Optional caption shown below the media. Send null to clear an existing caption. |
Responses
| Field | Type | Description |
|---|---|---|
stimuli_idrequired | string | Stimuli Id |
question_itemsrequired | string[] | Question Items |
captionrequired | string | — |
Example request
curl -X POST "https://external-api.prod.insightscompany.io/v1/stimuli/assign" \
-H "x-api-key: <api_key>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "<project_id>",
"stimuli_id": "<stimuli_id>",
"question_items": [
"What do you notice first?",
{ "question": "How does this make you feel?", "probing_level": "high" }
],
"caption": "Homepage redesign concept"
}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
project_id | UUID | Yes | The project UUID |
stimuli_id | UUID | No | The stimuli UUID to update — from the confirm response or the list endpoint. Omit to create a new text-only stimulus question (no media) |
question_items | (string | object)[] | Yes | Questions this asset should appear alongside. Each entry is a plain question string, or an object { question, probing_level, instructions }. Question text is trimmed and blank entries are dropped. Pass an empty array to clear all assignments (ignored when creating) |
question_items[].question | string | Yes | The exact question text shown to participants |
question_items[].probing_level | string | No | low, medium, or high (case-insensitive) — how hard the AI interviewer probes on that question. Defaults to low (no follow-ups). A bare string entry is stored as low. See probing levels |
question_items[].instructions | string | No | Private interviewer guidance for this question — how to probe, what to listen for. Never shown to participants. Omitted from storage when not set |
caption | string | No | Caption shown below the media. Omit to leave an existing caption unchanged |
Example response
question_items is always returned in object form, with probing_level resolved and instructions as null when unset:
{
"stimuli_id": "a1b2c3d4-...",
"question_items": [
{ "question": "What do you notice first?", "probing_level": "low", "instructions": null },
{ "question": "How does this make you feel?", "probing_level": "high", "instructions": "Probe for the emotional reaction, not the design critique." }
],
"caption": "Homepage redesign concept"
}Create a text-only question
Omit stimuli_id to add a standalone question with no media. The new stimulus is appended after existing ones and its stimuli_id is returned — use that same ID with this endpoint later to edit the question text or clear it.
curl -X POST "https://external-api.prod.insightscompany.io/v1/stimuli/assign" \
-H "x-api-key: <api_key>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "<project_id>",
"question_items": ["Before we look at anything — what matters most to you here?"]
}'{
"stimuli_id": "e5f6a7b8-...",
"question_items": [
{ "question": "Before we look at anything — what matters most to you here?", "probing_level": "low", "instructions": null }
],
"caption": null
}Errors
| Status | code | Cause |
|---|---|---|
404 | not_found | The project, or the given stimuli_id, was not found for your organisation |
409 | survey_aware_guide_required | The stimulus holds survey questions — edit it through the survey workflow |
409 | not_a_stimuli_project | Tried to create a text-only question (no stimuli_id) on a non-STIMULI project |
400 | empty_question_items | Tried to create a text-only question with an empty question_items list |
Notes
- Supported image formats: PNG, JPEG, GIF, WebP. Maximum 10 MB. PNG and JPEG are stored as WebP — the presign response's
s3_key/content_type/media_urlreflect the stored format, andfile_exton Confirm must match it. GIF and WebP are stored as-is. - Supported video format: MP4. Maximum 100 MB.
- The
project_idis theidfield returned when you create a project.