{"openapi":"3.1.0","info":{"title":"Juno Public API","description":"The Juno API for AI-moderated research. A developer briefs Juno on a user's behalf; Juno creates a study in Test mode, where the user can simulate the interview shape without credits, analysis, or exports. The user selects Go live for real participant interviews in Live mode, where credits are deducted and analysis and exports are available. Each study has one durable invite link. Verified organisation members can create scoped API keys (jsk_live_… / jsk_test_…) in organisation settings, subject to the organisation's MFA policy. Assistants can also connect through OAuth with PKCE S256: the user approves a workspace and permissions, then the client receives scoped credentials. For programmatic signup and key creation, read https://api.heyjuno.co/v1/access/openapi.json (the same path is available on the selected API server). That flow uses a separate client-generated transaction secret, matching email proof and MFA when required, then explicit workspace selection. Research API keys cannot manage credentials.","version":"1.0.0"},"paths":{"/v1/api/me":{"get":{"tags":["Public API"],"summary":"Check your access","description":"Verify Juno credentials and see their organisation, API data environment\nand scopes. The credential environment is separate from a study's Test mode\nor Live mode. Any valid research credential may call this; no specific\nscope is required.","operationId":"whoami_v1_api_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/WhoAmIOut"},{"$ref":"#/components/schemas/OAuthWhoAmIOut"}],"title":"Response Whoami V1 Api Me Get"}}}}}}},"/v1/api/studies":{"get":{"tags":["Public API"],"summary":"List studies","description":"List your organisation's studies, newest created first. A study is one\nresearch project: an AI-moderated interview Juno designs and runs, plus the\nanalysis built from the answers. Page with the cursor from the X-Next-Cursor\nresponse header, which is absent on the last page.","operationId":"list_studies_v1_api_studies_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Page size — newest first, max 200.","default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page, taken from the previous response: over REST it arrives in the X-Next-Cursor response header, and over MCP it is the next_cursor field of the result, because JSON-RPC has no headers. Omit for the first page; a response without one is the last page. Cursors are keyset-based, so rows arriving mid-pagination never shift or duplicate the page you are walking.","title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicStudySummary"},"title":"Response List Studies V1 Api Studies Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:read","security":[{"ApiKey":[]},{"OAuth":["studies:read"]}]},"post":{"tags":["Public API"],"summary":"Create study","description":"Brief Juno to create a research study from plain-language guidance:\ndescribe what the user wants to learn, and Juno designs the interview — objectives, screener\nquestions, and the questions the interviewer will ask. Upload documents\nfirst and include their file_ids when they are part of the brief. Authoring runs in\nthe background: this returns a job_id; poll Check authoring status\nuntil the study and its brief are ready. New studies start in test mode so\nthe user can safely simulate the shape of the interview before choosing\nGo live. Test mode uses no credits, provides no analysis or exports,\nand is not intended for real participant research.","operationId":"create_study_v1_api_studies_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudyGuidanceRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthoringJobOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:write","security":[{"ApiKey":[]},{"OAuth":["studies:write"]}]}},"/v1/api/studies/{study_id}":{"get":{"tags":["Public API"],"summary":"Get study","description":"Fetch one study, including its current brief — the study design Juno\nauthored: title, objectives, and the questions the interviewer asks. The\nbrief is null until an authoring job has completed.","operationId":"get_study_v1_api_studies__study_id__get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicStudyDetail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:read","security":[{"ApiKey":[]},{"OAuth":["studies:read"]}]}},"/v1/api/studies/{study_id}/interview-link":{"get":{"tags":["Public API"],"summary":"Get invite link","description":"Get the study's invite link — the durable URL the user shares with\nparticipants. In Test mode it opens the safe preview experience; after the\nuser selects Go live, it admits real participants to Live mode.","operationId":"get_study_interview_link_v1_api_studies__study_id__interview_link_get","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Study Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewLinkOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"links:read","security":[{"ApiKey":[]},{"OAuth":["links:read"]}]}},"/v1/api/studies/{study_id}/participant-links":{"post":{"tags":["Public API"],"summary":"Create participant link","description":"Create an invite link carrying what you already know about the people who\nwill open it — company, role, the systems they use. Juno briefs the\ninterviewer with it, so the interview does not ask for details you supplied.\n\nBy default the link is for a **group**: send one link to everyone in that\nsegment. Set `audience` to `person` when the context identifies one\nindividual and you want the finished interview matched back to their record;\nthat link then belongs to whoever opens it first. The context is recorded on\nthe resulting interview and returned by List interviews.","operationId":"create_participant_link_v1_api_studies__study_id__participant_links_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Study Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantLinkRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantLinkOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"links:write","security":[{"ApiKey":[]},{"OAuth":["links:write"]}]}},"/v1/api/files":{"post":{"tags":["Public API"],"summary":"Upload file","description":"Upload background material for Create a study or Update a study.\nThe returned file_id has no effect by itself; include it in file_ids\nwhen creating or updating the study. Supports PDF, DOCX, TXT, and MD.","operationId":"upload_file_v1_api_files_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_file_v1_api_files_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileUploadOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:write","security":[{"ApiKey":[]},{"OAuth":["studies:write"]}]}},"/v1/api/studies/jobs/{job_id}":{"get":{"tags":["Public API"],"summary":"Check study job","description":"Track a study Juno is authoring. Creating or refining a study returns a\njob_id; poll this endpoint until status is ready, at which\npoint study_id and the authored brief (title, objectives, and\ninterview questions) are populated. failed means authoring did not\ncomplete. A failed job can still include a study saved before a later\nrefusal. The brief is that study's current version. A 409 means no confirmed\nmutation result is available; it does not establish whether anything saved.","operationId":"get_authoring_status_v1_api_studies_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthoringStatusOut"}}}},"409":{"description":"The completed job has no confirmed study mutation result."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:read","security":[{"ApiKey":[]},{"OAuth":["studies:read"]}]}},"/v1/api/studies/{study_id}:refine":{"post":{"tags":["Public API"],"summary":"Update study","description":"Update a study with further plain-language instructions from the user —\nin Test mode or Live mode. The technical :refine path and\nrefine_study operation name are retained for wire compatibility. Each\nupdate creates a new design version. Upload new documents first and include\ntheir file_ids when they are part of the update. Returns a job_id; poll Check\nauthoring status like a fresh authoring run.","operationId":"refine_study_v1_api_studies__study_id__refine_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Study Id"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudyGuidanceRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthoringJobOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:write","security":[{"ApiKey":[]},{"OAuth":["studies:write"]}]}},"/v1/api/studies/{study_id}:simulate":{"post":{"tags":["Public API"],"summary":"Simulate interview","description":"In Test mode, simulate the interview before the user selects Go live:\nJuno plays both sides — the\ninterviewer runs the study's real interview, and a synthetic participant\nanswers it — producing a sample transcript. Optionally steer the\nparticipant with a persona description. Simulation runs in the\nbackground: this returns a job_id; poll Check a simulation to watch\nthe transcript grow turn by turn. Simulation uses no credits, provides no\nanalysis or exports, and is not real participant research. The current\nendpoint does not enforce mode=test; developers must treat that as the\nproduct contract.","operationId":"simulate_study_v1_api_studies__study_id__simulate_post","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Study Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulationRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulationJobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:write","security":[{"ApiKey":[]},{"OAuth":["studies:write"]}]}},"/v1/api/simulations/{job_id}":{"get":{"tags":["Public API"],"summary":"Check simulation","description":"Track a simulation started with Simulate an interview and read its\nsample transcript. The response carries the turns produced so far, so you\ncan render the conversation while it runs; once status is\ncompleted the transcript is final. Simulated interviews are a Test-mode\naction — they are never counted, analysed, or exported as real interviews.","operationId":"get_simulation_status_v1_api_simulations__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimulationStatusOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:read","security":[{"ApiKey":[]},{"OAuth":["studies:read"]}]}},"/v1/api/interviews":{"get":{"tags":["Public API"],"summary":"List interviews","description":"List participant interviews, newest first — the endpoint to poll to learn\nwhen interviews happen. Each entry carries the interview's state, timings,\nand the values captured from the participant's invite link, plus a link into\nJuno; transcripts and participant identity are deliberately not included.\nFilter by study_id and by status, and page with the cursor from the\nX-Next-Cursor response header (absent on the last page). Check is_test\nbefore acting on an interview: Test-mode rehearsals appear here too.","operationId":"list_interviews_v1_api_interviews_get","parameters":[{"name":"study_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only return interviews belonging to this study.","title":"Study Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["in_progress","completed","failed"],"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Opaque cursor for the next page, taken from the previous response: over REST it arrives in the X-Next-Cursor response header, and over MCP it is the next_cursor field of the result, because JSON-RPC has no headers. Omit for the first page; a response without one is the last page. Cursors are keyset-based, so rows arriving mid-pagination never shift or duplicate the page you are walking.","title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Page size — newest first, max 200.","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PublicInterview"},"title":"Response List Interviews V1 Api Interviews Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"export:read","security":[{"ApiKey":[]},{"OAuth":["export:read"]}]}},"/v1/api/interviews/{interview_id}":{"get":{"tags":["Public API"],"summary":"Get interview","description":"Fetch one interview by id. Same projection as List interviews: state,\ntimings, invite-link values, and a permalink — no transcript or participant\nidentity. Use Start an export for the full transcript data.","operationId":"get_interview_v1_api_interviews__interview_id__get","parameters":[{"name":"interview_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Interview Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicInterview"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"export:read","security":[{"ApiKey":[]},{"OAuth":["export:read"]}]}},"/v1/api/studies/{study_id}/mode":{"put":{"tags":["Public API"],"summary":"Set study mode","description":"Map the established mode wire field onto the product lifecycle.\ntest selects Test mode: simulate the interview shape with no credits,\nanalysis, or exports and no real participant research. live selects\nLive mode: real participant interviews deduct credits and enable analysis\nand exports. The user-facing action is Go live. The same field retains\npaused and closed as technical collection states. Going live the\nfirst time runs validation and requires studies:golive; other changes\nrequire studies:write. Repeating the current value is a no-op.","operationId":"set_study_mode_v1_api_studies__study_id__mode_put","parameters":[{"name":"study_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Study Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudyModeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StudyModeOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"studies:write · studies:golive to go live"}},"/v1/api/exports":{"post":{"tags":["Public API"],"summary":"Start export","description":"In Live mode, export completed participant interviews as CSV —\ntranscripts and answers with the organisation's PII-redaction settings\napplied. Test-mode simulations cannot be exported. Exports run in the\nbackground: this returns a job_id; poll Check export status, then\nfetch the file from Download an export once ready.","operationId":"start_export_v1_api_exports_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJobAccepted"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"export:read","security":[{"ApiKey":[]},{"OAuth":["export:read"]}]}},"/v1/api/exports/{job_id}":{"get":{"tags":["Public API"],"summary":"Check export status","description":"Track an export started with Start an export. Poll until ready is\ntrue, then fetch the file from Download an export. failed means the\nexport did not complete — start a new one.","operationId":"get_export_job_v1_api_exports__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJobStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"export:read","security":[{"ApiKey":[]},{"OAuth":["export:read"]}]}},"/v1/api/exports/{job_id}/download":{"get":{"tags":["Public API"],"summary":"Download export","description":"Fetch the finished export file: a short-lived signed URL to the CSV\n(redaction preserved), or the CSV streamed inline where object storage is\nnot configured. Responds 409 until the export is ready — poll Check\nexport status first.","operationId":"download_export_job_v1_api_exports__job_id__download_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-required-scope":"export:read","security":[{"ApiKey":[]},{"OAuth":["export:read"]}]}}},"components":{"schemas":{"AuthoringJobOut":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Authoring job handle. Poll GET /v1/api/studies/jobs/{job_id} until status is 'ready'."},"thread_id":{"type":"string","format":"uuid","title":"Thread Id","description":"The authoring conversation thread the run executes in."}},"additionalProperties":false,"type":"object","required":["job_id","thread_id"],"title":"AuthoringJobOut","description":"202 acceptance handle. ``job_id`` is the chat run id == DBOS workflow id."},"AuthoringStatusOut":{"properties":{"status":{"type":"string","title":"Status","description":"Derived job status: 'working', 'ready', or 'failed'."},"study_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Study Id","description":"The study the job created or updated, once one exists."},"brief":{"anyOf":[{"$ref":"#/components/schemas/Brief"},{"type":"null"}],"description":"The current brief, once authored and ready to review."}},"additionalProperties":false,"type":"object","required":["status"],"title":"AuthoringStatusOut","description":"Derived authoring-job status. ``status`` ∈ {working, ready, failed}."},"Body_upload_file_v1_api_files_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_file_v1_api_files_post"},"Brief":{"properties":{"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Study title Juno authored."},"objectives":{"items":{"type":"string"},"type":"array","title":"Objectives","description":"What the study sets out to learn, as researcher-legible statements."},"pre_interview_questions":{"items":{"type":"string"},"type":"array","title":"Pre Interview Questions","description":"Questions asked before the interview starts (screeners)."},"verbatim_questions":{"items":{"type":"string"},"type":"array","title":"Verbatim Questions","description":"Questions the interviewer asks word for word during the interview."},"estimated_duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Duration Minutes","description":"Planned interview length in minutes, when the design specifies one."}},"additionalProperties":false,"type":"object","required":["title","objectives","pre_interview_questions","verbatim_questions","estimated_duration_minutes"],"title":"Brief","description":"Lean, non-leaky projection of the study's brief — Juno's authored design.\n\nThe same noun the app uses (the Brief tab). Never carries ``design_json``,\n``interpreted_brief``, or brand config — only the researcher-legible\nsurface."},"ExportFormat":{"type":"string","enum":["csv"],"title":"ExportFormat","description":"Supported export formats. Start with interviews CSV; extend as needed."},"ExportJobAccepted":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Export job handle. Poll GET /v1/api/exports/{job_id} until ready."}},"additionalProperties":false,"type":"object","required":["job_id"],"title":"ExportJobAccepted","description":"202 acceptance handle. ``job_id`` addresses the async export job."},"ExportJobStatus":{"properties":{"status":{"type":"string","title":"Status","description":"Derived export status: 'queued', 'running', 'completed', or 'failed'."},"ready":{"type":"boolean","title":"Ready","description":"True only when the artifact is downloadable from GET /v1/api/exports/{job_id}/download."}},"additionalProperties":false,"type":"object","required":["status","ready"],"title":"ExportJobStatus","description":"Derived job status. ``status`` ∈ {queued, running, completed, failed}.\n\n``ready`` is ``True`` only when the artifact is downloadable (status\n``completed``), so callers can poll on a single boolean."},"ExportRequest":{"properties":{"study_id":{"type":"string","format":"uuid","title":"Study Id","description":"Study whose Live-mode participant interviews to export."},"format":{"$ref":"#/components/schemas/ExportFormat","description":"Export format. Only 'csv' is supported today.","default":"csv"}},"additionalProperties":false,"type":"object","required":["study_id"],"title":"ExportRequest","description":"Instruction to start an async export for one of the caller's studies."},"FileUploadOut":{"properties":{"file_id":{"type":"string","format":"uuid","title":"File Id","description":"Use as a file_ids entry when creating or updating a study."},"filename":{"type":"string","title":"Filename","description":"Original filename as uploaded."},"byte_size":{"type":"integer","title":"Byte Size","description":"Stored size in bytes."},"extracted":{"type":"boolean","title":"Extracted","description":"Whether text was parsed out of the file. False for empty or binary types that carry no extractable content."}},"additionalProperties":false,"type":"object","required":["file_id","filename","byte_size","extracted"],"title":"FileUploadOut","description":"Lean handle for an uploaded guidance file.\n\n``file_id`` has no effect until it is used in ``file_ids`` when creating or\nupdating a study. ``extracted`` reports whether text was parsed out of the\nfile (false for empty/binary types that carry no extractable content)."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InterviewLinkOut":{"properties":{"url":{"type":"string","title":"Url","description":"Absolute invite URL (e.g. https://www.heyjuno.co/chat/{token}). The user shares it with participants; it is durable across lifecycle changes."},"mode":{"type":"string","enum":["test","live","paused","closed"],"title":"Mode","description":"The study's lifecycle value at the time of this response. Advisory only — participant behavior follows the value when they open the link."}},"additionalProperties":false,"type":"object","required":["url","mode"],"title":"InterviewLinkOut","description":"The study's single durable invite link, plus its current lifecycle value.\n\nThe URL never changes as the study moves through its lifecycle. In Test mode\nit supports previewing; after Go live it admits real participants to Live\nmode. ``mode`` here is point-in-time information, not a property of the link."},"OAuthWhoAmIOut":{"properties":{"key_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Key Id","description":"Identifier of the presented key."},"key_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Name","description":"Name assigned to this key."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When this key expires; null means no configured expiry."},"org_id":{"type":"string","format":"uuid","title":"Org Id","description":"The organisation this key belongs to."},"org_name":{"type":"string","title":"Org Name","description":"Human-readable name of that organisation. Clients label a stored connection with this — an id alone makes two connected organisations indistinguishable in a list."},"environment":{"type":"string","title":"Environment","description":"API data environment from the key prefix: 'test' or 'live'. This is separate from a study's Test mode or Live mode."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Scopes granted to this key, sorted. Calls needing a missing scope are refused."},"credential_type":{"type":"string","const":"oauth","title":"Credential Type","default":"oauth"},"grant_id":{"type":"string","format":"uuid","title":"Grant Id"},"client_id":{"type":"string","title":"Client Id"}},"additionalProperties":false,"type":"object","required":["org_id","org_name","environment","scopes","grant_id","client_id"],"title":"OAuthWhoAmIOut","description":"A user-delegated connection, attributed separately from an API key."},"ParticipantLinkOut":{"properties":{"url":{"type":"string","title":"Url","description":"Absolute invite URL. The context you supplied is stored against the link rather than written into it, so the URL reveals nothing about who it is for. Treat it as sensitive: anyone holding it arrives carrying the context."},"audience":{"type":"string","enum":["group","person"],"title":"Audience","description":"The audience this link was minted for. 'group' is reusable — send it to as many people as you like. 'person' addresses one interview; reopening the same link continues that interview, so give each different participant a fresh link."},"mode":{"type":"string","enum":["test","live","paused","closed"],"title":"Mode","description":"The study's lifecycle value when the link was minted. Advisory only — participant behavior follows the value when they open it."}},"additionalProperties":false,"type":"object","required":["url","audience","mode"],"title":"ParticipantLinkOut","description":"An invite URL and the audience it was minted for."},"ParticipantLinkRequest":{"properties":{"context":{"additionalProperties":{"type":"string"},"type":"object","title":"Context","description":"Key/value facts about the people this link is for, e.g. {\"company\": \"Coca-Cola\", \"role\": \"Head of Martech\"}. At most 20 keys; keys up to 100 characters, values up to 2048, 16KB total."},"audience":{"type":"string","enum":["group","person"],"title":"Audience","description":"Who the link is for. 'group' (the default) describes a segment — everyone you send it to gets this context, so one link covers a whole company or role. Use 'person' when the context identifies one individual (e.g. a contact_id you want the finished interview matched back to): the link then addresses one interview. Reopening that same link continues the interview with its original context, even in another browser or device. Give each different participant a fresh person link and treat it as sensitive.","default":"group"},"note":{"type":"string","title":"Note","description":"Free-text direction for the interviewer about the people this link is for, e.g. 'These participants recently cancelled. Explore what changed and why they left.' Unlike context (facts, which become data columns on the interview), the note is guidance Juno follows during the conversation. Up to 2048 characters. Never shown to the participant and never included in exports.","default":""}},"additionalProperties":false,"type":"object","title":"ParticipantLinkRequest","description":"Context to attach to an invite link, and who the link is for.\n\nValues become the interview's entry attributes: recorded on the interview\nrow, shown in the interviews table and exports, and — excluding campaign\nattribution — briefed to the interviewer as established fact so it does not\nask the participant for details you already supplied."},"PublicInterview":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique interview id, stable for the life of the interview. Use it to deduplicate when polling."},"study_id":{"type":"string","format":"uuid","title":"Study Id","description":"The study this interview belongs to."},"status":{"type":"string","enum":["in_progress","completed","failed"],"title":"Status","description":"'in_progress' while the participant is still going, 'completed' once the interview ended normally, 'failed' if it errored out."},"is_complete":{"type":"boolean","title":"Is Complete","description":"True only for 'completed'. Convenient for filtering in one field."},"is_test":{"type":"boolean","title":"Is Test","description":"True for Test-mode interviews, which are rehearsals rather than real participant research. Check this before acting on an interview — test runs use no credits and are not real data."},"modality":{"type":"string","title":"Modality","description":"How the participant answered: 'text' or 'voice'."},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"When the interview record was created."},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At","description":"When the interview ended. Null while still in progress."},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Active participant time in seconds, excluding pauses. Null until the runtime has measured any."},"entry_attributes":{"additionalProperties":{"type":"string"},"type":"object","title":"Entry Attributes","description":"Values captured from the participant's invite link — whatever the caller attached when inviting them (a CRM contact id, company, role). The intended way to match an interview back to the record it came from."},"invite_audience":{"anyOf":[{"type":"string","enum":["group","person"]},{"type":"null"}],"title":"Invite Audience","description":"Audience of the contextual participant link used to start this interview: 'group' or 'person'. Null when the interview came from the study's plain link or predates participant-link attribution."},"permalink":{"type":"string","title":"Permalink","description":"Absolute URL that opens this interview's transcript in Juno."}},"additionalProperties":false,"type":"object","required":["id","study_id","status","is_complete","is_test","modality","started_at","permalink"],"title":"PublicInterview","description":"One participant interview, as metadata plus a link back into Juno.\n\nDeliberately carries no transcript, answers, or participant identity: an\nintegration forwards this into third-party systems, and a participant\nconsented to research with Juno, not to their answers being syndicated.\nCorrelation is meant to run through ``entry_attributes`` — the caller's own\nidentifiers, which they attached to the invite link themselves."},"PublicStudyDetail":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique study id. Use it in every study-scoped call."},"name":{"type":"string","title":"Name","description":"Human-readable study name, as shown in the Juno app."},"mode":{"type":"string","enum":["test","live","paused","closed"],"title":"Mode","description":"Current lifecycle value. 'test' means Test mode: the user can simulate the interview shape with no credits, analysis, or exports; it is not for real participant research. 'live' means Live mode: real participant interviews deduct credits and enable analysis and exports. 'paused' and 'closed' are technical collection states. Use PUT /v1/api/studies/{study_id}/mode; mode='live' is the API equivalent of the user selecting Go live."},"launched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Launched At","description":"When the study first went live. Null if it never has."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the study was created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the study last changed."},"brief":{"anyOf":[{"$ref":"#/components/schemas/Brief"},{"type":"null"}],"description":"The study's current brief. Null only while nothing has been authored yet (an authoring job is still working)."}},"additionalProperties":false,"type":"object","required":["id","name","mode","launched_at","created_at","updated_at"],"title":"PublicStudyDetail","description":"One study together with its current brief.\n\n``brief`` is the same lean projection the authoring-status endpoint\nreports; it is ``None`` only while nothing has been authored yet."},"PublicStudySummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique study id. Use it in every study-scoped call."},"name":{"type":"string","title":"Name","description":"Human-readable study name, as shown in the Juno app."},"mode":{"type":"string","enum":["test","live","paused","closed"],"title":"Mode","description":"Current lifecycle value. 'test' means Test mode: the user can simulate the interview shape with no credits, analysis, or exports; it is not for real participant research. 'live' means Live mode: real participant interviews deduct credits and enable analysis and exports. 'paused' and 'closed' are technical collection states. Use PUT /v1/api/studies/{study_id}/mode; mode='live' is the API equivalent of the user selecting Go live."},"launched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Launched At","description":"When the study first went live. Null if it never has."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the study was created."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"When the study last changed."}},"additionalProperties":false,"type":"object","required":["id","name","mode","launched_at","created_at","updated_at"],"title":"PublicStudySummary","description":"Stable, non-leaky projection of a study for the public API.\n\n``mode`` is the established collapsed lifecycle field. ``test``/``live``\nmap to Test mode/Live mode; ``paused``/``closed`` are collection states.\n``launched_at`` being non-null means the study has been live at least once.\nInternal design/brief/brand JSON is never surfaced here."},"SimulationJobAccepted":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Simulation job handle. Poll GET /v1/api/simulations/{job_id} to watch the transcript grow and to learn when it completes."}},"additionalProperties":false,"type":"object","required":["job_id"],"title":"SimulationJobAccepted","description":"202 acceptance handle. ``job_id`` addresses the async simulation job."},"SimulationRequest":{"properties":{"persona":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Persona","description":"Optional plain-language description of the synthetic participant (e.g. 'a skeptical enterprise buyer who churned last month'). When omitted, Juno invents a plausible participant for the study."},"max_turns":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Max Turns","description":"Maximum number of interview turns to simulate. The interview may finish naturally in fewer turns.","default":12}},"additionalProperties":false,"type":"object","title":"SimulationRequest","description":"Instruction to simulate a Test-mode interview with a synthetic participant."},"SimulationStatusOut":{"properties":{"status":{"type":"string","title":"Status","description":"Derived simulation status: 'queued', 'running', 'completed', or 'failed'."},"ready":{"type":"boolean","title":"Ready","description":"True only when the simulation completed and the transcript is final."},"turns":{"items":{"$ref":"#/components/schemas/SimulationTranscriptTurn"},"type":"array","title":"Turns","description":"The sample transcript so far, oldest first. Grows while the simulation runs; final once status is 'completed'. Simulated interviews are a Test-mode action — they use no credits, provide no analysis or exports, and never appear as real participant interviews."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the simulation failed, when status is 'failed'."}},"additionalProperties":false,"type":"object","required":["status","ready","turns"],"title":"SimulationStatusOut","description":"Derived simulation status plus the transcript accumulated so far.\n\n``turns`` is populated incrementally while the simulation runs, so callers\ncan render the conversation live from a plain polling loop; once ``status``\nis ``completed`` the transcript is final."},"SimulationTranscriptTurn":{"properties":{"speaker":{"type":"string","enum":["interviewer","participant"],"title":"Speaker","description":"Who is talking: 'interviewer' is Juno running the study's real interview; 'participant' is the synthetic respondent."},"text":{"type":"string","title":"Text","description":"What was said in this turn."},"turn_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Turn Number","description":"1-based interview turn index, when the runtime reports one."}},"additionalProperties":false,"type":"object","required":["speaker","text"],"title":"SimulationTranscriptTurn","description":"One spoken turn of a simulated interview transcript."},"StudyGuidanceRequest":{"properties":{"guidance":{"type":"string","minLength":1,"title":"Guidance","description":"Plain-language instruction from the user: the research goal, intended participants, and what the user wants to learn — or, when updating, what to change. A sentence or two is enough."},"file_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"File Ids","description":"Optional file ids from POST /v1/api/files. Include them when documents are part of Create a study or Update a study."}},"additionalProperties":false,"type":"object","required":["guidance"],"title":"StudyGuidanceRequest","description":"Plain-language instruction Juno creates or updates a study from.\n\nUsed by both POST /v1/api/studies (create) and POST\n/v1/api/studies/{study_id}:refine. The latter technical path means Update a\nstudy. Guidance is free text — what the user would tell Juno — never a\nstructured design patch."},"StudyModeOut":{"properties":{"mode":{"type":"string","enum":["test","live","paused","closed"],"title":"Mode","description":"The study's lifecycle value after the change."},"url":{"type":"string","title":"Url","description":"Absolute invite URL. Durable — the same link across every lifecycle change."}},"additionalProperties":false,"type":"object","required":["mode","url"],"title":"StudyModeOut","description":"Result of a lifecycle change: the settled value plus the invite link."},"StudyModeRequest":{"properties":{"mode":{"type":"string","enum":["test","live","paused","closed"],"title":"Mode","description":"Target lifecycle value. 'test' and 'live' map to Test mode and Live mode; setting 'live' is the API equivalent of Go live and needs the studies:golive scope. 'paused' and 'closed' are technical collection states; 'paused' is only valid from live. Setting the current value is a no-op."}},"additionalProperties":false,"type":"object","required":["mode"],"title":"StudyModeRequest","description":"Instruction to choose Test/Live mode or a technical collection state."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WhoAmIOut":{"properties":{"key_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Key Id","description":"Identifier of the presented key."},"key_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Name","description":"Name assigned to this key."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When this key expires; null means no configured expiry."},"org_id":{"type":"string","format":"uuid","title":"Org Id","description":"The organisation this key belongs to."},"org_name":{"type":"string","title":"Org Name","description":"Human-readable name of that organisation. Clients label a stored connection with this — an id alone makes two connected organisations indistinguishable in a list."},"environment":{"type":"string","title":"Environment","description":"API data environment from the key prefix: 'test' or 'live'. This is separate from a study's Test mode or Live mode."},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Scopes granted to this key, sorted. Calls needing a missing scope are refused."}},"additionalProperties":false,"type":"object","required":["org_id","org_name","environment","scopes"],"title":"WhoAmIOut","description":"Identity of the presented key: which org, which environment, what it can do.\n\nA standard \"whoami\" any client can call to discover its own grants — and what\nthe playground key bar reads to show scopes and disable out-of-scope actions."}},"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","description":"Organisation-scoped API key as a Bearer token (jsk_live_… / jsk_test_…). Create keys in organisation settings or through the verified-identity flow described at /v1/access/openapi.json on the selected API server. That flow requires a separate management transaction secret, matching email proof and MFA when required."},"OAuth":{"type":"oauth2","description":"User-approved workspace access. Use PKCE S256 and the advertised /v1/api resource on authorization and token requests. Discover the issuer at /.well-known/oauth-authorization-server.","flows":{"authorizationCode":{"authorizationUrl":"/v1/oauth/authorize","tokenUrl":"/v1/oauth/token","refreshUrl":"/v1/oauth/token","scopes":{"studies:read":"Read studies and their status.","studies:write":"Create and update studies.","studies:golive":"Take studies live and incur applicable charges.","links:read":"Read study invitation links.","links:write":"Create and manage study invitation links.","export:read":"Export interview results and transcripts.","agent:run":"Ask Juno to carry out research work."}}}}}},"servers":[{"url":"https://api.heyjuno.co","description":"Production"},{"url":"https://api.northflank-dev.heyjuno.co","description":"Development"}],"security":[{"ApiKey":[]},{"OAuth":[]}]}