Voiceflow allows customers to register voice notes, upload audio directly to S3-compatible storage, and track processing status. Audio never passes directly through the application server.

Upload Flow Overview#

Submitting feedback follows a three-step client flow:

  1. Register: The customer client calls the API to validate file metadata and create a pending record. The API returns a signed S3 upload URL.
  2. Direct Upload: The client sends the raw audio file directly to object storage using an HTTP PUT request.
  3. Confirm: The client notifies the API that the upload has finished. The API verifies the uploaded file's existence and size before queuing background processing.

All customer feedback routes are mounted on feedbackRouter under the /api/feedback path and require authenticated customer credentials.

Registering Feedback#

To register an audio recording, submit a POST /api/feedback request. The request body is validated against registerFeedbackSchema.

Validation Rules and Constraints#

  • mimeType: Allowed audio MIME types are audio/webm, audio/mp4, and audio/ogg. Values containing codecs (such as audio/webm;codecs=opus) are trimmed to the base MIME type.
  • sizeBytes: An integer between 1 and MAX_SIZE_BYTES (2,097,152 bytes or 2 MB).
  • durationMs: An integer between 1 and MAX_DURATION_MS (30,500 milliseconds).
  • consent: Must explicitly be true.
  • languageHint (optional): One of the supported Sarvam language codes defined in SPEECH_LANGUAGES (such as en-IN, hi-IN, ta-IN, te-IN, mr-IN, etc.).

Example Request#

Registration Response#

The API creates a record with UPLOADING status, assigns a storage path scoped to the user ({userId}/{id}.{ext}), and generates a pre-signed URL via createUploadUrl.

{
  "id": "a4b595ca-938b-4cce-91f8-0e1ce33a8cfa",
  "uploadUrl": "https://bucket.s3.region.amazonaws.com/user_123/a4b595ca-938b-4cce-91f8-0e1ce33a8cfa.webm?X-Amz-..."
}

Direct Upload to Storage#

The uploadUrl is an S3 pre-signed PUT URL that expires after 15 minutes (900 seconds). The pre-signed URL binds both ContentType and ContentLength. When performing the upload, the HTTP request must use the exact declared MIME type and file byte size.

Confirming Upload Completion#

After the direct upload to storage succeeds, call POST /api/feedback/:id/complete to confirm completion.

When executing completeUpload, the server verifies the upload:

  1. Ensures the recording belongs to the authenticated customer and is currently in UPLOADING status. If already confirmed, it returns an INVALID_STATE (409) error.
  2. Verifies the file in object storage. If the file is missing or the stored size does not match sizeBytes, it returns an UPLOAD_MISSING (400) error.
  3. Updates the database status to UPLOADED.
  4. Emits a feedback/uploaded event to Inngest to trigger the background processing pipeline (see Processing Feedback and Scheduled Cleanup).

A successful confirmation returns HTTP status 202 Accepted:

{
  "status": "UPLOADED"
}

Tracking Customer Feedback#

Customers can retrieve their submitted feedback entries and monitor processing progress.

Fetch a Specific Recording#

Send a GET /api/feedback/:id request:

The response returns public details including processing status and transcriptions. Sentiment analysis details are restricted to admins and omitted from customer responses:

{
  "id": "a4b595ca-938b-4cce-91f8-0e1ce33a8cfa",
  "status": "COMPLETED",
  "durationMs": 12000,
  "errorCode": null,
  "createdAt": "2025-01-15T10:00:00.000Z",
  "completedAt": "2025-01-15T10:00:25.000Z",
  "transcription": {
    "originalText": "नमस्ते, सेवा बहुत अच्छी थी।",
    "englishText": "Hello, the service was very good.",
    "languageCode": "hi-IN"
  }
}

List Customer Recordings#

Send a GET /api/feedback request to retrieve a paginated list of recordings for the authenticated customer:

The endpoint returns up to 20 items per page ordered newest to oldest (createdAt: "desc", id: "desc"), alongside a nextCursor value:

{
  "items": [
    {
      "id": "a4b595ca-938b-4cce-91f8-0e1ce33a8cfa",
      "status": "COMPLETED",
      "durationMs": 12000,
      "errorCode": null,
      "createdAt": "2025-01-15T10:00:00.000Z",
      "completedAt": "2025-01-15T10:00:25.000Z",
      "transcription": {
        "originalText": "Great support experience.",
        "englishText": "Great support experience.",
        "languageCode": "en-IN"
      }
    }
  ],
  "nextCursor": null
}