文字起こしAPIでの開発:初めての統合
API開発者向けチュートリアル

文字起こしAPIでの開発:初めての統合

BMMamane B. MoussaMay 26, 2026Updated July 2, 20265 min read

Summarize this article with:

統合の5つのステップ

どんなバックエンドにも1時間以内で文字起こし機能を追加できます:APIキーを取得し、音声をPOST(ファイルアップロードまたはURL)、完了時にポーリングまたはWebhookを受信し、結果を取得して、必要な形式でエクスポートします。ここで示すパターンはConvertAudioToText APIを例にしていますが、「送信してからポーリング」という構造自体は、あらゆる非同期文字起こしプロバイダーで共通の標準です。まだプロバイダーを選定中であれば、まず2026年版の文字起こしAPI比較をご覧ください。

5つのステップとは、認証、ジョブ送信、結果取得、エラーハンドリング、エクスポートです。以下の各セクションで、実行可能なコードとともに1つずつ解説します。

ステップ1:APIキーを取得する

ConvertAudioToTextのAPIアクセスにはBusinessプランが必要です。 ダッシュボード内で「設定(Settings)」→「APIキー(API Keys)」と進み、新しいキーを作成します。統合に紐づくわかりやすい名前(「production-backend」「staging-ingest」など)を付けましょう。キーは一度しか表示されないため、すぐにコピーしてください。

キーの形式:ck_live_の後に64文字の16進数。パスワードと同じように扱い、環境変数に保存してください。ソース管理には絶対に入れないこと。

すべてのリクエストでAuthorization: Bearer <key>を使用します:

curl https://api.convertaudiototext.com/api/v1/result/some-job-id \
  -H "Authorization: Bearer ck_live_..."

Node.jsの場合:

const headers = { 'Authorization': `Bearer ${process.env.CATT_API_KEY}` };

Pythonの場合:

headers = {'Authorization': f'Bearer {os.environ["CATT_API_KEY"]}'}

ステップ2:文字起こしジョブを送信する

エンドポイントはPOST /api/v1/transcribeです。job_id付きのHTTP 201が即座に返され、実際の文字起こしは非同期で行われます。

送信方法は2つあります:ファイルアップロードとURL。どちらも同じレスポンス形式を返します。

ファイルアップロード

音声がローカルにある場合や、ユーザーからマルチパートフォームアップロードとして受け取る場合に使います。

import FormData from 'form-data';
import { createReadStream } from 'fs';
import fetch from 'node-fetch';

async function submitFileJob(filePath, language = 'en') {
  const form = new FormData();
  form.append('source', 'upload');
  form.append('language', language);
  form.append('file', createReadStream(filePath));

  const res = await fetch('https://api.convertaudiototext.com/api/v1/transcribe', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${process.env.CATT_API_KEY}` },
    body: form
  });

  if (!res.ok) throw new Error(`Submit failed: ${res.status}`);
  const { job_id, status } = await res.json();
  return { job_id, status };
}

URLによる送信

音声がすでにクラウドストレージ(S3、R2、GCS、または任意の公開HTTPS URL)にある場合に使います。サーバーがオリジンから高帯域でダウンロードするため、手元から再アップロードするよりも高速です。

async function submitURLJob(audioURL, language = 'en') {
  const res = await fetch('https://api.convertaudiototext.com/api/v1/transcribe', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CATT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      source: 'url',
      input_url: audioURL,
      language: language
    })
  });

  if (!res.ok) throw new Error(`Submit failed: ${res.status}`);
  const { job_id, status } = await res.json();
  return { job_id, status };
}

どちらも{ job_id: "uuid-string", status: "queued" }を返します。

ConvertAudioToTextの音声アップロードツール
ConvertAudioToTextの音声アップロードツール

APIの基盤となっている音声アップロードツール。Web UIを使ってもAPIを使っても、同じジョブパイプラインが動きます。

ステップ3:結果を取得する

完了したジョブはGET /api/v1/result/{job_id}で取得できます。待機方法は2つ:ポーリングとWebhookです。

ポーリング

status"completed"または"failed"になるまで5秒ごとにポーリングします。このエンドポイントは即座に応答し、遅延はすべて文字起こし処理側にあります。

async function pollForResult(jobId, maxWaitMs = 600000) {
  const start = Date.now();

  while (Date.now() - start < maxWaitMs) {
    const res = await fetch(
      `https://api.convertaudiototext.com/api/v1/result/${jobId}`,
      { headers: { 'Authorization': `Bearer ${process.env.CATT_API_KEY}` } }
    );
    const data = await res.json();

    if (data.status === 'completed') return data;
    if (data.status === 'failed') throw new Error(data.error || 'Job failed');

    await new Promise(r => setTimeout(r, 5000));
  }

  throw new Error('Timeout waiting for transcription result');
}

