The participant API

On this page

The participant API is the set of unauthenticated endpoints that power the participant portal. It’s how someone can scan a QR code or open a link, record a conversation, and have it land in your project - without ever creating an account. If you’re building your own recorder or kiosk on top of dembrane, this is the surface you’ll use.

These routes live in server/dembrane/api/participant.py and are served under /api/participant/* by the FastAPI backend on port 8000.

Note

"Unauthenticated" means no user token is required - the project's identifier is the capability. Anyone with a project's public link can initiate and upload conversations to it, which is the point. Don't treat a project ID as a secret. Everything authenticated lives behind Directus tokens instead.

OpenAPI / interactive docs

Set SERVE_API_DOCS=1 (see configuration) and the backend serves:

These render the live schema for your build, including request/response shapes for every endpoint below. Keep them on while integrating; they’re the authoritative parameter list.

Reading public project data

Fetch the public metadata a recorder needs to render the start screen:

Method & path Returns
GET /api/participant/projects/{pid} Public project metadata - title, language, the portal-editor configuration (welcome text, whether to ask for name/email, verification settings).
GET /api/participant/projects/{pid}/conversations/{cid} A single conversation’s public state.
GET /api/participant/conversations/{cid}/chunks The chunks recorded so far for a conversation.

The portal-editor fields returned here are what the host configured for the participant experience.

Initiating a conversation

Create a conversation to record into:

POST /api/participant/conversations/initiate
Content-Type: application/json

{
  "project_id": "<pid>",
  "name": "Optional participant name",
  "email": "optional@example.org",
  "tag_id_list": ["<tag-id>", "..."],
  "source": "PORTAL_AUDIO"
}

Whether name and email are expected depends on the project’s portal-editor settings (ask for name / ask for email). The response carries the new conversation’s ID, which you use for every upload call that follows.

The upload sequence

dembrane records in chunks (the portal uses ~30-second chunks). Each chunk goes to S3-compatible storage via a presigned URL, then is confirmed so the backend can enqueue transcription. The typical loop:

  1. Get a presigned upload URL.

    POST /api/participant/conversations/{cid}/get-upload-url

    Returns a presigned S3 URL (and the object key) to PUT the chunk to directly.

    Important

    This endpoint is rate-limited to 40 requests per minute. One presigned URL per chunk, so a long recording made of short chunks will approach that ceiling - pace your requests.

  2. Upload the bytes to the presigned URL (a direct PUT to S3/MinIO - this doesn’t go through the dembrane backend, which is what keeps large uploads off the API).

  3. Confirm the upload so the backend knows the chunk has landed and can process it:

    POST /api/participant/conversations/{cid}/confirm-upload

Repeat 1–3 for each chunk as the recording proceeds.

Alternatives to the presigned flow

What happens after upload

Once chunks are confirmed, the processing pipeline takes over: each chunk is transcribed, corrected (key terms + redaction), and - when the conversation is finished and all chunks are processed - merged and summarised. You can poll the chunk and conversation endpoints to follow progress, and the host dashboard shows the same state live. See transcription for what each stage does.

Participant report endpoints

If the project enables a participant-facing report, the participant API also exposes endpoints to fetch that report and its artifacts, and to unsubscribe from notifications. These back the portal’s finish and report screens; their exact shapes are in the /docs explorer for your build. See your report for the participant-side view.

A minimal end-to-end flow

GET  /api/participant/projects/{pid}                      → render start screen
POST /api/participant/conversations/initiate              → conversation {cid}
loop while recording:
  POST /api/participant/conversations/{cid}/check-s3       → ok to upload?
  POST /api/participant/conversations/{cid}/get-upload-url → presigned URL  (≤ 40/min)
  PUT  <presigned-url>  (the chunk bytes)                  → straight to S3/MinIO
  POST /api/participant/conversations/{cid}/confirm-upload → enqueue processing
finish:
  (mark finished; fetch participant report if enabled)

Related

Comments