{"openapi": "3.0.3", "info": {"title": "Outset Platform API", "version": "1.9.0", "description": "Partner-facing REST API for the Outset AI-moderated research platform. Authenticate with OAuth 2.1 (authorization code + PKCE); see `/.well-known/oauth-authorization-server` for the live endpoint discovery document.", "contact": {"name": "Outset Support", "url": "https://outset.ai", "email": "support@outset.ai"}}, "paths": {"/v2/interviews/{interview_id}/": {"get": {"operationId": "interviews_retrieve", "description": "``GET /v2/interviews/{interview_id}/`` \u2014 single interview transcript.\n\nReturns the interview header, ordered messages (PARTICIPANT /\nINTERVIEWER / SYSTEM), screener answers, and disqualification causes.\nClosed-ended answers carry structured values on each message row:\n``selected_options`` (pick/rank order, with any \"Other\" custom text),\n``numeric_value`` for rating / number questions, ``matrix_selections``\n(one ``{row_question_id, option_id}`` entry per matrix row), and task\ncompletion metadata.\nSet ``?include_recordings=true`` to inline pre-signed S3 URLs for\nmessage recordings and participant uploads (off by default to skip\nthe per-file S3 STS calls).\n\nRequires the `analytics:read` scope, on a user-delegated token or a partner credential.", "parameters": [{"in": "query", "name": "include_recordings", "schema": {"type": "boolean"}, "description": "If true, inline presigned S3 URLs for message recordings and participant uploads. Off by default to skip per-file STS round-trips when text is enough."}, {"in": "path", "name": "interview_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Interviews"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InterviewTranscriptResponse"}}}, "description": ""}}, "security": [{"oauth2": ["analytics:read"]}, {"partnerAssertion": []}]}}, "/v2/me/": {"get": {"operationId": "me_retrieve", "description": "The authenticated person, their organization, and what the token can do.\n\nRequires a user-delegated OAuth token. A partner assertion is refused here: the credential names a partner, not a person, so there is no identity to return.", "summary": "Current identity and capabilities", "tags": ["Me"], "responses": {"200": {"description": "No response body"}}}}, "/v2/organizations/": {"get": {"operationId": "organizations_retrieve", "description": "Every organization mapped to the calling partner.\n\nRequires the `organizations:read` partner scope.", "summary": "List organizations", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}], "tags": ["Organizations"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrganizationListResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}, "post": {"operationId": "organizations_create", "description": "Creates a client organization in the calling partner's estate.\n\nIdempotent on `external_org_id`: repeating the call returns the existing organization with `200` rather than creating a second one, so a retry after a dropped response is safe. A first creation answers `201`.\n\nOn a repeat, `name` is ignored \u2014 the response carries the organization's current name. Use `PATCH` to rename.\n\nRequires the `organizations:write` partner scope.", "summary": "Create an organization", "tags": ["Organizations"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrganizationCreate"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/OrganizationCreate"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/OrganizationCreate"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrganizationResponse"}}}, "description": ""}, "201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrganizationResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}}, "/v2/organizations/{external_org_id}/": {"get": {"operationId": "organizations_retrieve_2", "description": "Read or update one organization the calling partner manages.\n\nRequires the `organizations:read` partner scope.", "summary": "Read an organization", "parameters": [{"in": "path", "name": "external_org_id", "schema": {"type": "string"}, "required": true}], "tags": ["Organizations"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrganizationResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}, "patch": {"operationId": "organizations_partial_update", "description": "Sets an organization's status, its name, or both.\n\n`SUSPENDED` refuses sign-ins to the organization and refuses member changes through this API. A member whose only organization this is has their existing sessions ended as well; one who also belongs elsewhere keeps their session for those organizations and is refused this one. Restoring does not restore a session that was ended \u2014 those people sign in again.\n\nReads stay available while suspended, so a billing period that ends after the suspension can still be reconciled. Renaming is also still allowed.\n\nRequires the `organizations:write` partner scope.", "summary": "Update an organization", "parameters": [{"in": "path", "name": "external_org_id", "schema": {"type": "string"}, "required": true}], "tags": ["Organizations"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PatchedOrganizationUpdate"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/PatchedOrganizationUpdate"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/PatchedOrganizationUpdate"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/OrganizationResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}}, "/v2/projects/": {"get": {"operationId": "projects_retrieve", "description": "Every live project in the organization your credential acts on, newest first, limited to the workspaces a user-delegated token can reach. Archived projects are omitted; so is the hidden container the Simulations product uses.\n\nRequires the `projects:read` scope, on a user-delegated token or a partner credential.", "summary": "List projects", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}], "tags": ["Projects"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["projects:read"]}, {"partnerAssertion": []}]}, "post": {"operationId": "projects_create", "description": "Creates a project in the organization your credential acts on.\n\nWith a user-delegated token the project lands in the workspace named by `workspace_id`, which must be one the token reaches, or in the organization's **default workspace** when none is named. A partner's project always lands in the default workspace. The response reports where it landed. A request is refused when the workspace it would land in is out of the credential's reach.\n\nWith a user-delegated token the project is owned by the token's person. A partner request carries no acting person, so ownership is left empty and the project's history records the assertion that created it instead.\n\nNot idempotent: repeating the call creates a second project. Refused once the organization reaches the project limit or the answered-questions allowance on its plan, while the organization is suspended, and on a plan that does not include authoring studies.\n\nRequires the `projects:write` scope, on a user-delegated token or a partner credential.", "summary": "Create a project", "tags": ["Projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectCreate"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/ProjectCreate"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/ProjectCreate"}}}, "required": true}, "responses": {"201": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectResponse"}}}, "description": ""}}, "security": [{"oauth2": ["projects:write"]}, {"partnerAssertion": []}]}}, "/v2/projects/{project_id}/": {"get": {"operationId": "projects_retrieve_2", "description": "One project in the organization your credential acts on. An identifier that is archived, belongs to another organization, sits in a workspace your credential cannot reach, or does not exist is a `404` alike.\n\nRequires the `projects:read` scope, on a user-delegated token or a partner credential.", "summary": "Read a project", "parameters": [{"in": "path", "name": "project_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Projects"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectResponse"}}}, "description": ""}}, "security": [{"oauth2": ["projects:read"]}, {"partnerAssertion": []}]}, "patch": {"operationId": "projects_partial_update", "description": "Renames a project. A project cannot be moved between workspaces through this API, and cannot be given an owner; `interview_method` is set at creation and is not writable here. Refused for a project holding research delivered to this organization, whose name follows its source, for a demo project, while the organization is suspended, and on a plan that does not include authoring studies.\n\nRequires the `projects:write` scope, on a user-delegated token or a partner credential.", "summary": "Update a project", "parameters": [{"in": "path", "name": "project_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Projects"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PatchedProjectUpdate"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/PatchedProjectUpdate"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/PatchedProjectUpdate"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ProjectResponse"}}}, "description": ""}}, "security": [{"oauth2": ["projects:write"]}, {"partnerAssertion": []}]}, "delete": {"operationId": "projects_destroy", "description": "Archives the project. It stops appearing on this API and moves to the **Archived** tab in the Outset app, where a researcher can restore it \u2014 the only way to reverse an archive made here, because this API has no un-archive. Its studies and interviews are retained for billing and compliance; this is not a delete. Refused for a project holding research delivered to this organization or acting as a syndication tracker, for a demo project, while the organization is suspended, and on a plan that does not include authoring studies.\n\nRequires the `projects:write` scope, on a user-delegated token or a partner credential.", "summary": "Archive a project", "parameters": [{"in": "path", "name": "project_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Projects"], "responses": {"204": {"description": "No response body"}}, "security": [{"oauth2": ["projects:write"]}, {"partnerAssertion": []}]}}, "/v2/projects/{project_id}/interviews/": {"get": {"operationId": "projects_interviews_retrieve", "description": "``GET /v2/projects/{id}/interviews/`` \u2014 list interviews across every\nstudy in a project.  Same filters, scope, and ordering as the\nstudy-scoped variant.\n\nRequires the `analytics:read` scope, on a user-delegated token or a partner credential.", "parameters": [{"in": "query", "name": "completed_only", "schema": {"type": "boolean"}}, {"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "include_archived", "schema": {"type": "boolean"}}, {"in": "query", "name": "interview_types", "schema": {"type": "array", "items": {"type": "string"}}, "description": "Filter by interview type code. Repeat the param for multi-value. Allowed: USER, SYNTH, TEST, IMPRT. Default: USER only."}, {"in": "query", "name": "only_screened_in", "schema": {"type": "boolean"}}, {"in": "query", "name": "only_screened_out", "schema": {"type": "boolean"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}, {"in": "path", "name": "project_id", "schema": {"type": "string", "format": "uuid"}, "required": true}, {"in": "query", "name": "since", "schema": {"type": "string", "format": "date-time"}}], "tags": ["Projects"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InterviewListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["analytics:read"]}, {"partnerAssertion": []}]}}, "/v2/recruitment-wallet/": {"get": {"operationId": "recruitment_wallet_retrieve", "description": "Settled recruitment wallet balance for the organization the credential names.\n\nThe balance is organization-wide: the wallet is an organization-level credit pool and is not divided per workspace, so a credential carrying `wallet:read` sees the whole balance regardless of any workspace grant.\n\nRequires the `wallet:read` scope, on a user-delegated token or a partner credential.", "tags": ["Recruitment wallet"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/RecruitmentWalletResponse"}}}, "description": ""}}, "security": [{"oauth2": ["wallet:read"]}, {"partnerAssertion": []}]}}, "/v2/screener-templates/": {"get": {"operationId": "screener_templates_retrieve", "description": "Screener templates belonging to the organization your credential acts on, newest first \u2014 for a user-delegated token, those available organization-wide or in a workspace it reaches. `description` is always empty and `is_global` always false for this kind.\n\nRequires the `templates:read` scope, on a user-delegated token or a partner credential.", "summary": "List screener templates", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}], "tags": ["Screener templates"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TemplateListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["templates:read"]}, {"partnerAssertion": []}]}}, "/v2/screener-templates/{template_id}/": {"get": {"operationId": "screener_templates_retrieve_2", "description": "Read one screener template, or change which workspaces it is shared with.\n\nRequires the `templates:read` scope, on a user-delegated token or a partner credential.", "summary": "Read a screener template", "parameters": [{"in": "path", "name": "template_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Screener templates"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TemplateResponse"}}}, "description": ""}}, "security": [{"oauth2": ["templates:read"]}, {"partnerAssertion": []}]}, "patch": {"operationId": "screener_templates_partial_update", "description": "Replaces the template's shared-workspace list. Sharing is the only property this API changes \u2014 templates themselves are authored in the Outset app.\n\n**An empty list means the whole organization**, not none.\n\nEvery identifier must name a live workspace your credential can reach; one that does not refuses the whole request and changes nothing. A template Outset provides to every organization cannot be shared through this API. Refused while the organization is suspended, and on a plan that does not include authoring studies.\n\nWith a user-delegated token two more rules apply, as in the app. An empty list is refused when the organization restricts organization-wide sharing to admins and the token's person is not one. And a template also shared with a workspace the token cannot reach is refused with `403`, because replacing its list would remove those shares unseen.\n\nRequires the `templates:write` scope, on a user-delegated token or a partner credential.", "summary": "Set which workspaces a template is available in", "parameters": [{"in": "path", "name": "template_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Screener templates"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PatchedTemplateSharing"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/PatchedTemplateSharing"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/PatchedTemplateSharing"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TemplateResponse"}}}, "description": ""}}, "security": [{"oauth2": ["templates:write"]}, {"partnerAssertion": []}]}}, "/v2/simulation-studies/{study_id}/launch/": {"post": {"operationId": "simulation_studies_launch_create", "description": "``POST /v2/simulation-studies/{study_id}/launch/`` \u2014 launch a simulation run.\n\nResolves the study through the org-scoped marker chain (404 on a foreign or\nmissing id), reads an optional ``Idempotency-Key`` header (a malformed value\n400s; absent, one is generated), and hands off to ``launch_simulation``. The\nresponse body is the Simulate service's own contract \u2014 an accepted\n``{requested, queued}`` or a refusal ``{status, errors}`` \u2014 so it is returned\nverbatim, **not** wrapped in the single-object envelope, mirroring the v1\nsimulate action.", "parameters": [{"in": "header", "name": "Idempotency-Key", "schema": {"type": "string", "format": "uuid"}, "description": "Optional UUID that de-duplicates retries: replaying a key returns the original launch outcome instead of starting a second run. One is generated when omitted."}, {"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Digital twins"], "responses": {"202": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationLaunchAccepted"}}}, "description": "Launch accepted; twin interviews queued."}, "400": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationLaunchRefusal"}}}, "description": "Launch refused \u2014 unsupported guide, invalid twin count, or unsupported language. (A malformed `Idempotency-Key` returns the generic error envelope instead.)"}, "403": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationLaunchRefusal"}}}, "description": "Simulation usage limit reached. (Twins-not-enabled and insufficient-scope return the generic error envelope instead.)"}, "409": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationLaunchRefusal"}}}, "description": "Launch refused \u2014 a run is already in progress, the configuration changed, or a prior result is unacknowledged."}, "429": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationLaunchRefusal"}}}, "description": "Too many launch attempts; retry after the `Retry-After` interval."}}, "security": [{"oauth2": ["twins:launch"]}]}}, "/v2/simulation-studies/{study_id}/status/": {"get": {"operationId": "simulation_studies_status_retrieve", "description": "``GET /v2/simulation-studies/{study_id}/status/`` \u2014 a simulation study's latest run.\n\nResolves the study through the org-scoped simulation-study marker chain; a\nforeign or missing id 404s. ``run`` is ``null`` when the study has never run.", "parameters": [{"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Digital twins"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationStudyStatusResponse"}}}, "description": ""}}, "security": [{"oauth2": ["twins:read"]}]}}, "/v2/simulation-studies/{study_id}/stop/": {"post": {"operationId": "simulation_studies_stop_create", "description": "``POST /v2/simulation-studies/{study_id}/stop/`` \u2014 stop a running simulation early.\n\nSame scope, entitlement, and org-scoped lookup as launch, and the same unwrapped service body.\nStopping twice is safe: a repeat while the run is still settling returns ``202`` again.", "parameters": [{"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Digital twins"], "responses": {"202": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationStopAccepted"}}}, "description": "Stop accepted; retention follows the completion threshold and no further twins are interviewed."}, "409": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/SimulationLaunchRefusal"}}}, "description": "No simulation is running for this study (`SIMULATION_NOT_RUNNING`)."}}, "security": [{"oauth2": ["twins:launch"]}]}}, "/v2/studies/": {"get": {"operationId": "studies_retrieve", "description": "Studies in the organization your credential acts on, newest first, limited to the workspaces a user-delegated token can reach. Archived and deleted studies are omitted, as are the studies the Simulations product runs against digital twins. Filters combine with AND.\n\nA row carries the study's settings, `question_count` and `has_screener` \u2014 not the guide itself. Read one study to get its questions and screener.\n\nRequires the `studies:read` scope, on a user-delegated token or a partner credential.", "summary": "List studies", "parameters": [{"in": "query", "name": "active", "schema": {"type": "boolean"}, "description": "`true` for published studies, `false` for the rest. Any other value is refused."}, {"in": "query", "name": "created_after", "schema": {"type": "string", "format": "date-time"}, "description": "Return studies created at or after this instant (ISO 8601)."}, {"in": "query", "name": "created_before", "schema": {"type": "string", "format": "date-time"}, "description": "Return studies created strictly before this instant (ISO 8601)."}, {"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}, {"in": "query", "name": "project", "schema": {"type": "string", "format": "uuid"}, "description": "Return only studies in this project."}, {"in": "query", "name": "study", "schema": {"type": "string", "format": "uuid"}, "description": "Return only this study. A study outside this organization returns no rows."}, {"in": "query", "name": "workspace", "schema": {"type": "string", "format": "uuid"}, "description": "Return only studies in this workspace. Workspace identifiers come from `GET /v2/me/` for a user-delegated token and from the organization response for a partner; workspaces are not otherwise addressable here."}], "tags": ["Studies"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StudyListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["studies:read"]}, {"partnerAssertion": []}]}}, "/v2/studies/{study_id}/": {"get": {"operationId": "studies_retrieve_2", "description": "Read one study.\n\nRequires the `studies:read` scope, on a user-delegated token or a partner credential.", "summary": "Read a study", "parameters": [{"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Studies"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StudyResponse"}}}, "description": ""}}, "security": [{"oauth2": ["studies:read"]}, {"partnerAssertion": []}]}, "patch": {"operationId": "studies_partial_update", "description": "Sets whether participant recordings are processed for visual analysis. That is the only setting this API writes \u2014 everything else about a study is authored in the Outset app, and publishing has its own endpoint because it needs its own scope.\n\n**Enabling visual analysis starts paid processing** of every recording the study captures from then on, and of recordings captured in the last 14 days whose interviews have already finished \u2014 the reconciliation sweep re-drives those.\n\nRefused for a study in a demo project, while the organization is suspended, and on a plan that does not include authoring studies.\n\n**A user-delegated token is refused with `403`.** Visual analysis is turned on by Outset or a partner, never by a researcher \u2014 the same rule the Outset app applies.\n\nRequires the `studies:write` partner scope.", "summary": "Update a study's settings", "parameters": [{"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Studies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PatchedStudyUpdate"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StudyResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}}, "/v2/studies/{study_id}/interviews/": {"get": {"operationId": "studies_interviews_retrieve", "description": "``GET /v2/studies/{id}/interviews/`` \u2014 list interviews in a study.\n\nCursor-paginated, newest-first.  Default filter set matches the MCP\n``list_interviews`` tool and the web UI (USER-typed, non-archived).\nSort order is ``(-created, id)`` via ``OAuthCursorPagination``; the\nMCP tool uses ``(-created, -id)`` \u2014 both are deterministic but the\ntiebreaker direction differs between the surfaces.\n\nRequires the `analytics:read` scope, on a user-delegated token or a partner credential.", "parameters": [{"in": "query", "name": "completed_only", "schema": {"type": "boolean"}}, {"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "include_archived", "schema": {"type": "boolean"}}, {"in": "query", "name": "interview_types", "schema": {"type": "array", "items": {"type": "string"}}, "description": "Filter by interview type code. Repeat the param for multi-value. Allowed: USER, SYNTH, TEST, IMPRT. Default: USER only."}, {"in": "query", "name": "only_screened_in", "schema": {"type": "boolean"}}, {"in": "query", "name": "only_screened_out", "schema": {"type": "boolean"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}, {"in": "query", "name": "since", "schema": {"type": "string", "format": "date-time"}}, {"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Studies"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/InterviewListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["analytics:read"]}, {"partnerAssertion": []}]}}, "/v2/studies/{study_id}/publish/": {"post": {"operationId": "studies_publish_create", "description": "Approves the study's pending review and publishes it. **This spends money and opens the study to participants**, which is why it needs the `studies:launch` scope rather than the ordinary study write.\n\nSend `review_id` \u2014 the review you are approving: the identifier carried on the publish-requested message a partner portal received, or the pending review a person looked at. If the review it names is no longer the one awaiting a decision \u2014 a researcher resubmitted before your approval landed, or an earlier attempt of this same call already approved it \u2014 you get `409` rather than an approval of a review nobody at your end saw.\n\nWith a user-delegated token the approval is the token's person's, as it would be in the app, and only an organization admin may give it: any other role is refused with `403`. A partner's approval names no person.\n\nA study set to recruit participants through Outset is published by launching that recruitment, charged to the researcher who requested the review. The approval counts as accepting the warning that estimated screen-out costs may exceed the workspace budget.\n\nRefusals to expect: `400` when the study has no review awaiting a decision or is not in a publishable state (including recruitment that can't launch, such as insufficient funds, a recruitment cost above the workspace budget, or an unfinished recruitment title or description), `409` for the conflict above, for a study that is already terminally closed, or for an Outset-recruited study with no recruitment the approval can launch (`outset_recruitment_not_launchable`), `404` for a study outside your credential's reach. Note that some preparation runs before the publish validators, so a refused publish is not guaranteed to have changed nothing.\n\nRefused while the organization is suspended, and on a plan that does not include authoring studies. Already-published studies are returned unchanged with their review approved.\n\nRequires the `studies:launch` scope, on a user-delegated token or a partner credential.", "summary": "Approve a review and publish a study", "parameters": [{"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Studies"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StudyPublish"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StudyResponse"}}}, "description": ""}}, "security": [{"oauth2": ["studies:launch"]}, {"partnerAssertion": []}]}}, "/v2/studies/{study_id}/statistics/": {"get": {"operationId": "studies_statistics_retrieve", "description": "Four blocks for one study: participant flow, settled recruitment spend, per-screener-question counts, and per-question counts.\n\n**The counts are not limited to completed interviews.** They exist to show drop-off through the flow, so an interview that started and stopped is counted alongside one that finished. The billable answered-question figure is a different number and does not appear here.\n\nNo participant-level data: every figure is an aggregate. Cached briefly, so a figure may lag the underlying data by up to an hour.\n\nRequires the `studies:read` scope, on a user-delegated token or a partner credential.", "summary": "Read a study's statistics", "parameters": [{"in": "path", "name": "study_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Studies"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/StudyStatisticsResponse"}}}, "description": ""}}, "security": [{"oauth2": ["studies:read"]}, {"partnerAssertion": []}]}}, "/v2/study-templates/": {"get": {"operationId": "study_templates_retrieve", "description": "Study templates belonging to the organization your credential acts on, newest first \u2014 for a user-delegated token, those available organization-wide or in a workspace it reaches. Templates are authored in the Outset app; the only property this API changes is which workspaces each one is available in.\n\nRequires the `templates:read` scope, on a user-delegated token or a partner credential.", "summary": "List study templates", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}], "tags": ["Study templates"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TemplateListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["templates:read"]}, {"partnerAssertion": []}]}}, "/v2/study-templates/{template_id}/": {"get": {"operationId": "study_templates_retrieve_2", "description": "Read one study template, or change which workspaces it is shared with.\n\nRequires the `templates:read` scope, on a user-delegated token or a partner credential.", "summary": "Read a study template", "parameters": [{"in": "path", "name": "template_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Study templates"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TemplateResponse"}}}, "description": ""}}, "security": [{"oauth2": ["templates:read"]}, {"partnerAssertion": []}]}, "patch": {"operationId": "study_templates_partial_update", "description": "Replaces the template's shared-workspace list. Sharing is the only property this API changes \u2014 templates themselves are authored in the Outset app.\n\n**An empty list means the whole organization**, not none.\n\nEvery identifier must name a live workspace your credential can reach; one that does not refuses the whole request and changes nothing. A template Outset provides to every organization cannot be shared through this API. Refused while the organization is suspended, and on a plan that does not include authoring studies.\n\nWith a user-delegated token two more rules apply, as in the app. An empty list is refused when the organization restricts organization-wide sharing to admins and the token's person is not one. And a template also shared with a workspace the token cannot reach is refused with `403`, because replacing its list would remove those shares unseen.\n\nRequires the `templates:write` scope, on a user-delegated token or a partner credential.", "summary": "Set which workspaces a template is available in", "parameters": [{"in": "path", "name": "template_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Study templates"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PatchedTemplateSharing"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/PatchedTemplateSharing"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/PatchedTemplateSharing"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TemplateResponse"}}}, "description": ""}}, "security": [{"oauth2": ["templates:write"]}, {"partnerAssertion": []}]}}, "/v2/twin-panel-threads/{panel_thread_id}/": {"get": {"operationId": "twin_panel_threads_retrieve", "description": "``GET /v2/twin-panel-threads/{id}/`` \u2014 poll one admitted panel turn.", "parameters": [{"in": "path", "name": "panel_thread_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Digital twins"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TwinPanelThreadResponse"}}}, "description": ""}}, "security": [{"oauth2": ["twins:interact"]}]}}, "/v2/twin-panel-turns/": {"post": {"operationId": "twin_panel_turns_create", "description": "``POST /v2/twin-panel-turns/`` \u2014 admit a panel turn for asynchronous resolution.", "tags": ["Digital twins"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TwinPanelTurnRequest"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/TwinPanelTurnRequest"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/TwinPanelTurnRequest"}}}, "required": true}, "responses": {"202": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TwinPanelThreadResponse"}}}, "description": ""}}, "security": [{"oauth2": ["twins:interact"]}]}}, "/v2/twins/": {"get": {"operationId": "twins_retrieve", "description": "``GET /v2/twins/`` \u2014 the organization's reachable digital twins.\n\nCursor-paginated, newest-first (``OAuthCursorPagination`` default ordering).\nEach row is the public twin shape (derived pseudonyms, archetype, audiences,\nattributes, activity count) \u2014 no human identity, transcript, or memory.", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}], "tags": ["Digital twins"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TwinRowListResponse"}}}, "description": ""}}, "security": [{"oauth2": ["twins:read"]}]}}, "/v2/twins/{twin_id}/": {"get": {"operationId": "twins_retrieve_2", "description": "``GET /v2/twins/{twin_id}/`` \u2014 one twin's profile.\n\nThe roster fields plus grounding provenance (when the research was collected,\nwhen the twin was last rebuilt, whether it is a shared house-library twin, and\na grounding-confidence band). A twin your organization can't reach 404s.", "parameters": [{"in": "path", "name": "twin_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Digital twins"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TwinProfileResponse"}}}, "description": ""}}, "security": [{"oauth2": ["twins:read"]}]}}, "/v2/twins/usage/": {"get": {"operationId": "twins_usage_retrieve", "description": "``GET /v2/twins/usage/`` \u2014 the organization's answered-question usage this month.\n\nTwins bill on answers actually received, metered per answered question. This\nis a separate contract from core Outset usage and is never summed with it.", "tags": ["Digital twins"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TwinUsageResponse"}}}, "description": ""}}, "security": [{"oauth2": ["twins:read"]}]}}, "/v2/usage/": {"get": {"operationId": "usage_retrieve", "description": "Returns one row per user in your organization who logged in, created work, or had engagement sessions within a date range \u2014 logins, engagement sessions, and the research output (projects, studies, completed interviews) attributed to each person. This is the endpoint to poll on a schedule (e.g. daily) to track adoption across your team.\n\n**Authentication.** Send an OAuth 2.1 bearer token (`Authorization: Bearer <token>`) granted the `usage:read` scope. The token must be consented at the organization level (all workspaces); access is additionally limited to organizations explicitly enabled for the usage API. The authenticated user must be an organization admin or non-researcher admin.\n\nA partner credential reads the same metrics for one client organization by naming it in the assertion's `outset_org` claim. The three conditions above are properties of a user-delegated token \u2014 a consent grant, an organization role, and a per-organization allow-list \u2014 and none of them has a partner analogue; `usage:read` on the partner registration is the equivalent grant, and the signed claim is what limits the read to one client.\n\n**Date range.** `start_date` and `end_date` are both required and inclusive, in `YYYY-MM-DD` (RFC 3339 full-date) format, evaluated in UTC. The range may span at most 366 days.\n\n**One row per user.** Each entry covers a single user keyed by `email`. A user who created work or had engagement sessions but never logged in during the range still appears, with an empty `logins` list; `user_id` is `null` when no current Outset user maps to that email.\n\n**Logins vs. engagement sessions.** `logins` is the auth-event audit log (one entry per credentials / Google / SAML / OIDC sign-in). `engagement_session_count` is the number of distinct product-usage sessions (PostHog 30-minute inactivity buckets) \u2014 actual active usage, which a single login can span several of, or none. Use `GET /v2/usage/sessions/` for per-session detail and `GET /v2/usage/logins/` for a flat login feed.\n\n**Pagination.** Results are cursor-paginated and ordered by email. When `has_more` is `true`, pass the returned `next_cursor` back as the `cursor` query parameter to fetch the next page. Page size defaults to 50 and is capped at 200.\n\nRequires the `usage:read` scope, on a user-delegated token or a partner credential.", "summary": "Per-user usage metrics", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Cursor from the previous response's `next_cursor`."}, {"in": "query", "name": "end_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive end date in RFC 3339 full-date format.", "required": true}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}, "description": "Number of users to return, capped at 200."}, {"in": "query", "name": "start_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive start date in RFC 3339 full-date format.", "required": true}], "tags": ["Usage"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UsageResponse"}}}, "description": ""}}, "security": [{"oauth2": ["usage:read"]}, {"partnerAssertion": []}]}}, "/v2/usage/logins/": {"get": {"operationId": "usage_logins_retrieve", "description": "Returns a flat list of login events for your organization over a date range \u2014 one row per successful credentials / Google / SAML / OIDC sign-in recorded server-side. This is the authentication audit log.\n\nIt is the flat counterpart of the `logins` array on `GET /v2/usage/`: use this endpoint when you want to iterate every login event in a range without paginating through users first. It is **distinct from `GET /v2/usage/sessions/`** \u2014 a login is an auth event, whereas a session is a window of actual product engagement; the two do not map one-to-one.\n\n**Authentication.** OAuth 2.1 bearer token (`Authorization: Bearer <token>`) with the `usage:read` scope, consented at the organization level; access is limited to organizations enabled for the usage API. The authenticated user must be an organization admin or non-researcher admin.\n\nA partner credential reads the same metrics for one client organization by naming it in the assertion's `outset_org` claim; the consent, role and allow-list conditions above are properties of a user-delegated token and have no partner analogue.\n\n**Date range.** `start_date` and `end_date` are both required and inclusive, in `YYYY-MM-DD` (RFC 3339 full-date) format, evaluated in UTC. The range may span at most 366 days.\n\n**Pagination.** Cursor-paginated, newest login first. When `has_more` is `true`, pass the returned `next_cursor` back as the `cursor` query parameter. Page size defaults to 50 and is capped at 200.\n\nRequires the `usage:read` scope, on a user-delegated token or a partner credential.", "summary": "Login events", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Cursor from the previous response's `next_cursor`."}, {"in": "query", "name": "end_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive end date in RFC 3339 full-date format.", "required": true}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}, "description": "Number of logins to return, capped at 200."}, {"in": "query", "name": "start_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive start date in RFC 3339 full-date format.", "required": true}], "tags": ["Usage"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UsageLoginsResponse"}}}, "description": ""}}, "security": [{"oauth2": ["usage:read"]}, {"partnerAssertion": []}]}}, "/v2/usage/projects/": {"get": {"operationId": "usage_projects_retrieve", "description": "Returns usage totals grouped by project for completed interviews in the requested date range. Each row includes raw completes, valid completes, and `total_interview_duration_seconds` for valid interviews. Low-quality interviews are included in the raw count but excluded from the valid count and duration total. Studies that are not assigned to a project are omitted.\n\nRequires the `usage:read` scope, on a user-delegated token or a partner credential.", "summary": "Interview minutes by project", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Cursor from the previous response's `next_cursor`."}, {"in": "query", "name": "end_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive end date in RFC 3339 full-date format.", "required": true}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}, "description": "Number of projects to return, capped at 200."}, {"in": "query", "name": "start_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive start date in RFC 3339 full-date format.", "required": true}], "tags": ["Usage"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UsageProjectsResponse"}}}, "description": ""}}, "security": [{"oauth2": ["usage:read"]}, {"partnerAssertion": []}]}}, "/v2/usage/sessions/": {"get": {"operationId": "usage_sessions_retrieve", "description": "Returns one row per engagement session for your organization over a date range \u2014 real product usage, with each session's duration and event count. A session is a PostHog `$session_id`: a window of activity that closes after 30 minutes of inactivity, **not** a login. Use this for an accurate picture of how much time researchers actually spend in the product.\n\n**This is distinct from `GET /v2/usage/logins/`**, which lists raw authentication events. One login can span several engagement sessions, and a returning user can open a new session without logging in again. Correlate the two by joining on `user_id` and the session's time range.\n\n**Authentication.** OAuth 2.1 bearer token (`Authorization: Bearer <token>`) with the `usage:read` scope, consented at the organization level; access is limited to organizations enabled for the usage API. The authenticated user must be an organization admin or non-researcher admin.\n\nA partner credential reads the same metrics for one client organization by naming it in the assertion's `outset_org` claim; the consent, role and allow-list conditions above are properties of a user-delegated token and have no partner analogue.\n\n**Date range.** `start_date` and `end_date` are both required and inclusive, in `YYYY-MM-DD` (RFC 3339 full-date) format, evaluated in UTC. The range may span at most 366 days.\n\n**Pagination.** Cursor-paginated: when `has_more` is `true`, pass the returned `next_cursor` back as the `cursor` query parameter. Page size defaults to 50 and is capped at 200.\n\nSession data is sourced from an upstream analytics provider. A `400` with `code=result_too_large` means the requested range should be shortened; transient `502` (provider unavailable) and `503` (data being prepared \u2014 retry after `Retry-After`) responses should be retried with backoff.\n\nRequires the `usage:read` scope, on a user-delegated token or a partner credential.", "summary": "Engagement sessions", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Cursor from the previous response's `next_cursor`."}, {"in": "query", "name": "end_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive end date in RFC 3339 full-date format.", "required": true}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}, "description": "Number of sessions to return, capped at 200."}, {"in": "query", "name": "start_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive start date in RFC 3339 full-date format.", "required": true}], "tags": ["Usage"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UsageSessionsResponse"}}}, "description": ""}}, "security": [{"oauth2": ["usage:read"]}, {"partnerAssertion": []}]}}, "/v2/usage/studies/": {"get": {"operationId": "usage_studies_retrieve", "description": "Returns usage totals grouped by study for completed interviews in the requested date range. Each row includes raw completes, valid completes, `total_interview_duration_seconds`, and the owning `project_id` when available. Low-quality interviews are included in the raw count but excluded from the valid count and duration total.\n\nRequires the `usage:read` scope, on a user-delegated token or a partner credential.", "summary": "Interview minutes by study", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}, "description": "Cursor from the previous response's `next_cursor`."}, {"in": "query", "name": "end_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive end date in RFC 3339 full-date format.", "required": true}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}, "description": "Number of studies to return, capped at 200."}, {"in": "query", "name": "start_date", "schema": {"type": "string", "format": "date"}, "description": "Inclusive start date in RFC 3339 full-date format.", "required": true}], "tags": ["Usage"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UsageStudiesResponse"}}}, "description": ""}}, "security": [{"oauth2": ["usage:read"]}, {"partnerAssertion": []}]}}, "/v2/users/": {"get": {"operationId": "users_retrieve", "description": "List the members of the organization named in the assertion.\n\nRequires the `users:read` partner scope.", "summary": "List organization members", "parameters": [{"in": "query", "name": "cursor", "schema": {"type": "string"}}, {"in": "query", "name": "email", "schema": {"type": "string"}, "description": "Return only the member with this email address. Case-insensitive exact match; an address that is not a member of this organization returns no rows. Sending the parameter with an empty value is rejected \u2014 omit it to list everyone."}, {"in": "query", "name": "page_size", "schema": {"type": "integer"}}], "tags": ["Users"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserListResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}}, "/v2/users/{user_id}/": {"get": {"operationId": "users_retrieve_2", "description": "Read a member, change their role, or remove them from the organization.\n\nRequires the `users:read` partner scope.", "summary": "Read an organization member", "parameters": [{"in": "path", "name": "user_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Users"], "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}, "patch": {"operationId": "users_partial_update", "description": "Read a member, change their role, or remove them from the organization.\n\nRequires the `users:write` partner scope.", "summary": "Set an organization member's role", "parameters": [{"in": "path", "name": "user_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Users"], "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/PatchedUserUpdate"}}, "application/x-www-form-urlencoded": {"schema": {"$ref": "#/components/schemas/PatchedUserUpdate"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/PatchedUserUpdate"}}}}, "responses": {"200": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/UserResponse"}}}, "description": ""}}, "security": [{"partnerAssertion": []}]}, "delete": {"operationId": "users_destroy", "description": "Ends this person's membership of the organization named in the assertion. **This is not account deletion and not durable revocation** \u2014 read the whole list before relying on it:\n\n- The Outset account survives, and so does every other organization it belongs to, including organizations you do not administer.\n- Their existing sessions end unless they are left a membership of an organization that is **still active**. With one left, a session stays valid, because a session names no organization and ending it would sign them out of organizations unrelated to you. A membership of an organization **you have suspended** does not count \u2014 a session there resolves to nothing \u2014 so removing a person from one of your organizations while another is suspended signs them out everywhere.\n- **Your identity provider can re-admit them.** The next assertion it makes for this person recreates the membership at the default role, because that is how they arrived in the first place. To revoke access, remove them at your end too; doing it only here is undone by their next sign-in.\n- Their interviews, studies and other work stay with the organization.\n\nRefused while the organization is suspended.\n\nRequires the `users:write` partner scope.", "summary": "Remove a member from the organization", "parameters": [{"in": "path", "name": "user_id", "schema": {"type": "string", "format": "uuid"}, "required": true}], "tags": ["Users"], "responses": {"204": {"description": "No response body"}}, "security": [{"partnerAssertion": []}]}}}, "components": {"schemas": {"InterviewListResponse": {"type": "object", "description": "Paginated envelope for ``GET /v2/studies/{id}/interviews/`` etc.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/InterviewRow"}}, "next_cursor": {"type": "string", "nullable": true}, "has_more": {"type": "boolean"}}, "required": ["data", "has_more", "next_cursor"]}, "InterviewMethodEnum": {"enum": ["STANDARD", "USABILITY"], "type": "string", "description": "* `STANDARD` - STANDARD\n* `USABILITY` - USABILITY"}, "InterviewRow": {"type": "object", "description": "Single interview row, matching ``InterviewRow`` TypedDict.", "properties": {"id": {"type": "string", "format": "uuid"}, "name": {"type": "string", "nullable": true}, "participant_label": {"type": "string", "description": "The composed handle on its own, e.g. `Participant #A-0040`, never a researcher's rename. `participant_label` prefers the rename where one is set, so this is what a surface uses when it has to name the pseudonym itself \u2014 the rename box's reset control, which offers to put the interview back on its handle."}, "type": {"type": "string"}, "started_at": {"type": "string", "format": "date-time", "nullable": true}, "completed_at": {"type": "string", "format": "date-time", "nullable": true}, "is_active": {"type": "boolean"}, "progress_percentage": {"type": "number", "format": "double", "description": "How far through the study the participant got, as a fraction from 0 to 1 (0.42 = 42%). Note the panel-monitoring interview row publishes this as a 0-100 percentage instead."}, "screened_out": {"type": "boolean", "nullable": true}, "is_fraud": {"type": "boolean"}, "is_low_quality": {"type": "boolean"}, "language_code": {"type": "string", "nullable": true}, "fraud_score": {"type": "number", "format": "double", "nullable": true}, "total_engagement_time_seconds": {"type": "number", "format": "double", "nullable": true}, "archived": {"type": "boolean"}, "status": {"type": "string", "description": "Lifecycle only \u2014 how far the interview got. Fraud is reported on ``quality_status``, not here."}, "quality_status": {"type": "string", "description": "How far the interview can be trusted: \"Fraud detected\", \"Low\", \"Medium\", \"High\" or \"N/A\". Fraud outranks the score, so a flagged interview reads \"Fraud detected\" whatever it scored."}, "url": {"type": "string", "format": "uri"}}, "required": ["archived", "completed_at", "fraud_score", "id", "is_active", "is_fraud", "is_low_quality", "language_code", "name", "participant_label", "progress_percentage", "quality_status", "screened_out", "started_at", "status", "total_engagement_time_seconds", "type", "url"]}, "InterviewTranscript": {"type": "object", "description": "Full transcript payload returned by ``GET /v2/interviews/{id}/``.", "properties": {"interview": {"$ref": "#/components/schemas/InterviewRow"}, "messages": {"type": "array", "items": {"$ref": "#/components/schemas/_MessageRow"}}, "screener_answers": {"type": "array", "items": {"$ref": "#/components/schemas/_ScreenerAnswerRow"}}, "disqualification_causes": {"nullable": true}}, "required": ["disqualification_causes", "interview", "messages", "screener_answers"]}, "InterviewTranscriptResponse": {"type": "object", "description": "``GET /v2/interviews/{id}/`` envelope.\n\nThe view returns ``Response(envelope(payload))`` \u2014 i.e. ``{\"data\": {...}}``.\nThe schema needs to reflect that so generated clients look for the\ntranscript fields under ``data``, not at the root.  Without this wrapper,\nthe OpenAPI doc and the runtime response disagree and codegen breaks.", "properties": {"data": {"$ref": "#/components/schemas/InterviewTranscript"}}, "required": ["data"]}, "NullEnum": {"enum": [null]}, "Organization": {"type": "object", "description": "One organization in a partner's estate.", "properties": {"external_org_id": {"type": "string", "description": "Your own identifier for this organization \u2014 the value you send in the 'outset_org' claim."}, "organization_id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the organization."}, "name": {"type": "string", "description": "Organization name as its members see it."}, "status": {"type": "string", "description": "ACTIVE or SUSPENDED. A suspended organization refuses logins and provisions no replacement, but stays readable so a billing period ending after suspension can still be reconciled."}, "created": {"type": "string", "format": "date-time", "description": "When the organization was created, UTC."}, "workspaces": {"type": "array", "items": {"$ref": "#/components/schemas/Workspace"}, "description": "Non-archived workspaces in this organization. Read-only; workspaces are not managed through this API."}}, "required": ["created", "external_org_id", "name", "organization_id", "status", "workspaces"]}, "OrganizationCreate": {"type": "object", "description": "``POST /v2/organizations/`` request body.", "properties": {"external_org_id": {"type": "string", "description": "Your identifier for the organization. Creation is idempotent on this value: repeating the call returns the organization you already created rather than a second one. It appears in the URL of the organization's own endpoints, so it may not contain slashes or whitespace.", "maxLength": 255}, "name": {"type": "string", "description": "Display name. Defaults to the external identifier when omitted.", "maxLength": 255}}, "required": ["external_org_id"]}, "OrganizationListResponse": {"type": "object", "description": "``GET /v2/organizations/`` envelope.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Organization"}}, "next_cursor": {"type": "string", "nullable": true}, "has_more": {"type": "boolean"}}, "required": ["data", "has_more", "next_cursor"]}, "OrganizationResponse": {"type": "object", "description": "Single-organization envelope.", "properties": {"data": {"$ref": "#/components/schemas/Organization"}}, "required": ["data"]}, "OrganizationUpdateStatusEnum": {"enum": ["ACTIVE", "SUSPENDED"], "type": "string", "description": "* `ACTIVE` - ACTIVE\n* `SUSPENDED` - SUSPENDED"}, "PatchedOrganizationUpdate": {"type": "object", "description": "``PATCH /v2/organizations/{external_org_id}/`` request body.\n\nBoth fields are optional, but a request setting neither changes nothing and is\nrefused \u2014 silently accepting an empty patch would report success for a rename that\nnever happened.", "properties": {"status": {"allOf": [{"$ref": "#/components/schemas/OrganizationUpdateStatusEnum"}], "description": "Set SUSPENDED to stop users logging into this organization, or ACTIVE to restore it. Suspension retains all data and is reversible.\n\n* `ACTIVE` - ACTIVE\n* `SUSPENDED` - SUSPENDED"}, "name": {"type": "string", "description": "Rename the organization \u2014 the name its members see, not your own identifier for it, which is fixed at creation. Your provider name is appended automatically, so the value you send is a prefix rather than the final name. Organization names are unique across Outset, so a name already taken within your estate is rejected.", "maxLength": 255}}}, "PatchedProjectUpdate": {"type": "object", "description": "``PATCH /v2/projects/{project_id}/`` request body.\n\nThe name only. ``interview_method`` is accepted at creation for compatibility but is not\nwritable here: nothing reads the stored value, so a PATCH that echoed a new one back would\nreport a change of behaviour that does not happen.\n\nThe one field is optional so the body stays a partial update, but a request setting nothing is\nrefused \u2014 reporting success for a rename that never happened is worse than a 400.", "properties": {"name": {"type": "string", "description": "New project name.", "maxLength": 255}}}, "PatchedStudyUpdate": {"type": "object", "description": "``PATCH /v2/studies/{study_id}/`` request body.\n\nOne field. Everything else about a study is authored in the Outset app, and publishing has its\nown route because it needs its own scope.", "properties": {"visual_intelligence_enabled": {"type": "boolean", "description": "Turn visual analysis of participant recordings on or off for this study. **This is paid processing** \u2014 enabling it starts incurring cost on every recording the study captures from that point on, and on recordings captured in the last 14 days whose interviews have already finished."}}}, "PatchedTemplateSharing": {"type": "object", "description": "Template PATCH request body. Sharing is the only writable property.", "properties": {"workspace_ids": {"type": "array", "items": {"type": "string", "format": "uuid"}, "description": "The complete list of workspaces this template should be available in \u2014 a replacement, not an addition. **Send an empty list to make it available to the whole organization.** Every identifier must name a live workspace your credential can reach.", "maxItems": 200}}}, "PatchedUserUpdate": {"type": "object", "description": "``PATCH /v2/users/{user_id}/`` request body.", "properties": {"role": {"allOf": [{"$ref": "#/components/schemas/UserUpdateRoleEnum"}], "description": "The member's new role in the organization named by the assertion.\n\n* `ADMIN` - ADMIN\n* `MANAGER` - MANAGER\n* `USER` - USER\n* `PWDR_USER` - PWDR_USER\n* `OBSERVER` - OBSERVER"}}}, "Project": {"type": "object", "description": "One project in the organization the calling credential acts on.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the project."}, "name": {"type": "string", "description": "Project name as its researchers see it."}, "workspace_id": {"type": "string", "format": "uuid", "description": "The workspace holding this project \u2014 the one named at creation, or the organization's default workspace. Reported so you can confirm where a project landed."}, "interview_method": {"type": "string", "nullable": true, "description": "`STANDARD` or `USABILITY`; `null` when the project does not pin one."}, "format": {"type": "string", "description": "`STANDARD` or `DIARY`. A `DIARY` study is longitudinal \u2014 participants return across multiple sessions over time; a `STANDARD` study is one-shot."}, "is_demo": {"type": "boolean", "description": "True for a free playground project. Never true for one you create."}, "created": {"type": "string", "format": "date-time", "description": "When the project was created, UTC."}}, "required": ["created", "format", "id", "interview_method", "is_demo", "name", "workspace_id"]}, "ProjectCreate": {"type": "object", "description": "``POST /v2/projects/`` request body.\n\nNo owner field. A user-delegated request's person owns what it creates; a partner request has\nno acting person to own anything, so attribution travels in the project's change history\ninstead.", "properties": {"name": {"type": "string", "description": "Project name, as its researchers will see it.", "maxLength": 255}, "workspace_id": {"type": "string", "format": "uuid", "description": "The workspace to create the project in, for a user-delegated token: one the token reaches, as `GET /v2/me/` lists them. Omit it to use the organization's default workspace. Ignored for a partner, whose projects always land in the default workspace."}, "interview_method": {"allOf": [{"$ref": "#/components/schemas/InterviewMethodEnum"}], "default": "STANDARD", "description": "`STANDARD` (default) or `USABILITY`. Stored on the project and reported back, but it does not change how the project or its studies behave \u2014 each study carries its own interview method.\n\n* `STANDARD` - STANDARD\n* `USABILITY` - USABILITY"}}, "required": ["name"]}, "ProjectListResponse": {"type": "object", "description": "``GET /v2/projects/`` envelope.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Project"}}, "next_cursor": {"type": "string", "nullable": true}, "has_more": {"type": "boolean"}}, "required": ["data", "has_more", "next_cursor"]}, "ProjectResponse": {"type": "object", "description": "Single-project envelope.", "properties": {"data": {"$ref": "#/components/schemas/Project"}}, "required": ["data"]}, "Question": {"type": "object", "description": "One question in a study guide.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Identifier for the question."}, "text": {"type": "string", "description": "Question text as the participant sees it."}, "type": {"allOf": [{"$ref": "#/components/schemas/TypeEnum"}], "description": "Question type code.\n\n* `TEXT` - TEXT\n* `MULTIPLE_CHOICE` - MULTIPLE_CHOICE\n* `MULTIPLE_SELECT` - MULTIPLE_SELECT\n* `NUMBER_INPUT` - NUMBER_INPUT\n* `TASK` - TASK\n* `RATING` - RATING\n* `STACK_RANK` - STACK_RANK\n* `INFO_ONLY` - INFO_ONLY\n* `TYPED_RESPONSE` - TYPED_RESPONSE\n* `MATRIX` - MATRIX\n* `IMAGE_CODESIGN` - IMAGE_CODESIGN"}, "order": {"type": "integer", "nullable": true, "description": "Position in the study guide, from one. `null` for a matrix child question, which is asked inside its parent rather than at a position of its own."}, "has_display_logic": {"type": "boolean", "nullable": true, "description": "Whether this question is shown conditionally on an earlier answer. `null` on questions authored before the flag existed; treat it as `false`."}, "rating_scale": {"nullable": true, "description": "Scale for a rating question \u2014 `ZERO_THROUGH_TEN`, `ONE_THROUGH_FIVE`, `ONE_THROUGH_TEN` or `ONE_THROUGH_SEVEN`; `null` for every other question type.\n\n* `ZERO_THROUGH_TEN` - ZERO_THROUGH_TEN\n* `ONE_THROUGH_FIVE` - ONE_THROUGH_FIVE\n* `ONE_THROUGH_TEN` - ONE_THROUGH_TEN\n* `ONE_THROUGH_SEVEN` - ONE_THROUGH_SEVEN", "oneOf": [{"$ref": "#/components/schemas/RatingScaleEnum"}, {"$ref": "#/components/schemas/NullEnum"}]}, "rating_labels": {"type": "array", "items": {"type": "string"}, "nullable": true, "description": "Endpoint labels for a rating question, low to high; an empty list for every other question type. Rows authored before the column took its default may carry `null`."}, "options": {"type": "array", "items": {"$ref": "#/components/schemas/QuestionOption"}, "description": "Answer options; empty for open questions."}}, "required": ["has_display_logic", "id", "options", "order", "rating_labels", "rating_scale", "text", "type"]}, "QuestionOption": {"type": "object", "description": "One answer option on a study question.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Identifier for the option."}, "text": {"type": "string", "description": "Option text as the participant sees it."}, "order": {"type": "integer", "description": "Position among this question's options, from zero."}, "anchor_type": {"type": "string", "description": "`OTHER` for a write-in option pinned to the end of the list; empty string otherwise."}}, "required": ["anchor_type", "id", "order", "text"]}, "RatingScaleEnum": {"enum": ["ZERO_THROUGH_TEN", "ONE_THROUGH_FIVE", "ONE_THROUGH_TEN", "ONE_THROUGH_SEVEN"], "type": "string", "description": "* `ZERO_THROUGH_TEN` - ZERO_THROUGH_TEN\n* `ONE_THROUGH_FIVE` - ONE_THROUGH_FIVE\n* `ONE_THROUGH_TEN` - ONE_THROUGH_TEN\n* `ONE_THROUGH_SEVEN` - ONE_THROUGH_SEVEN"}, "RecruitmentWallet": {"type": "object", "description": "Recruitment wallet balance for the token's organization.\n\nPorts the v1 ``APIRecruitmentWalletSerializer`` field-for-field. The\nvalue is the settled wallet balance (org-level credit sum) from\n``billing.services.get_credit_balance`` \u2014 *not* the unallocated\nbalance; see ``backend/billing/CLAUDE.md`` for the distinction.", "properties": {"organization_id": {"type": "string", "format": "uuid"}, "wallet_balance_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$"}, "as_of": {"type": "string", "format": "date-time"}}, "required": ["as_of", "organization_id", "wallet_balance_usd"]}, "RecruitmentWalletResponse": {"type": "object", "description": "``GET /v2/recruitment-wallet/`` envelope.\n\nThe view returns ``Response(envelope(payload))`` \u2014 i.e. ``{\"data\": {...}}``\n\u2014 so the schema wraps the payload under ``data`` to match the runtime\nresponse (see the envelope note in ``backend/api/v2/CLAUDE.md``).", "properties": {"data": {"$ref": "#/components/schemas/RecruitmentWallet"}}, "required": ["data"]}, "Screener": {"type": "object", "description": "The screener attached to a study.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Identifier for the screener."}, "name": {"type": "string", "description": "Screener name, internal to the researcher."}, "introduction": {"type": "string", "description": "Text shown before the first question."}, "rejection_message": {"type": "string", "description": "Text shown to a participant who is screened out."}, "questions": {"type": "array", "items": {"$ref": "#/components/schemas/ScreenerQuestion"}, "description": "Screener questions, in order."}}, "required": ["id", "introduction", "name", "questions", "rejection_message"]}, "ScreenerQuestion": {"type": "object", "description": "One screener question, asked before the interview to qualify a participant.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Identifier for the screener question."}, "text": {"type": "string", "description": "Question text as the participant sees it."}, "type": {"type": "string", "description": "Screener question type code."}, "optional": {"type": "boolean", "description": "Whether a participant may skip it."}, "order": {"type": "integer", "description": "Position in the screener, from zero."}, "min_selections": {"type": "integer", "nullable": true, "description": "Fewest options a participant must pick; `null` when unconstrained."}, "max_selections": {"type": "integer", "nullable": true, "description": "Most options a participant may pick; `null` when unconstrained."}, "disqualify_immediately": {"type": "boolean", "description": "Whether a disqualifying answer ends the screener at this question."}, "qualify_immediately": {"type": "boolean", "description": "Whether a qualifying answer admits the participant at this question."}, "options": {"type": "array", "items": {"$ref": "#/components/schemas/ScreenerQuestionOption"}, "description": "Answer options for this question."}}, "required": ["disqualify_immediately", "id", "max_selections", "min_selections", "optional", "options", "order", "qualify_immediately", "text", "type"]}, "ScreenerQuestionOption": {"type": "object", "description": "One answer option on a screener question.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Identifier for the option."}, "text": {"type": "string", "description": "Option text as the participant sees it."}, "order": {"type": "integer", "description": "Position among this question's options, from zero."}, "anchor_type": {"type": "string", "description": "`OTHER` for a write-in option pinned to the end of the list; empty string otherwise."}}, "required": ["anchor_type", "id", "order", "text"]}, "SimulationLaunchAccepted": {"type": "object", "description": "``202`` body for ``POST /v2/simulation-studies/{id}/launch/`` \u2014 the run was queued.\n\nNot wrapped in the ``{\"data\": ...}`` envelope: this endpoint returns the Simulate service's own\ncontract verbatim (mirroring the v1 simulate action), so accepted and refusal bodies sit at the\nresponse root.", "properties": {"requested": {"type": "integer", "minimum": 0, "description": "Twins requested for this run."}, "queued": {"type": "integer", "minimum": 0, "description": "Twin interviews actually queued. May be fewer than `requested` when the eligible twin pool (after consent and prior-run exclusions) is smaller."}}, "required": ["queued", "requested"]}, "SimulationLaunchRefusal": {"type": "object", "description": "Refusal body for ``POST /v2/simulation-studies/{id}/launch/`` (Simulate service contract).\n\nCarried by the ``400`` / ``403`` / ``409`` / ``429`` refusals the Simulate service returns\n(unsupported guide, usage-limit reached, already running / configuration changed, rate limited).\nStandard DRF error envelopes (auth `401`, entitlement/scope `403`, missing-study `404`, malformed\n`Idempotency-Key` `400`) use the generic error shape instead.", "properties": {"status": {"type": "string", "description": "Always `error` for a refusal."}, "errors": {"type": "array", "items": {"$ref": "#/components/schemas/_SimulationLaunchError"}}}, "required": ["errors", "status"]}, "SimulationStopAccepted": {"type": "object", "description": "``202`` body for ``POST /v2/simulation-studies/{id}/stop/`` \u2014 the running simulation is stopping.\n\nNot wrapped in the ``{\"data\": ...}`` envelope, for the same reason as the launch body. Interviews\nthat already finished are kept; the rest are closed and the run settles shortly after, which the\nstatus endpoint reports.", "properties": {"status": {"allOf": [{"$ref": "#/components/schemas/SimulationStopAcceptedStatusEnum"}], "description": "Always `stopping`.\n\n* `stopping` - stopping"}}, "required": ["status"]}, "SimulationStopAcceptedStatusEnum": {"enum": ["stopping"], "type": "string", "description": "* `stopping` - stopping"}, "SimulationStudyStatus": {"type": "object", "description": "Status of one simulation study and its latest run.", "properties": {"study_id": {"type": "string", "format": "uuid", "description": "The simulation study's identifier."}, "study_name": {"type": "string", "description": "The simulation study's display name."}, "run": {"allOf": [{"$ref": "#/components/schemas/_SimulationRun"}], "nullable": true, "description": "The most recent run's progress, or `null` when the study has never run."}}, "required": ["run", "study_id", "study_name"]}, "SimulationStudyStatusResponse": {"type": "object", "description": "``GET /v2/simulation-studies/{study_id}/status/`` envelope.", "properties": {"data": {"$ref": "#/components/schemas/SimulationStudyStatus"}}, "required": ["data"]}, "Study": {"type": "object", "description": "One study with its full guide \u2014 the shape `GET /v2/studies/{study_id}/` returns.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the study."}, "name": {"type": "string", "description": "Study name as its researchers see it."}, "project_id": {"type": "string", "format": "uuid", "description": "Project holding this study. Always present."}, "workspace_id": {"type": "string", "format": "uuid", "description": "Workspace that owns this study's project. Always present."}, "language_code": {"type": "string", "nullable": true, "description": "Primary interview language, e.g. `en`; `null` if unset."}, "active": {"type": "boolean", "description": "Whether the study is published and open to participants."}, "status": {"allOf": [{"$ref": "#/components/schemas/StudyStatusEnum"}], "description": "Study lifecycle: DRAFT (never published), LIVE (published and open to participants), PAUSED (published before, not open now; can be published again), or CLOSED (finished; terminal \u2014 a closed study never reopens). Unlike `active`, this tells a paused study from a closed one and from a draft.\n\n* `DRAFT` - DRAFT\n* `LIVE` - LIVE\n* `PAUSED` - PAUSED\n* `CLOSED` - CLOSED"}, "visual_intelligence_enabled": {"type": "boolean", "description": "Whether participant recordings are processed for visual analysis. Paid processing."}, "interview_method": {"type": "string", "description": "How the interview is conducted \u2014 e.g. `CHAT`, `VOICE`, `VIDEO`."}, "moderation_type": {"type": "string", "nullable": true, "description": "`AI_MODERATED` or `HUMAN_MODERATED`."}, "welcome_message": {"type": "string", "description": "Text shown before the interview starts."}, "end_message": {"type": "string", "description": "Text shown once the interview finishes."}, "completion_url": {"type": "string", "description": "Where a finishing participant is redirected; empty when unset."}, "max_interviews": {"type": "integer", "nullable": true, "description": "Cap on completed interviews; `null` for no cap."}, "created": {"type": "string", "format": "date-time", "description": "When the study was created, UTC."}, "question_count": {"type": "integer", "description": "Number of questions in the guide."}, "has_screener": {"type": "boolean", "description": "Whether a screener is attached to this study (a screener with no questions still reports `true`)."}, "questions": {"type": "array", "items": {"$ref": "#/components/schemas/Question"}, "description": "The study guide, in order."}, "screener": {"allOf": [{"$ref": "#/components/schemas/Screener"}], "nullable": true, "description": "The screener, or `null` when there is none."}}, "required": ["active", "completion_url", "created", "end_message", "has_screener", "id", "interview_method", "language_code", "max_interviews", "moderation_type", "name", "project_id", "question_count", "questions", "screener", "status", "visual_intelligence_enabled", "welcome_message", "workspace_id"]}, "StudyListResponse": {"type": "object", "description": "``GET /v2/studies/`` envelope.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/StudyRow"}}, "next_cursor": {"type": "string", "nullable": true}, "has_more": {"type": "boolean"}}, "required": ["data", "has_more", "next_cursor"]}, "StudyPublish": {"type": "object", "description": "``POST /v2/studies/{study_id}/publish/`` request body. Both fields are optional.", "properties": {"review_id": {"type": "string", "format": "uuid", "nullable": true, "description": "The review you are approving \u2014 the identifier carried on the publish-requested message your portal received. Send it: if a researcher replaced that review with a resubmit before your approval landed, the request is refused with `409` instead of approving a review nobody at your end has seen. Omitted, whichever review is currently pending is approved."}, "apply_figma_preflight": {"type": "boolean", "default": true, "description": "Whether to run the design-tool preflight before publishing. Leave it alone unless a publish came back reporting a preflight problem you have confirmed is not real \u2014 then resend the identical request with this set to `false` to publish anyway."}}}, "StudyQuestionStat": {"type": "object", "description": "Reach and answer counts for one question.", "properties": {"question_id": {"type": "string", "format": "uuid", "description": "Identifier for the question these counts belong to."}, "text": {"type": "string", "description": "Question text as the participant saw it."}, "type": {"type": "string", "description": "Question type code."}, "order": {"type": "integer", "nullable": true, "description": "Position in the guide, from zero; `null` when the question has no position of its own (a matrix child, which is asked inside its parent)."}, "reached_count": {"type": "integer", "description": "Interviews that reached this question, whether or not they answered it."}, "answered_count": {"type": "integer", "description": "Interviews in which the participant answered it."}}, "required": ["answered_count", "order", "question_id", "reached_count", "text", "type"]}, "StudyResponse": {"type": "object", "description": "Single-study envelope.", "properties": {"data": {"$ref": "#/components/schemas/Study"}}, "required": ["data"]}, "StudyRow": {"type": "object", "description": "One study as the list reports it: everything except the guide.\n\nThe guide is deliberately absent from a list row. Rendering it costs a question read, an\noption read and a screener read per study, so a fifty-row page pays that fifty times \u2014 which\nis the shape that misses the study list's latency target at a large client.\n``question_count`` and ``has_screener`` say whether there is a guide worth fetching;\n``GET /v2/studies/{study_id}/`` returns it.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the study."}, "name": {"type": "string", "description": "Study name as its researchers see it."}, "project_id": {"type": "string", "format": "uuid", "description": "Project holding this study. Always present."}, "workspace_id": {"type": "string", "format": "uuid", "description": "Workspace that owns this study's project. Always present."}, "language_code": {"type": "string", "nullable": true, "description": "Primary interview language, e.g. `en`; `null` if unset."}, "active": {"type": "boolean", "description": "Whether the study is published and open to participants."}, "status": {"allOf": [{"$ref": "#/components/schemas/StudyStatusEnum"}], "description": "Study lifecycle: DRAFT (never published), LIVE (published and open to participants), PAUSED (published before, not open now; can be published again), or CLOSED (finished; terminal \u2014 a closed study never reopens). Unlike `active`, this tells a paused study from a closed one and from a draft.\n\n* `DRAFT` - DRAFT\n* `LIVE` - LIVE\n* `PAUSED` - PAUSED\n* `CLOSED` - CLOSED"}, "visual_intelligence_enabled": {"type": "boolean", "description": "Whether participant recordings are processed for visual analysis. Paid processing."}, "interview_method": {"type": "string", "description": "How the interview is conducted \u2014 e.g. `CHAT`, `VOICE`, `VIDEO`."}, "moderation_type": {"type": "string", "nullable": true, "description": "`AI_MODERATED` or `HUMAN_MODERATED`."}, "welcome_message": {"type": "string", "description": "Text shown before the interview starts."}, "end_message": {"type": "string", "description": "Text shown once the interview finishes."}, "completion_url": {"type": "string", "description": "Where a finishing participant is redirected; empty when unset."}, "max_interviews": {"type": "integer", "nullable": true, "description": "Cap on completed interviews; `null` for no cap."}, "created": {"type": "string", "format": "date-time", "description": "When the study was created, UTC."}, "question_count": {"type": "integer", "description": "Number of questions in the guide."}, "has_screener": {"type": "boolean", "description": "Whether a screener is attached to this study (a screener with no questions still reports `true`)."}}, "required": ["active", "completion_url", "created", "end_message", "has_screener", "id", "interview_method", "language_code", "max_interviews", "moderation_type", "name", "project_id", "question_count", "status", "visual_intelligence_enabled", "welcome_message", "workspace_id"]}, "StudyStatistics": {"type": "object", "description": "Aggregate counts and spend for one study. No participant-level data.", "properties": {"study_id": {"type": "string", "format": "uuid", "description": "The study these figures describe."}, "summary": {"allOf": [{"$ref": "#/components/schemas/StudyStatisticsSummary"}], "description": "Participant flow through the study."}, "costs": {"allOf": [{"$ref": "#/components/schemas/StudyStatisticsCosts"}], "description": "Settled recruitment spend, in USD."}, "screener_questions": {"type": "array", "items": {"$ref": "#/components/schemas/StudyQuestionStat"}, "description": "Reach and answer counts per screener question, in order."}, "questions": {"type": "array", "items": {"$ref": "#/components/schemas/StudyQuestionStat"}, "description": "Reach and answer counts per study question, in order."}}, "required": ["costs", "questions", "screener_questions", "study_id", "summary"]}, "StudyStatisticsCosts": {"type": "object", "description": "Settled recruitment spend on this study, in USD. Every figure is positive.", "properties": {"recruitment_created_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Charged when recruitment was first created."}, "recruitment_participants_increased_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Charged when the participant target was raised."}, "recruitment_incentive_increased_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Charged when the per-participant incentive was raised."}, "screened_out_bonus_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Paid to participants who were screened out."}, "incentive_payout_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Paid to participants who completed."}, "refund_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Refunded to the wallet."}, "concierge_recruitment_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,10}(?:\\.\\d{0,2})?$", "description": "Charged for Outset-managed recruitment."}}, "required": ["concierge_recruitment_usd", "incentive_payout_usd", "recruitment_created_usd", "recruitment_incentive_increased_usd", "recruitment_participants_increased_usd", "refund_usd", "screened_out_bonus_usd"]}, "StudyStatisticsResponse": {"type": "object", "description": "Statistics envelope.", "properties": {"data": {"$ref": "#/components/schemas/StudyStatistics"}}, "required": ["data"]}, "StudyStatisticsSummary": {"type": "object", "description": "Participant flow through the study, from arrival to completion.\n\nEvery count spans the whole flow, not only completed interviews \u2014 the numbers exist to show\ndrop-off. The billable answered-question figure is a different number and is not reported here.", "properties": {"total_interviews": {"type": "integer", "description": "Interviews that started or were screened out. Not limited to completed ones."}, "total_completed": {"type": "integer", "description": "Interviews that finished and passed quality checks."}, "first_completed_at": {"type": "string", "format": "date-time", "nullable": true, "description": "When the first interview completed; `null` if none has."}, "last_completed_at": {"type": "string", "format": "date-time", "nullable": true, "description": "When the most recent interview completed; `null` if none has."}, "total_screened_out": {"type": "integer", "description": "Participants the screener rejected."}, "total_over_quota": {"type": "integer", "description": "Participants turned away because their quota was full."}, "total_fraud": {"type": "integer", "description": "Interviews flagged as fraudulent."}, "total_incomplete": {"type": "integer", "description": "Interviews that started, were not screened out, and neither finished nor met quality checks."}, "total_archived": {"type": "integer", "description": "Interviews archived by a researcher."}, "total_active_sessions": {"type": "integer", "description": "Interviews currently in progress."}, "screen_out_limit_usd": {"type": "string", "format": "decimal", "pattern": "^-?\\d{0,8}(?:\\.\\d{0,2})?$", "nullable": true, "description": "Cap on what this study will pay out for screen-outs; `null` when uncapped."}}, "required": ["first_completed_at", "last_completed_at", "screen_out_limit_usd", "total_active_sessions", "total_archived", "total_completed", "total_fraud", "total_incomplete", "total_interviews", "total_over_quota", "total_screened_out"]}, "StudyStatusEnum": {"enum": ["DRAFT", "LIVE", "PAUSED", "CLOSED"], "type": "string", "description": "* `DRAFT` - DRAFT\n* `LIVE` - LIVE\n* `PAUSED` - PAUSED\n* `CLOSED` - CLOSED"}, "Template": {"type": "object", "description": "One study or screener template, and the workspaces it is shared with.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the template."}, "name": {"type": "string", "description": "Template name as its researchers see it."}, "description": {"type": "string", "description": "What the template is for. Always empty for a screener template."}, "is_global": {"type": "boolean", "description": "True for a template Outset provides to every organization. Its sharing cannot be changed. Always false for a screener template."}, "organization_wide": {"type": "boolean", "description": "True when the template is available everywhere in the organization \u2014 its shared-workspace list is empty. **Read `workspaces` together with this field.** An empty `workspaces` with `organization_wide` false means the template is restricted to workspaces you cannot see, not that it is shared with none."}, "workspaces": {"type": "array", "items": {"$ref": "#/components/schemas/Workspace"}, "description": "The workspaces this template is available in that are visible to you. Archived workspaces and the hidden container the Simulations product uses never appear here, so this list can be empty while the template is still restricted \u2014 see `organization_wide`."}}, "required": ["description", "id", "is_global", "name", "organization_wide", "workspaces"]}, "TemplateListResponse": {"type": "object", "description": "Template list envelope.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Template"}}, "next_cursor": {"type": "string", "nullable": true}, "has_more": {"type": "boolean"}}, "required": ["data", "has_more", "next_cursor"]}, "TemplateResponse": {"type": "object", "description": "Single-template envelope.", "properties": {"data": {"$ref": "#/components/schemas/Template"}}, "required": ["data"]}, "TwinPanelThread": {"type": "object", "properties": {"panel_thread_id": {"type": "string", "format": "uuid", "description": "Panel conversation identifier."}, "title": {"type": "string", "nullable": true, "description": "Conversation title; `null` before it is seeded."}, "status": {"type": "string", "description": "Current thread status."}, "created": {"type": "string", "format": "date-time", "description": "Creation time (ISO 8601, UTC)."}, "modified": {"type": "string", "format": "date-time", "description": "Last completed-turn time (ISO 8601, UTC)."}, "participant_count": {"type": "integer", "minimum": 0}, "error": {"allOf": [{"$ref": "#/components/schemas/_TwinPanelError"}], "nullable": true, "description": "Failure information for the current panel turn; `null` unless status is `ERROR`."}, "snapshot": {"$ref": "#/components/schemas/_TwinPanelSnapshot"}}, "required": ["created", "error", "modified", "panel_thread_id", "participant_count", "snapshot", "status", "title"]}, "TwinPanelThreadResponse": {"type": "object", "properties": {"data": {"$ref": "#/components/schemas/TwinPanelThread"}}, "required": ["data"]}, "TwinPanelTurnRequest": {"type": "object", "properties": {"question": {"type": "string", "description": "Question to ask the twin panel.", "maxLength": 8000}, "panel_thread_id": {"type": "string", "format": "uuid", "nullable": true, "description": "Existing panel thread to continue; omit to create one."}, "audience": {"allOf": [{"$ref": "#/components/schemas/_TwinPanelAudience"}], "nullable": true, "description": "Optional documented twin audience selection for this turn."}}, "required": ["question"]}, "TwinProfile": {"type": "object", "description": "One twin as its profile (`GET /v2/twins/{twin_id}/`) reads it \u2014 the roster fields plus provenance.", "properties": {"twin_id": {"type": "string", "format": "uuid", "description": "Stable twin identifier. Use it on `GET /v2/twins/{twin_id}/`."}, "label": {"type": "string", "description": "Derived library label for the twin, e.g. `twin-35-acme`. Not a name."}, "name": {"type": "string", "description": "Derived display pseudonym, e.g. `Twin-35`. Not a real person's name."}, "archetype": {"allOf": [{"$ref": "#/components/schemas/_TwinArchetype"}], "nullable": true, "description": "The twin's persona archetype, or `null`."}, "audiences": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinAudience"}, "description": "Audiences (batches) this twin belongs to that your organization owns."}, "attributes": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinAttribute"}, "description": "The twin's attribute values, from your organization's attribute vocabulary."}, "activity_count": {"type": "integer", "minimum": 0, "description": "How many times this twin has answered for your organization across panels, chats, and simulations."}, "grounded_at": {"type": "string", "format": "date-time", "nullable": true, "description": "When the underlying research was collected (ISO 8601, UTC); `null` if never grounded."}, "last_built_at": {"type": "string", "format": "date-time", "nullable": true, "description": "When the twin's current persona core finished generating (ISO 8601, UTC); `null` until built."}, "is_shared": {"type": "boolean", "description": "Whether the twin comes from the shared house library rather than being owned by your organization."}, "grounding_confidence": {"allOf": [{"$ref": "#/components/schemas/_TwinGroundingConfidence"}], "nullable": true, "description": "Average grounding confidence across the answers this twin gave your organization, as a band; `null` when it has answered nothing yet."}}, "required": ["activity_count", "archetype", "attributes", "audiences", "grounded_at", "grounding_confidence", "is_shared", "label", "last_built_at", "name", "twin_id"]}, "TwinProfileResponse": {"type": "object", "description": "``GET /v2/twins/{twin_id}/`` envelope.", "properties": {"data": {"$ref": "#/components/schemas/TwinProfile"}}, "required": ["data"]}, "TwinRow": {"type": "object", "description": "One twin as the roster (`GET /v2/twins/`) reads it.\n\n``label`` and ``name`` are derived pseudonyms (e.g. ``twin-35-acme`` /\n``Twin-35``), never a real person's name.", "properties": {"twin_id": {"type": "string", "format": "uuid", "description": "Stable twin identifier. Use it on `GET /v2/twins/{twin_id}/`."}, "label": {"type": "string", "description": "Derived library label for the twin, e.g. `twin-35-acme`. Not a name."}, "name": {"type": "string", "description": "Derived display pseudonym, e.g. `Twin-35`. Not a real person's name."}, "archetype": {"allOf": [{"$ref": "#/components/schemas/_TwinArchetype"}], "nullable": true, "description": "The twin's persona archetype, or `null`."}, "audiences": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinAudience"}, "description": "Audiences (batches) this twin belongs to that your organization owns."}, "attributes": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinAttribute"}, "description": "The twin's attribute values, from your organization's attribute vocabulary."}, "activity_count": {"type": "integer", "minimum": 0, "description": "How many times this twin has answered for your organization across panels, chats, and simulations."}}, "required": ["activity_count", "archetype", "attributes", "audiences", "label", "name", "twin_id"]}, "TwinRowListResponse": {"type": "object", "description": "Paginated envelope for ``GET /v2/twins/``.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/TwinRow"}}, "next_cursor": {"type": "string", "nullable": true, "description": "Pass as the `cursor` query parameter to fetch the next page; `null` on the last page."}, "has_more": {"type": "boolean", "description": "Whether more pages remain after this one."}}, "required": ["data", "has_more", "next_cursor"]}, "TwinUsage": {"type": "object", "description": "The organization's twins answered-question usage for the current billing month.\n\nTwins bill on answers actually received, metered per answered question. This\nis a separate contract from core Outset usage \u2014 never summed with it.", "properties": {"used": {"type": "integer", "minimum": 0, "description": "Answered questions billed to your organization so far this month."}, "cap": {"type": "integer", "nullable": true, "description": "Monthly answered-question allowance; `null` when your organization runs uncapped."}, "remaining": {"type": "integer", "nullable": true, "description": "Answered questions left before the cap (`cap - used`, floored at 0); `null` when uncapped."}, "is_exhausted": {"type": "boolean", "description": "Whether the monthly allowance is spent. Always `false` for an uncapped organization."}, "overage_active": {"type": "boolean", "description": "Whether turns still proceed despite the allowance being spent, billing as overage. When `true`, `is_exhausted` is `true` but your organization keeps being admitted at overage rates \u2014 so do not treat `is_exhausted`/`remaining == 0` as a stop signal."}, "period_start": {"type": "string", "format": "date-time", "description": "Start of the current usage month (ISO 8601, UTC)."}, "resets_at": {"type": "string", "format": "date-time", "description": "When the current usage month ends and the count resets (UTC)."}}, "required": ["cap", "is_exhausted", "overage_active", "period_start", "remaining", "resets_at", "used"]}, "TwinUsageResponse": {"type": "object", "description": "``GET /v2/twins/usage/`` envelope.", "properties": {"data": {"$ref": "#/components/schemas/TwinUsage"}}, "required": ["data"]}, "TypeEnum": {"enum": ["TEXT", "MULTIPLE_CHOICE", "MULTIPLE_SELECT", "NUMBER_INPUT", "TASK", "RATING", "STACK_RANK", "INFO_ONLY", "TYPED_RESPONSE", "MATRIX", "IMAGE_CODESIGN"], "type": "string", "description": "* `TEXT` - TEXT\n* `MULTIPLE_CHOICE` - MULTIPLE_CHOICE\n* `MULTIPLE_SELECT` - MULTIPLE_SELECT\n* `NUMBER_INPUT` - NUMBER_INPUT\n* `TASK` - TASK\n* `RATING` - RATING\n* `STACK_RANK` - STACK_RANK\n* `INFO_ONLY` - INFO_ONLY\n* `TYPED_RESPONSE` - TYPED_RESPONSE\n* `MATRIX` - MATRIX\n* `IMAGE_CODESIGN` - IMAGE_CODESIGN"}, "UsageLogin": {"type": "object", "description": "One login event nested under a user in ``GET /v2/usage/``.\n\nA login row is an auth-event audit record from ``users_usersession``\n(one row per credentials / Google / SAML / OIDC login). It is **not**\na session in any engagement sense \u2014 for that, see\n``engagement_session_count`` on the user or ``GET /v2/usage/sessions/``.", "properties": {"login_id": {"type": "string", "format": "uuid", "description": "Unique ID of this login event in the auth audit log."}, "login_at": {"type": "string", "format": "date-time", "description": "When the login occurred (ISO 8601 timestamp, UTC)."}, "auth_method": {"type": "string", "description": "How the user authenticated \u2014 e.g. `credentials`, `google`, `saml`, or `oidc`."}}, "required": ["auth_method", "login_at", "login_id"]}, "UsageLoginItem": {"type": "object", "description": "One login event as returned by the flat ``GET /v2/usage/logins/`` list.", "properties": {"login_id": {"type": "string", "format": "uuid", "description": "Unique ID of this login event in the auth audit log."}, "user_id": {"type": "string", "format": "uuid", "nullable": true, "description": "Outset user ID that logged in, or `null` if no current user maps to the email."}, "user_email": {"type": "string", "format": "email", "description": "Email of the user that logged in."}, "login_at": {"type": "string", "format": "date-time", "description": "When the login occurred (ISO 8601 timestamp, UTC)."}, "auth_method": {"type": "string", "description": "How the user authenticated \u2014 e.g. `credentials`, `google`, `saml`, or `oidc`."}}, "required": ["auth_method", "login_at", "login_id", "user_email", "user_id"]}, "UsageLoginsResponse": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UsageLoginItem"}}, "next_cursor": {"type": "string", "nullable": true, "description": "Pass as the `cursor` query parameter to fetch the next page; `null` on the last page."}, "has_more": {"type": "boolean", "description": "Whether more pages remain after this one."}}, "required": ["data", "has_more", "next_cursor"]}, "UsageProject": {"type": "object", "description": "One project's interview-minute rollup, as returned by ``GET /v2/usage/projects/``.\n\nAggregates completed interviews by the project that owns the study they\nbelong to. The filtering semantics match the per-user duration rollup on\n``GET /v2/usage/``: only non-fraud, non-archived, completed interviews of a\nvalid type are counted, and low-quality interviews are excluded from the\n``valid`` count and the duration sum.", "properties": {"project_id": {"type": "string", "format": "uuid", "description": "ID of the project the interviews' studies belong to."}, "project_name": {"type": "string", "description": "Name of the project."}, "interview_completes_raw": {"type": "integer", "minimum": 0, "description": "Completed interviews across this project's studies in the date range, including low-quality ones (excludes fraudulent and archived interviews)."}, "interview_completes_valid": {"type": "integer", "minimum": 0, "description": "The high-quality subset of `interview_completes_raw` \u2014 low-quality interviews excluded."}, "total_interview_duration_seconds": {"type": "number", "format": "double", "minimum": 0, "description": "Total recorded duration, in seconds, of the valid (non-low-quality) completed interviews."}}, "required": ["interview_completes_raw", "interview_completes_valid", "project_id", "project_name", "total_interview_duration_seconds"]}, "UsageProjectsResponse": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UsageProject"}}, "next_cursor": {"type": "string", "nullable": true, "description": "Pass as the `cursor` query parameter to fetch the next page; `null` on the last page."}, "has_more": {"type": "boolean", "description": "Whether more pages remain after this one."}}, "required": ["data", "has_more", "next_cursor"]}, "UsageResponse": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UsageUser"}}, "next_cursor": {"type": "string", "nullable": true, "description": "Pass as the `cursor` query parameter to fetch the next page; `null` on the last page."}, "has_more": {"type": "boolean", "description": "Whether more pages remain after this one."}}, "required": ["data", "has_more", "next_cursor"]}, "UsageSessionItem": {"type": "object", "description": "One engagement session as returned by ``GET /v2/usage/sessions/``.\n\n``session_id`` is the PostHog ``$session_id`` \u2014 a 30-min inactivity\nbucket assigned by posthog-js, **not** a login event. To correlate\nsessions with logins, join on ``user_id`` and timestamp range.", "properties": {"user_id": {"type": "string", "format": "uuid", "description": "Outset user ID the engagement session belongs to."}, "session_id": {"type": "string", "description": "PostHog `$session_id` \u2014 a 30-minute inactivity bucket, not a login event."}, "started_at": {"type": "string", "format": "date-time", "description": "Start of the session: its first event (ISO 8601, UTC)."}, "ended_at": {"type": "string", "format": "date-time", "description": "End of the session: its last recorded event (ISO 8601, UTC)."}, "duration_seconds": {"type": "integer", "minimum": 0, "description": "Elapsed time from the first to the last event in the session, in seconds."}, "event_count": {"type": "integer", "minimum": 0, "description": "Number of product events recorded in the session."}}, "required": ["duration_seconds", "ended_at", "event_count", "session_id", "started_at", "user_id"]}, "UsageSessionsResponse": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UsageSessionItem"}}, "next_cursor": {"type": "string", "nullable": true, "description": "Pass as the `cursor` query parameter to fetch the next page; `null` on the last page."}, "has_more": {"type": "boolean", "description": "Whether more pages remain after this one."}}, "required": ["data", "has_more", "next_cursor"]}, "UsageStudiesResponse": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/UsageStudy"}}, "next_cursor": {"type": "string", "nullable": true, "description": "Pass as the `cursor` query parameter to fetch the next page; `null` on the last page."}, "has_more": {"type": "boolean", "description": "Whether more pages remain after this one."}}, "required": ["data", "has_more", "next_cursor"]}, "UsageStudy": {"type": "object", "description": "One study's interview-minute rollup, as returned by ``GET /v2/usage/studies/``.\n\nAggregates completed interviews by the study they belong to. The filtering\nsemantics match the per-user duration rollup on ``GET /v2/usage/`` (see\n``UsageProjectSerializer``). ``project_id`` lets you correlate a study back\nto the per-project rollup; it is ``null`` for studies not in any project.", "properties": {"study_id": {"type": "string", "format": "uuid", "description": "ID of the study (interview definition)."}, "study_name": {"type": "string", "description": "Name of the study."}, "project_id": {"type": "string", "format": "uuid", "nullable": true, "description": "ID of the project this study belongs to, or `null` if it is not in a project."}, "interview_completes_raw": {"type": "integer", "minimum": 0, "description": "Completed interviews on this study in the date range, including low-quality ones (excludes fraudulent and archived interviews)."}, "interview_completes_valid": {"type": "integer", "minimum": 0, "description": "The high-quality subset of `interview_completes_raw` \u2014 low-quality interviews excluded."}, "total_interview_duration_seconds": {"type": "number", "format": "double", "minimum": 0, "description": "Total recorded duration, in seconds, of the valid (non-low-quality) completed interviews."}}, "required": ["interview_completes_raw", "interview_completes_valid", "project_id", "study_id", "study_name", "total_interview_duration_seconds"]}, "UsageUser": {"type": "object", "properties": {"user_id": {"type": "string", "format": "uuid", "nullable": true, "description": "Outset user ID, or `null` when no current user maps to this email (e.g. a removed account)."}, "email": {"type": "string", "format": "email", "description": "Email identifying the user. Rows are keyed and ordered by this value."}, "logins": {"type": "array", "items": {"$ref": "#/components/schemas/UsageLogin"}, "description": "Login events for this user in the date range. Empty if they created work or had engagement sessions but no login event in the range."}, "engagement_session_count": {"type": "integer", "minimum": 0, "description": "Count of distinct product-usage sessions (PostHog 30-minute inactivity buckets) \u2014 actual active usage, which a single login can span several of, or none."}, "projects_created": {"type": "integer", "minimum": 0, "description": "Projects this user created in the date range (excludes demo and template projects)."}, "studies_created": {"type": "integer", "minimum": 0, "description": "Studies this user created in the date range (excludes templates; includes soft-deleted studies)."}, "interview_completes_raw": {"type": "integer", "minimum": 0, "description": "Completed interviews on this user's studies in the date range, including low-quality ones (excludes fraudulent and archived interviews)."}, "interview_completes_valid": {"type": "integer", "minimum": 0, "description": "The high-quality subset of `interview_completes_raw` \u2014 low-quality interviews excluded."}, "total_interview_duration_seconds": {"type": "number", "format": "double", "minimum": 0, "description": "Total recorded duration, in seconds, of the valid (non-low-quality) completed interviews."}}, "required": ["email", "engagement_session_count", "interview_completes_raw", "interview_completes_valid", "logins", "projects_created", "studies_created", "total_interview_duration_seconds", "user_id"]}, "User": {"type": "object", "description": "One member of a partner-managed organization.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the person."}, "email": {"type": "string", "format": "email", "description": "Email address, which is also their login identity."}, "name": {"type": "string", "description": "Full display name. Empty until the person completes their profile."}, "role": {"type": "string", "description": "Their role in this organization."}, "is_active": {"type": "boolean", "description": "Whether the Outset account can log in at all. False for a deprovisioned account. This is global to the person, not scoped to this organization."}, "joined": {"type": "string", "format": "date-time", "description": "When they became a member of this organization, UTC."}}, "required": ["email", "id", "is_active", "joined", "name", "role"]}, "UserDetail": {"type": "object", "description": "One member, read on their own route.\n\nCarries the workspace the list row omits: resolving it costs up to three queries per person,\nwhich a page of members cannot afford.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Outset's identifier for the person."}, "email": {"type": "string", "format": "email", "description": "Email address, which is also their login identity."}, "name": {"type": "string", "description": "Full display name. Empty until the person completes their profile."}, "role": {"type": "string", "description": "Their role in this organization."}, "is_active": {"type": "boolean", "description": "Whether the Outset account can log in at all. False for a deprovisioned account. This is global to the person, not scoped to this organization."}, "joined": {"type": "string", "format": "date-time", "description": "When they became a member of this organization, UTC."}, "current_workspace": {"allOf": [{"$ref": "#/components/schemas/Workspace"}], "nullable": true, "description": "The workspace this member's session resolves into \u2014 the one they last used, otherwise the organization's default. `null` only when they belong to no workspace at all. Read-only: workspaces are not managed through this API."}}, "required": ["current_workspace", "email", "id", "is_active", "joined", "name", "role"]}, "UserListResponse": {"type": "object", "description": "``GET /v2/users/`` envelope.", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/User"}}, "next_cursor": {"type": "string", "nullable": true}, "has_more": {"type": "boolean"}}, "required": ["data", "has_more", "next_cursor"]}, "UserResponse": {"type": "object", "description": "Single-member envelope.", "properties": {"data": {"$ref": "#/components/schemas/UserDetail"}}, "required": ["data"]}, "UserUpdateRoleEnum": {"enum": ["ADMIN", "MANAGER", "USER", "PWDR_USER", "OBSERVER"], "type": "string", "description": "* `ADMIN` - ADMIN\n* `MANAGER` - MANAGER\n* `USER` - USER\n* `PWDR_USER` - PWDR_USER\n* `OBSERVER` - OBSERVER"}, "Workspace": {"type": "object", "description": "A workspace inside a partner-managed organization.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Workspace identifier."}, "name": {"type": "string", "description": "Workspace name as its members see it."}, "is_default": {"type": "boolean", "description": "True for the workspace created with the organization. Every organization has exactly one."}}, "required": ["id", "is_default", "name"]}, "_MessageMatrixSelection": {"type": "object", "properties": {"row_question_id": {"type": "string", "format": "uuid", "description": "ID of the matrix row (child question) being answered."}, "option_id": {"type": "string", "format": "uuid", "description": "ID of the option the participant selected for that row. Resolve to display text via `selected_options` on the same message."}}, "required": ["option_id", "row_question_id"]}, "_MessageParticipantUpload": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "content_type": {"type": "string"}, "is_redacted": {"type": "boolean"}, "url": {"type": "string", "nullable": true}}, "required": ["content_type", "id", "is_redacted"]}, "_MessageRecording": {"type": "object", "properties": {"duration_seconds": {"type": "number", "format": "double", "nullable": true}, "media_type": {"type": "string", "nullable": true}, "url": {"type": "string", "nullable": true}}, "required": ["duration_seconds", "media_type"]}, "_MessageRow": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "order": {"type": "integer"}, "role": {"$ref": "#/components/schemas/_MessageRowRoleEnum"}, "content": {"type": "string"}, "created_at": {"type": "string"}, "is_end": {"type": "boolean"}, "question_id": {"type": "string", "format": "uuid", "nullable": true}, "engagement_time_seconds": {"type": "number", "format": "double", "nullable": true}, "is_redacted": {"type": "boolean"}, "recording": {"allOf": [{"$ref": "#/components/schemas/_MessageRecording"}], "nullable": true}, "participant_uploads": {"type": "array", "items": {"$ref": "#/components/schemas/_MessageParticipantUpload"}}, "participant_skipped": {"type": "boolean"}, "selected_options": {"type": "array", "items": {"$ref": "#/components/schemas/_MessageSelectedOption"}, "description": "Options the participant picked on a closed-ended question (multiple choice, multiple select, stack rank, matrix), in pick order \u2014 rank order for stack-rank questions. Empty for other messages."}, "matrix_selections": {"type": "array", "items": {"$ref": "#/components/schemas/_MessageMatrixSelection"}, "nullable": true, "description": "Per-row selections for matrix answers; `null` for non-matrix messages."}, "numeric_value": {"type": "number", "format": "double", "nullable": true, "description": "Numeric answer to a rating or number question; `null` when the message is not a plain numeric answer."}, "task_status": {"type": "string", "nullable": true, "description": "Task outcome (`COMPLETE` / `INCOMPLETE`) for task questions; `null` for other messages."}, "task_duration_seconds": {"type": "integer", "nullable": true, "description": "Seconds the participant spent on the task; `null` for other messages."}}, "required": ["content", "created_at", "engagement_time_seconds", "id", "is_end", "is_redacted", "matrix_selections", "numeric_value", "order", "participant_skipped", "participant_uploads", "question_id", "recording", "role", "selected_options", "task_duration_seconds", "task_status"]}, "_MessageRowRoleEnum": {"enum": ["PARTICIPANT", "INTERVIEWER", "SYSTEM"], "type": "string", "description": "* `PARTICIPANT` - PARTICIPANT\n* `INTERVIEWER` - INTERVIEWER\n* `SYSTEM` - SYSTEM"}, "_MessageSelectedOption": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid", "description": "ID of the question option the participant picked."}, "text": {"type": "string", "description": "Display text of the option."}, "custom_text": {"type": "string", "nullable": true, "description": "Participant-typed text when the picked option is an \"Other\" write-in; `null` otherwise."}}, "required": ["custom_text", "id", "text"]}, "_ScreenerAnswerOptionRow": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "text": {"type": "string"}}, "required": ["id", "text"]}, "_ScreenerAnswerRow": {"type": "object", "properties": {"question_id": {"type": "string", "format": "uuid"}, "question_text": {"type": "string"}, "selected_options": {"type": "array", "items": {"$ref": "#/components/schemas/_ScreenerAnswerOptionRow"}}, "text": {"type": "string"}, "participant_skipped": {"type": "boolean"}, "image_upload": {"allOf": [{"$ref": "#/components/schemas/_ScreenerImageUploadRow"}], "nullable": true}}, "required": ["participant_skipped", "question_id", "question_text", "selected_options", "text"]}, "_ScreenerImageUploadRow": {"type": "object", "properties": {"id": {"type": "string", "format": "uuid"}, "content_type": {"type": "string"}, "status": {"type": "string"}, "meets_criterion": {"type": "boolean", "nullable": true}, "analysis": {"type": "string"}, "is_redacted": {"type": "boolean"}, "presigned_image_url": {"type": "string", "nullable": true}}, "required": ["analysis", "content_type", "id", "is_redacted", "meets_criterion", "presigned_image_url", "status"]}, "_SimulationLaunchError": {"type": "object", "properties": {"field": {"type": "string", "nullable": true, "description": "The offending field, or `null` for a study-level refusal."}, "detail": {"type": "string", "description": "Human-readable refusal reason."}, "code": {"type": "string", "description": "Stable machine-readable refusal code to switch on, e.g. `NO_TWINS_AVAILABLE`, `SIMULATION_IN_PROGRESS`, `CONFIGURATION_CHANGED`, `SIMULATION_USAGE_LIMIT_REACHED`, `RATE_LIMITED`, `UNSUPPORTED_GUIDE`."}}, "required": ["code", "detail", "field"]}, "_SimulationRun": {"type": "object", "description": "Progress of a simulation study's most recent run.\n\nEvery count is scoped to *this run's* interviews, never the study-wide set, so\na half-done run reads as partial rather than as a finished study.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Identifier of the most recent run."}, "phase": {"type": "string", "description": "Run phase: `ACTIVE` (in progress), `PARTIAL` (some terminal, not all), or `COMPLETED`."}, "outcome": {"type": "string", "nullable": true, "description": "`STOPPED` once the run was stopped early (its `phase` stays `ACTIVE` until the remaining interviews are closed); otherwise `null`."}, "requested": {"type": "integer", "minimum": 0, "description": "Number of twins requested for this run."}, "active": {"type": "integer", "minimum": 0, "description": "Interviews in this run still in progress."}, "completed": {"type": "integer", "minimum": 0, "description": "Genuine finishers in this run (excludes screened-out and failed)."}, "screened_out": {"type": "integer", "minimum": 0, "description": "Interviews in this run that screened out."}, "failed": {"type": "integer", "minimum": 0, "description": "Interviews in this run flagged fraudulent or low-quality."}}, "required": ["active", "completed", "failed", "id", "outcome", "phase", "requested", "screened_out"]}, "_TwinArchetype": {"type": "object", "description": "The persona archetype a twin was grounded into, or ``null`` when it has none.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Archetype identifier."}, "label": {"type": "string", "description": "Short archetype name, e.g. a persona label."}, "description": {"type": "string", "description": "Longer description of the archetype; may be empty."}}, "required": ["description", "id", "label"]}, "_TwinAttribute": {"type": "object", "description": "One attribute value on the twin, from the organization's own attribute vocabulary.", "properties": {"key": {"type": "string", "description": "Machine-readable attribute key, unique within the organization."}, "label": {"type": "string", "description": "Human-readable attribute name."}, "value": {"type": "string", "description": "The twin's value for this attribute."}}, "required": ["key", "label", "value"]}, "_TwinAudience": {"type": "object", "description": "One audience (batch) this twin belongs to, scoped to the token's organization.", "properties": {"id": {"type": "string", "format": "uuid", "description": "Audience identifier."}, "name": {"type": "string", "description": "Audience (batch) display name, as the organization set it."}, "description": {"type": "string", "nullable": true, "description": "Audience description; may be empty or `null`."}}, "required": ["description", "id", "name"]}, "_TwinGroundingConfidence": {"type": "object", "description": "The twin's grounding-confidence band. No numeric score ever crosses the wire \u2014 a band only.", "properties": {"band": {"type": "string", "description": "Traffic-light confidence band, e.g. `low` / `medium` / `high`."}}, "required": ["band"]}, "_TwinPanelAnswer": {"type": "object", "properties": {"message_id": {"type": "string", "description": "Assistant message owning this answer."}, "question_index": {"type": "integer", "minimum": 0, "description": "Question position within the turn."}, "question": {"type": "string", "description": "Question asked of this twin."}, "answer": {"type": "string", "description": "Twin-generated answer."}, "confidence": {"allOf": [{"$ref": "#/components/schemas/_TwinPanelConfidence"}], "nullable": true}}, "required": ["answer", "confidence", "message_id", "question", "question_index"]}, "_TwinPanelAttribute": {"type": "object", "properties": {"label": {"type": "string", "description": "Attribute display label."}, "value": {"type": "string", "description": "Twin's value for this attribute."}}, "required": ["label", "value"]}, "_TwinPanelAudience": {"type": "object", "properties": {"groups": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelAudienceGroup"}}}, "required": ["groups"]}, "_TwinPanelAudienceFilter": {"type": "object", "properties": {"attribute_key": {"type": "string", "description": "Twin attribute key to filter, such as age or industry."}, "values": {"type": "array", "items": {"type": "string"}, "description": "Selected values for this attribute."}}, "required": ["attribute_key", "values"]}, "_TwinPanelAudienceGroup": {"type": "object", "properties": {"audience_id": {"type": "string", "format": "uuid", "nullable": true, "description": "Named audience id; null selects from the whole twin library."}, "filters": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelAudienceFilter"}}}}, "_TwinPanelConfidence": {"type": "object", "properties": {"band": {"type": "string", "description": "Banded confidence; no raw score is exposed."}}, "required": ["band"]}, "_TwinPanelConfidenceByTurn": {"type": "object", "properties": {"message_id": {"type": "string"}, "confidence": {"$ref": "#/components/schemas/_TwinPanelTopline"}, "questions": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelQuestionConfidence"}}}, "required": ["confidence", "message_id", "questions"]}, "_TwinPanelError": {"type": "object", "properties": {"code": {"type": "string", "description": "Stable failure code: `NO_TWINS_MATCHED` when the audience reached no twins, or `PANEL_FAILED` for another panel failure."}, "message": {"type": "string", "description": "Caller-safe guidance for correcting or retrying the panel request."}}, "required": ["code", "message"]}, "_TwinPanelParticipant": {"type": "object", "properties": {"subject_id": {"type": "string", "format": "uuid", "description": "Stable twin identifier."}, "handle": {"type": "string", "description": "Citation handle used in the synthesis."}, "name": {"type": "string", "description": "Derived twin pseudonym."}, "display_label": {"type": "string", "description": "Derived library label."}, "persona": {"type": "string", "description": "Archetype tagline; never the persona core."}, "attributes": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelAttribute"}}, "answers": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelAnswer"}}, "overall_confidence": {"allOf": [{"$ref": "#/components/schemas/_TwinPanelTopline"}], "nullable": true, "description": "This twin's grounding averaged across all of its scored answers; `null` when none scored."}, "overall_confidence_by_turn": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelTurnTopline"}, "description": "The same aggregate scoped to one turn, one row per assistant turn that produced scored answers, in turn order. A turn with no scored answer has no row."}}, "required": ["answers", "attributes", "display_label", "handle", "name", "overall_confidence", "overall_confidence_by_turn", "persona", "subject_id"]}, "_TwinPanelQuestion": {"type": "object", "properties": {"question": {"type": "string", "description": "A question asked of the panel."}}, "required": ["question"]}, "_TwinPanelQuestionConfidence": {"type": "object", "properties": {"question_index": {"type": "integer", "minimum": 0}, "question": {"type": "string"}, "confidence": {"allOf": [{"$ref": "#/components/schemas/_TwinPanelTopline"}], "nullable": true}}, "required": ["confidence", "question", "question_index"]}, "_TwinPanelSnapshot": {"type": "object", "properties": {"questions": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelQuestion"}}, "participants": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelParticipant"}}, "topline_confidence": {"allOf": [{"$ref": "#/components/schemas/_TwinPanelTopline"}], "nullable": true}, "confidence_by_turn": {"type": "array", "items": {"$ref": "#/components/schemas/_TwinPanelConfidenceByTurn"}}}, "required": ["confidence_by_turn", "participants", "questions", "topline_confidence"]}, "_TwinPanelTopline": {"type": "object", "description": "One band over a set of scored cells. Serves the whole panel, one twin, and one twin's turn.", "properties": {"band": {"type": "string", "description": "Confidence band over the cells this aggregate covers."}, "answer_count": {"type": "integer", "minimum": 0}, "counts": {"$ref": "#/components/schemas/_TwinPanelToplineCounts"}}, "required": ["answer_count", "band", "counts"]}, "_TwinPanelToplineCounts": {"type": "object", "properties": {"HIGH": {"type": "integer", "minimum": 0}, "MEDIUM": {"type": "integer", "minimum": 0}, "LOW": {"type": "integer", "minimum": 0}}, "required": ["HIGH", "LOW", "MEDIUM"]}, "_TwinPanelTurnTopline": {"type": "object", "description": "The same band, scoped to one assistant turn and carrying the turn it covers.", "properties": {"band": {"type": "string", "description": "Confidence band over the cells this aggregate covers."}, "answer_count": {"type": "integer", "minimum": 0}, "counts": {"$ref": "#/components/schemas/_TwinPanelToplineCounts"}, "message_id": {"type": "string", "description": "Assistant turn this aggregate covers; matches `answers[].message_id` on the same twin."}}, "required": ["answer_count", "band", "counts", "message_id"]}}, "securitySchemes": {"oauth2": {"type": "oauth2", "description": "OAuth 2.1 authorization code flow with PKCE. Register a client via `POST /users/oauth/register/` (Dynamic Client Registration) or the Developer settings page in the Outset app. Clients may also identify themselves with an `https` URL `client_id` that serves a Client ID Metadata Document (CIMD); see `/.well-known/oauth-authorization-server`.", "flows": {"authorizationCode": {"authorizationUrl": "https://outset.ai/oauth/authorize", "tokenUrl": "https://api.outset.ai/users/oauth/token/", "refreshUrl": "https://api.outset.ai/users/oauth/token/", "scopes": {"projects:read": "List projects, workspaces, and organization info", "projects:write": "Create, rename, and archive projects", "analytics:read": "View reports, search answers, and read insights", "analytics:write": "Create highlight reels, correct transcript text, and flag/redact PII", "studies:read": "View study guides, questions, and screener config", "studies:write": "Create and edit studies, questions, sections, and screeners", "studies:launch": "Publish, unpublish, or permanently close studies and launch or expand recruitment (may incur fees)", "templates:read": "View study and screener templates and where they are shared", "templates:write": "Save, edit, and delete screener templates, and change which workspaces templates are shared with", "usage:read": "View per-user usage metrics for the organization (sessions, projects, studies, completes)", "wallet:read": "View the organization's recruitment wallet balance", "twins:read": "Browse the digital-twin library and read twin panels, chats, and simulation results", "twins:write": "Configure twin simulations and audiences", "twins:interact": "Ask or chat with digital twins (may incur twin-usage costs) and read those conversations", "twins:launch": "Launch twin simulation studies (may incur twin-usage costs)"}}}}, "partnerAssertion": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "A short-lived assertion signed by the partner's own key and presented as a bearer credential (RFC 7523). It is verified against the key set published at the provider's registered `jwks_uri` \u2014 Outset issues no token for this flow.\n\nThe JWS header MUST declare `\"typ\": \"outset-partner-assertion+jwt\"`. This separates the assertion from the OIDC ID token the same provider signs for browser login, which is otherwise indistinguishable and is rejected here.\n\nRequired claims: `iss`, `aud`, `iat`, `exp`, `jti`, and `outset_provider`, the provider slug naming which key set verifies the signature. `outset_org` names the client organization the request acts on: organization-scoped endpoints require it, and endpoints operating across the whole estate reject it.\n\n`jti` is single-use, so a replayed assertion is refused. It must be at most 128 characters of `A-Z a-z 0-9 . _ : + / = -` \u2014 a UUID, hex, or base64 identifier all qualify, a braced GUID does not. Assertions older than five minutes are refused regardless of their own expiry. What a credential may reach is the scope set granted to its provider, re-read on every request. **A credential with no scopes granted reaches nothing**, and its refusals are indistinguishable from an invalid credential \u2014 that is the shipped state of every capability until an operator grants its scope, so a 403 on a route you expect to hold means asking us to grant it.\n\nWhile an organization is suspended its member and resource writes are refused and its reads are not, so a billing period that ends after a suspension is still reconcilable. The organization route itself stays writable, since setting its status back to `ACTIVE` is how a suspension is lifted; renaming stays open for the same reason. This differs from the legacy partner surface this API replaces, which refuses every request for a suspended organization.\n\nLists are cursor-paginated: follow `next_cursor` until `has_more` is false. There is no total-result count and no offset addressing."}}}, "servers": [{"url": "https://api.outset.ai", "description": "Production"}], "tags": [{"name": "Me", "description": "The identity behind the calling credential, and what that credential can reach."}, {"name": "Usage", "description": "Seat activity, engagement sessions, logins and interview minutes across a date range."}, {"name": "Recruitment wallet", "description": "The settled credit balance an organization spends on recruiting participants."}, {"name": "Organizations", "description": "The client organizations in a partner's estate, and each one's name and status."}, {"name": "Users", "description": "The people who belong to a client organization, and the role each one holds."}, {"name": "Projects", "description": "Folders grouping an organization's studies, and the interviews collected across one."}, {"name": "Studies", "description": "Research studies: their settings, statistics, interviews, and publication."}, {"name": "Study templates", "description": "Reusable study definitions, and the workspaces each one is shared with."}, {"name": "Screener templates", "description": "Reusable screener question sets, and the workspaces each one is shared with."}, {"name": "Interviews", "description": "A single participant's interview and its transcript."}, {"name": "Digital twins", "description": "Simulated respondents: the twin roster, simulated study runs, and panel conversations."}], "x-tagGroups": [{"name": "Account", "tags": ["Me", "Usage", "Recruitment wallet"]}, {"name": "Organizations", "tags": ["Organizations", "Users"]}, {"name": "Research", "tags": ["Projects", "Studies", "Study templates", "Screener templates", "Interviews"]}, {"name": "Digital twins", "tags": ["Digital twins"]}], "security": [{"oauth2": []}]}