ジョブごとに毎秒1回より速くポーリングしないでください。グローバルなレート制限はIPごとに毎分100リクエストなので、多数のジョブに対して密なループを回すと制限に引っかかります。

Webhook

大規模運用でリソースを有効に使うには、Webhookでポーリングを完全に置き換えられます。Webhook URLを一度登録します:

curl -X POST https://api.convertaudiototext.com/api/v1/dashboard/webhooks \
  -H "Authorization: Bearer $CATT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/transcription",
    "events": ["job.completed", "job.failed"]
  }'

レスポンスにはsecretフィールドが含まれます。必ず保存してください。 サーバーは一致するイベントが発生するたびに、あなたのURLへPOSTします。ペイロードは次のような形です:

{
  "event": "job.completed",
  "timestamp": "2026-07-01T14:22:05Z",
  "data": {
    "job_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "duration": 1842,
    "result_url": "https://api.convertaudiototext.com/api/v1/result/550e8400-...",
    "completed_at": "2026-07-01T14:22:04Z"
  }
}

すべての配信をHMAC-SHA256で検証します。サーバーは署名をX-Webhook-Signatureヘッダーにsha256=<hex>形式で送ります:

import crypto from 'crypto';

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// In your Express/Fastify handler:
app.post('/webhooks/transcription', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-webhook-signature'];
  if (!verifyWebhookSignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = JSON.parse(req.body);
  if (event === 'job.completed') {
    // fetch full transcript from data.result_url
  }
  res.status(200).end();
});

WebhookにはBusinessプランと公開可能なHTTPS URLが必要です。両者の詳しい比較については、文字起こしにおけるWebhook vs ポーリングのガイドをご覧ください。

ステップ4:結果をパースする

完了したジョブのレスポンスでは、文字起こし結果がtranscriptキーの下にネストされています。正確な構造はサーバー側のフォーマッタによって決まりますが、必ず含まれるフィールドは次のとおりです:

{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "language": "en",
  "duration": 1842,
  "filename": "interview.mp3",
  "transcript": {
    "text": "Hello, welcome to the show...",
    "summary": "An interview about...",
    "confidence": 0.97,
    "word_count": 4210,
    "timeline": [
      {
        "speaker": "Speaker 0",
        "text": "Hello, welcome to the show.",
        "start": 0.12,
        "end": 1.87
      }
    ],
    "topics": [...],
    "sentiments": [...]
  }
}

textフィールドは全文を連結した文字起こしです。timeline配列には、タイムスタンプ付きの話者別発言が入っています。summarytopicssentimentsフィールドは、AIインサイトを含むプランで英語音声の場合に値が入り、Starterプランでは削除されるか存在しません。

ステップ5:エクスポート形式

フォーマット済みファイルは次のようにダウンロードします:

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/srt" \
  -H "Authorization: Bearer $CATT_API_KEY" > captions.srt  # SRT for video captions

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/vtt" \
  -H "Authorization: Bearer $CATT_API_KEY" > captions.vtt  # WebVTT

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/txt" \
  -H "Authorization: Bearer $CATT_API_KEY" > transcript.txt  # Plain text with speaker labels

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/json" \
  -H "Authorization: Bearer $CATT_API_KEY" > transcript.json  # Structured JSON

curl "https://api.convertaudiototext.com/api/v1/result/$JOB_ID/docx" \
  -H "Authorization: Bearer $CATT_API_KEY" > transcript.docx  # DOCX

すべてのエクスポートエンドポイントには有料プランが必要です。無料枠からのリクエストはupgrade_urlフィールド付きのHTTP 402を返します。大規模な字幕ワークフローを構築する場合、SRTとVTTのエンドポイントはバッチパイプラインでうまく機能します。

エラーハンドリング

ハンドリングすべきエラーは3種類あり、それぞれ異なる復旧戦略を取ります。

4xxエラーは自分側のバグです。 認証の失敗(401)、必須フィールドの欠落(400)、クォータの枯渇(402)、レート制限への到達(429)。リクエストを修正せずにリトライしてはいけません。429の場合は、レスポンスボディのretry_afterフィールドを読みます。

5xxエラーは一時的です。 指数バックオフでリトライしてください。ほとんどは30秒以内に解消します。

ジョブレベルの失敗。 ジョブは正常に送信できても(HTTP 201)、後の処理中に失敗することがあります。status"failed"になり、errorに理由が入ります。よくある原因:破損した音声、全程無音の録音、非対応のコーデック。音声ソースが有効な場合にのみジョブをリトライしてください。

async function transcribeWithRetry(audioPath, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      const { job_id } = await submitFileJob(audioPath);
      return await pollForResult(job_id);
    } catch (err) {
      // 4xx: fix the request, do not retry
      if (err.status >= 400 && err.status < 500) throw err;
      // Last attempt: give up
      if (attempt === maxAttempts - 1) throw err;
      // 5xx: wait and try again
      const delayMs = 1000 * Math.pow(2, attempt);
      await new Promise(r => setTimeout(r, delayMs));
    }
  }
}

