> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tiro.ooo/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> How to create a Voice File Job, upload audio, check status, and retrieve or delete results.

export const PlanGateCard = ({plan, page, lang = "ko", children}) => {
  const BADGE = {
    pro: {
      ko: "Pro+",
      en: "Pro+",
      ja: "Pro+"
    },
    team: {
      ko: "팀 플랜",
      en: "Team Plan",
      ja: "チームプラン"
    },
    enterprise: {
      ko: "엔터프라이즈",
      en: "Enterprise",
      ja: "エンタープライズ"
    }
  };
  const COPY = {
    pro: {
      ko: {
        lead: "이 기능은 Pro 플랜 이상에 포함되어 있어요.",
        tail: "지금 쓰는 워크스페이스에서 바로 업그레이드할 수 있어요.",
        cta: "플랜 업그레이드하기"
      },
      en: {
        lead: "This feature is included in Pro plans and above.",
        tail: "You can upgrade right from your workspace.",
        cta: "Upgrade your plan"
      },
      ja: {
        lead: "この機能はProプラン以上でご利用いただけます。",
        tail: "ワークスペースからすぐにアップグレードできます。",
        cta: "プランをアップグレード"
      }
    },
    team: {
      ko: {
        lead: "이 기능은 함께 쓰는 워크스페이스에서 진가가 나와요.",
        tail: "팀원을 초대하면 바로 시작할 수 있어요.",
        cta: "팀원 초대하기"
      },
      en: {
        lead: "This feature shines in a shared workspace.",
        tail: "Invite your teammates to get started.",
        cta: "Invite teammates"
      },
      ja: {
        lead: "この機能はチームで使うワークスペースで真価を発揮します。",
        tail: "メンバーを招待してすぐに始められます。",
        cta: "メンバーを招待"
      }
    },
    enterprise: {
      ko: {
        lead: "이 기능은 엔터프라이즈 플랜에서 제공돼요.",
        tail: "조직에 필요한 보안 구성을 영업팀이 함께 설계해 드려요.",
        cta: "도입 문의하기"
      },
      en: {
        lead: "This feature is available on the Enterprise plan.",
        tail: "Our sales team will help design the security setup your organization needs.",
        cta: "Contact sales"
      },
      ja: {
        lead: "この機能はエンタープライズプランでご利用いただけます。",
        tail: "組織に必要なセキュリティ構成を営業チームがご支援します。",
        cta: "導入のお問い合わせ"
      }
    }
  };
  const HREF = {
    pro: "https://tiro.ooo/" + lang + "/n?utm_source=docs&utm_medium=plan_callout&utm_campaign=plan_upsell_pro&utm_content=" + page + "#settings/plans",
    team: "https://tiro.ooo/" + lang + "/n?utm_source=docs&utm_medium=plan_callout&utm_campaign=plan_upsell_team&utm_content=" + page + "#settings/plans",
    enterprise: "https://tiro.ax/" + lang + "/contact?utm_source=docs&utm_medium=plan_callout&utm_campaign=plan_upsell_enterprise&utm_content=" + page
  };
  const copy = COPY[plan][lang];
  return <div className="plan-gate-card">
      <div className="plan-gate-card-text">
        <span className="plan-gate-card-title">
          {({
    pro: "Pro Plan",
    team: "Team Plan",
    enterprise: "Enterprise Plan"
  })[plan]}
        </span>
        <p>
          {copy.lead} {children} {copy.tail}
        </p>
      </div>
      <a className="plan-gate-card-button" href={HREF[plan]}>
        {copy.cta}
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
      </a>
    </div>;
};

export const PlanBadge = ({plan, lang = "ko"}) => <span className="plan-badge">
    {({
  pro: {
    ko: "Pro+",
    en: "Pro+",
    ja: "Pro+"
  },
  team: {
    ko: "팀 플랜",
    en: "Team Plan",
    ja: "チームプラン"
  },
  enterprise: {
    ko: "엔터프라이즈",
    en: "Enterprise",
    ja: "エンタープライズ"
  }
})[plan][lang]}
  </span>;

