> Documentation index: https://docs.mayur.live/llms.txt

# Submitting and Uploading Feedback

Register audio recordings, upload directly to S3-compatible storage, and confirm upload completion.

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`.

```json
{
  "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 [[pipeline-and-background-jobs]]).

A successful confirmation returns HTTP status `202 Accepted`:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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
}
```