レート制限

認証済みAPIリクエストに適用される制限:

レイヤー制限対象範囲
グローバル毎分100リクエストIPアドレスごと
文字起こしジョブ毎分10件の送信認証済みユーザーごと
アップロードマルチパート(署名呼び出し)毎分1000件IPごと(独立した上限)

文字起こしジョブの制限は、毎分最大10ジョブを送信できるという意味であり、同時実行10という意味ではありません。その閾値を超えたジョブはサーバー側でキューに入ります。

いずれかの上限に達すると、レスポンスボディに秒単位のretry_afterを含むHTTP 429が返されます。

動作する完全なサンプル

import { createReadStream } from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';

const API_KEY = process.env.CATT_API_KEY;
const BASE    = 'https://api.convertaudiototext.com/api/v1';

async function submit(filePath, language = 'en') {
  const form = new FormData();
  form.append('source', 'upload');
  form.append('language', language);
  form.append('file', createReadStream(filePath));

  const res = await fetch(`${BASE}/transcribe`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${API_KEY}` },
    body: form
  });

  if (!res.ok) throw Object.assign(new Error('Submit failed'), { status: res.status });
  return res.json();
}

async function poll(jobId, maxWaitMs = 600000) {
  const deadline = Date.now() + maxWaitMs;

  while (Date.now() < deadline) {
    const res = await fetch(`${BASE}/result/${jobId}`, {
      headers: { 'Authorization': `Bearer ${API_KEY}` }
    });
    const data = await res.json();

    if (data.status === 'completed') return data;
    if (data.status === 'failed') throw new Error(data.error || 'Transcription failed');

    await new Promise(r => setTimeout(r, 5000));
  }
  throw new Error('Timeout');
}

export async function transcribeFile(filePath, language = 'en') {
  const { job_id } = await submit(filePath, language);
  const result     = await poll(job_id);
  return result.transcript?.text ?? '';
}

ここから先は、同一ファイルの再処理を避けるために文字起こし結果のキャッシュを追加し、同時実行ジョブが少し増えたらポーリングをWebhookに置き換えるとよいでしょう。

私見:「送信してからポーリング」という構造は主要な非同期文字起こしプロバイダーすべてで共通なので、クライアントコードはほぼそのまま流用できます。異なるのは認証ヘッダーの形式、レスポンスのフィールド名、そして話者ラベルのような高度な機能により高いプランが必要かどうかです。リリース前に、それぞれをプロバイダーの最新ドキュメントで確認してください。

APIキーやプラン階層の管理なしに、たまにきれいな文字起こしが必要なだけであれば、ConvertAudioToTextならセットアップ不要で無料のまま、アップロード・文字起こし・テキストのコピーができます。

よくある質問

APIを利用するにはどのプランが必要ですか?

ConvertAudioToTextでAPIキーを作成するには、DeveloperプランまたはBusinessプランが必要です。Developerは月600分、Businessは無制限です。購入前に/pricingで最新のプラン内容をご確認ください。

文字起こしジョブのレート制限はどのくらいですか?

グローバルな上限はIPアドレスごとに毎分100リクエストです。文字起こしジョブの送信はさらに、ユーザーごとに毎分10ジョブまでに制限されています。いずれかの上限に達すると、Retry-Afterヘッダー付きのHTTP 429が返されます。

ポーリングとWebhook、どちらを使うべきですか?

ポーリングは少量または単発の統合に向いており、公開エンドポイントも不要です。同時に複数のジョブを扱うようになったらWebhookの方が有利です。数秒ごとにN件のジョブをポーリングする代わりに、完了のたびにサーバーが1回のPOSTを受け取れます。WebhookにはBusinessプランと公開可能なHTTPS URLが必要です。

Webhookペイロードが本物かどうか検証するにはどうすればよいですか?

Webhook登録のたびにシークレット文字列が返されます。サーバーがペイロードを配信する際、そのシークレットを使って生のJSONボディのHMAC-SHA256を計算し、結果をX-Webhook-Signatureヘッダーにsha256=hex形式で送ります。こちら側でも同じHMACを再計算して比較してください。一致しないリクエストは拒否します。

APIがサポートするエクスポート形式は何ですか?

完了したジョブは、GET /api/v1/result//経由でSRT、VTT、TXT、JSON、DOCX、PDFとしてエクスポートできます。すべてのエクスポートエンドポイントには有料プランが必要で、無料枠からのリクエストはHTTP 402を返します。

出典

Try transcription free

Convert any audio or video to clean, unwatermarked text — speaker labels, timestamps, and AI summaries included. First 10 minutes free, no account.

Related Articles