<PlanBadge plan="enterprise" lang="en" /> The Voice File Job API is available on the Enterprise plan. It cannot be enabled on personal or Team plans.

A Voice File Job goes through create, upload, upload-complete, status check, and result retrieval. The upload-complete call is made by the client; no S3 event is used.

## What you need

* An account or workspace API key: `Authorization: Bearer {id}.{secret}`
* A key with scopes needs `voice_file_job:write` to create a job, notify upload completion, and delete, and `voice_file_job:read` to read. `write` includes `read`, and a key without scopes can do everything.
* A workspace with an Enterprise plan contract and the Voice File Job policy enabled
* An account or organization API key that is not bound to a workspace must send `workspaceGuid` when it creates a job
* transcriptLocaleHints: up to 1 (optional)
  * **If not provided, language will be automatically detected**
* translationLocales: up to 5 (optional)

## End-to-end checklist

1. Create job → receive `{ id, uploadUri }`
2. Upload audio to `uploadUri`
3. Call `PUT /v1/external/voice-file/jobs/{jobId}/upload-complete`
4. Receive the `voice_file_job.completed` or `voice_file_job.failed` webhook (recommended). Poll job status if you cannot receive webhooks
5. Fetch transcript and (optionally) translations
6. Get paragraph summaries for better content understanding

## How do you know when a job finishes? Webhooks (recommended)

Use webhooks to learn when a job finishes. Processing typically takes 45 seconds to 18 minutes depending on file duration, and can take longer when the queue is busy. With a webhook subscription, Tiro sends a `voice_file_job.completed` or `voice_file_job.failed` event when processing ends, so you do not query the job status repeatedly while you wait.

* Subscribe to `voice_file_job.completed` and `voice_file_job.failed`. A workspace webhook receives events for every job created in that workspace, and an organization webhook receives events for every job created in any workspace of the organization. Unlike note events, folder sharing does not limit these events. An account webhook receives events only for jobs the user created with an account API key in the workspace where the webhook is registered. See [Webhooks overview](/en/developers/webhooks/webhooks-overview) to register an endpoint.
* `data.resourceId` is the job ID, and `data.resource.status` is `COMPLETED` or `FAILED`. See [Event Structure](/en/developers/webhooks/events/schema) for every field.
* Webhook payloads contain job metadata, not transcript text. Fetch transcript content with `GET /v1/external/voice-file/jobs/{jobId}/transcript`.
* If every delivery retry fails, Tiro does not send that event again. When a job passes its expected processing time without an event, check its status with `GET /v1/external/voice-file/jobs/{jobId}`.

You can also subscribe to `voice_file_job.created` and `voice_file_job.deleted` to track job creation and deletion.

## Polling strategy (fallback)

Poll the job status when you cannot run a webhook endpoint or a webhook event does not arrive.

* Poll `GET /v1/external/voice-file/jobs/{jobId}` with exponential backoff
  * Start at 1–2s interval, then 4s, 8s, up to 30s cap
  * Stop conditions:
    * Success: `status` = `COMPLETED`
    * Failure: `status` = `FAILED`
  * After success:
    * Always fetch transcript: `GET /v1/external/voice-file/jobs/{jobId}/transcript`
    * If you requested translations: `GET /v1/external/voice-file/jobs/{jobId}/translations` or per-locale endpoint
    * Get paragraph summaries:
      * For transcript: `GET /v1/external/voice-file/jobs/{jobId}/transcript/paragraph-summary`
      * For translations: `GET /v1/external/voice-file/jobs/{jobId}/translations/{locale}/paragraph-summary`

<Note>
  The final status for successful processing is `COMPLETED`. This means all processing is complete including transcript, translation (if requested), and paragraph summaries for both.
</Note>

## Can you get the transcript when a job is FAILED?

Sometimes. A job also becomes `FAILED` when only summary generation fails. In that case, when `transcriptAvailable` in the `GET /v1/external/voice-file/jobs/{jobId}` response is `true`, you can fetch the transcript with `GET /v1/external/voice-file/jobs/{jobId}/transcript`.

The failure reason is in `errorMessage`. The `voice_file_job.failed` webhook carries the same value in `data.resource.errorMessage`.

## How does Tiro store and protect voice file data?

Tiro stores uploaded audio in Tiro-managed object storage and stores transcripts and translations in the Tiro service database. The API does not provide a configurable automatic retention period. Call `DELETE /v1/external/voice-file/jobs/{jobId}` when your application no longer needs the data.

Call `DELETE /v1/external/voice-file/jobs/{jobId}` to remove a job in any status. Tiro removes the audio and generated results, and ignores processing callbacks that arrive after deletion.

An account API key can access only the jobs it created; a workspace API key can access every job in the same workspace. Deleting a job also removes its audio, transcript, and translations, and the job can no longer be retrieved.

## File Limits & Requirements

### Supported Audio Formats

The supported file formats are as follows.

**Audio Formats:**

| Format | MIME Type | Extension |
| - | - | - |
| **MP3** | audio/mpeg | .mp3 |
| **WAV** | audio/wav | .wav |
| **M4A** | audio/mp4 | .m4a |

**Video Formats (audio extraction):**

| Format | MIME Type | Extension |
| - | - | - |
| **MP4** | video/mp4 | .mp4 |

### File Size & Duration Limits

| Limit Type | Value | Notes |
| - | - | - |
| **Max File Size** | 500 MB | |
| **Max Duration** | 4 hours | |
| **Min Sample Rate** | 8 kHz | Minimum for speech recognition |
| **Recommended Sample Rate** | 16 kHz+ | |
| **Max Sample Rate** | 48 kHz | |
| **Channels** | Mono or Stereo | Multi-speaker support |

### Processing Time Estimates

Estimated processing times by file duration are as follows.

| File Duration | Typical Processing Time |
| - | - |
| \< 5 minutes | 45-75 seconds |
| 5-20 minutes | 1-3 minutes |
| 20-60 minutes | 3-6 minutes |
| 1-4 hours | 6-18 minutes |

## Paragraph Summary Feature

Paragraph Summary summarizes the transcript or translation paragraph by paragraph.

### How it works

The transcript text is split into paragraphs and each paragraph is summarized. If you requested translations, the same process runs on the translated content. Summaries are generated asynchronously after transcription and translation finish.

## Sequence

```mermaid theme={"system"}
sequenceDiagram
    participant Client
    participant API as Tiro API
    participant Storage as File Storage
    participant Worker as Tiro Worker
    participant STT as STT Engine
    participant TR as Translation Engine
    participant WH as Webhook Service

    %% Job creation
    Client->>API: POST /v1/external/voice-file/jobs (workspaceGuid, transcriptLocaleHints, translationLocales)
    API->>Storage: Generate upload URL
    API-->>Client: 201 Created {id, uploadUri}

    %% File upload
    Client->>Storage: PUT audio file
    Client->>API: PUT /v1/external/voice-file/jobs/{jobId}/upload-complete

    %% Pipeline
    API->>Worker: Enqueue job (start processing)
    Worker->>STT: Speech-to-text
    STT-->>Worker: transcript
    alt translations requested
        Worker->>TR: Translate to requested locales
        TR-->>Worker: translations
    end
    Worker->>API: mark job = COMPLETED or FAILED
    opt Webhook configured
        API-->>WH: POST voice_file_job.completed or voice_file_job.failed
    end

    %% Results
    Client->>API: GET /v1/external/voice-file/jobs/{jobId}
    API-->>Client: status, timestamps
    Client->>API: GET /v1/external/voice-file/jobs/{jobId}/transcript
    API-->>Client: transcript
    Client->>API: GET /v1/external/voice-file/jobs/{jobId}/translations
    API-->>Client: translations[]
```

For a full example, see the [Step-by-Step Tutorial](/en/developers/voice-file/tutorial).

<PlanGateCard plan="enterprise" page="developers-voice-file-overview" lang="en" />


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.