ListenHubDocs
API リファレンス

テキスト読み上げ

低レイテンシの単一話者ストリーミングから長文の非同期ナレーションまで、5 つのエンドポイントでテキストを自然な音声に変換します。

ListenHub はテキスト読み上げ(Text to Speech)のエンドポイントを複数提供しており、それぞれ異なる用途に合わせて調整されています。base URL、認証方式、speaker ID は共通ですが、レイテンシ、レスポンスの種類、対応する話者数が異なります。

すべてのリクエストは https://api.marswave.ai/openapi/v1 に送信し、API キーで認証します:

Authorization: Bearer $LISTENHUB_API_KEY

キーは listenhub.ai/settings/api-keys で作成します。JSON レスポンスはすべて { "code": 0, "message": "", "data": { ... } } の形にラップされ、code が 0 以外ならエラーを示します。ストリーミング系のエンドポイント(/v1/tts、/v1/audio/speech)は、このラッパーではなく生のバイナリ音声を返します。

エンドポイントの選び方

エンドポイント話者同期 / 非同期レスポンス適した用途
POST /v1/tts単一同期バイナリ音声ストリームリアルタイム再生、アプリ内音声、低レイテンシ
POST /v1/audio/speech単一同期バイナリ音声ストリームOpenAI TTS エンドポイントの差し替え先
POST /v1/speech複数同期audioUrl を含む JSON対話、オーディオブック、準備済みの複数話者スクリプト
POST /v1/flow-speech/episodes単一非同期episodeId でポーリング記事やニュースレターのナレーション、URL から音声へ
POST /v1/flow-speech/episodes/tts複数非同期episodeId でポーリングそのまま変換する長い複数話者スクリプト

目安として、単一の話者で音声バイトをすぐに受け取りたいときは /v1/tts、短い複数話者スクリプトを 1 回の呼び出しでホスト済み URL にしたいときは /v1/speech、バックグラウンドで実行するのが妥当なほど長い処理には /v1/flow-speech/episodes 系のエンドポイントを選びます。

クレジット消費は音声の長さに応じて増え、ジョブ完了後に該当するレスポンス(credits)で返されます。生成前にコストを見積もるには、API リファレンスのクレジット見積もりエンドポイントを参照してください。


ストリーミング TTS

POST /v1/tts

低レイテンシの単一話者合成です。レスポンスボディは生成しながら流される生のバイナリ音声なので、クリップ全体が完成する前に先頭のバイトが届きます。リアルタイム再生やインタラクティブな音声機能に使います。

このエンドポイントは OpenAI のテキスト読み上げと同じリクエスト形式(input / voice / response_format)を受け付けるため、既存のクライアントを容易に移行できます。

curl -X POST "https://api.marswave.ai/openapi/v1/tts" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Hello, welcome to ListenHub text-to-speech.",
    "voice": "EN-Man-General-01",
    "response_format": "mp3",
    "speed": 1.25
  }' \
  --output output.mp3
const response = await fetch('https://api.marswave.ai/openapi/v1/tts', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    input: 'Hello, welcome to ListenHub text-to-speech.',
    voice: 'EN-Man-General-01',
    response_format: 'mp3',
    speed: 1.25,
  }),
});
const buffer = Buffer.from(await response.arrayBuffer());
// Write `buffer` to a file, or pipe `response.body` to a player
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/tts',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'input': 'Hello, welcome to ListenHub text-to-speech.',
        'voice': 'EN-Man-General-01',
        'response_format': 'mp3',
        'speed': 1.25,
    },
    stream=True,
)

with open('output.mp3', 'wb') as f:
    for chunk in response.iter_content(chunk_size=8192):
        f.write(chunk)

リクエストパラメータ

フィールド型必須説明
inputstringはい合成するテキスト。最大 20,000 文字。
voicestringはいSpeaker ID(Speakers の speakerId の値)。
response_formatstringいいえ要求する音声フォーマット。mp3、opus、aac、flac、wav、pcm のいずれか。デフォルトは mp3。
speednumberいいえ生成速度の倍率——再生速度ではなく、生成される音声そのものに適用されます。範囲は 0.5〜2.0、小数点以下 2 桁まで。既定値は 1(等速)。

レスポンスボディは JSON ラッパーではなくバイナリ音声です。ストリームまたは blob として読み取ってください。実際に返るコンテナは、opus を除くすべてのフォーマットで MP3(Content-Type: audio/mpeg)であり、opus のみ OGG/Opus(Content-Type: audio/ogg)で返されます。音声の送出が始まる前にリクエストが失敗した場合は JSON のエラーオブジェクトが返るため、ボディを音声として扱う前に Content-Type を確認してください。


OpenAI 互換 TTS

POST /v1/audio/speech

OpenAI SDK や OpenAI 互換の連携先がデフォルトで呼び出すパス上に置かれた、/v1/tts と完全に同一のエイリアスです。リクエストボディ、response_format の選択肢、ストリーミングのバイナリレスポンスはすべて同じです。既存の OpenAI TTS クライアントの向き先をこの URL に変え、ListenHub の API キーと ListenHub の voice ID を指定すれば、コードを変更せずに切り替えられます。

curl -X POST "https://api.marswave.ai/openapi/v1/audio/speech" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "This endpoint mirrors the OpenAI speech API.",
    "voice": "EN-Woman-General-01",
    "response_format": "mp3"
  }' \
  --output output.mp3
const response = await fetch('https://api.marswave.ai/openapi/v1/audio/speech', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    input: 'This endpoint mirrors the OpenAI speech API.',
    voice: 'EN-Woman-General-01',
    response_format: 'mp3',
  }),
});
const buffer = Buffer.from(await response.arrayBuffer());
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/audio/speech',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'input': 'This endpoint mirrors the OpenAI speech API.',
        'voice': 'EN-Woman-General-01',
        'response_format': 'mp3',
    },
    stream=True,
)

with open('output.mp3', 'wb') as f:
    for chunk in response.iter_content(chunk_size=8192):
        f.write(chunk)

リクエストパラメータとレスポンスの挙動は ストリーミング TTS と同じです。


複数話者スクリプトから音声へ

POST /v1/speech

準備済みの複数話者スクリプトから 1 つの音声ファイルを生成します。各行が自身の speakerId を持つため、対話に合わせて話者を交互に切り替えられます。呼び出しは同期で、ホスト済みの音声 URL と字幕をレスポンスで返すので、ポーリングは不要です。

curl -X POST "https://api.marswave.ai/openapi/v1/speech" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "scripts": [
      { "content": "Welcome everyone to this episode.", "speakerId": "EN-Man-General-01" },
      { "content": "Today we are discussing an interesting topic.", "speakerId": "EN-Woman-General-01" },
      { "content": "Great, let us begin.", "speakerId": "EN-Man-General-01" }
    ]
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/speech', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    scripts: [
      { content: 'Welcome everyone to this episode.', speakerId: 'EN-Man-General-01' },
      { content: 'Today we are discussing an interesting topic.', speakerId: 'EN-Woman-General-01' },
      { content: 'Great, let us begin.', speakerId: 'EN-Man-General-01' },
    ],
  }),
});
const { data } = await response.json();
console.log(data.audioUrl);
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/speech',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'scripts': [
            {'content': 'Welcome everyone to this episode.', 'speakerId': 'EN-Man-General-01'},
            {'content': 'Today we are discussing an interesting topic.', 'speakerId': 'EN-Woman-General-01'},
            {'content': 'Great, let us begin.', 'speakerId': 'EN-Man-General-01'},
        ]
    },
)
data = response.json()['data']
print(data['audioUrl'])

リクエストパラメータ

フィールド型必須説明
scriptsarrayはい1 行以上のスクリプト。記載順に合成されます。
scripts[].contentstringはい行のテキスト。空にはできません。全行を合計した長さの上限は 20,000 文字です。
scripts[].speakerIdstringはいこの行の Speaker ID。行ごとに異なる話者を指定できます。
speednumberいいえ生成速度の倍率——再生速度ではなく、生成される音声そのものに適用されます。範囲は 0.5〜2.0、小数点以下 2 桁まで。既定値は 1(等速)。

レスポンス

{
  "code": 0,
  "message": "",
  "data": {
    "audioUrl": "https://assets.listenhub.app/listenhub-public-prod/podcast/example.mp3",
    "audioDuration": 12500,
    "subtitlesUrl": "https://assets.listenhub.app/listenhub-public-prod/podcast/example.srt",
    "taskId": "1eed39d387a046c0a1213e6b8f139d77",
    "credits": 12
  }
}
フィールド型説明
audioUrlstring生成された MP3 ファイルの URL。
audioDurationinteger音声の長さ(ミリ秒)。
subtitlesUrlstringSRT 字幕ファイルの URL。有効期限は 24 時間です。
taskIdstringタスク ID。問い合わせの際に伝えると、サポートが該当リクエストを特定できます。
creditsintegerこのリクエストで消費されたクレジット。

長文テキスト読み上げ

POST /v1/flow-speech/episodes

まとまったテキスト、または URL の内容を単一話者のナレーションに変換します。このエンドポイントは非同期で動作し、リクエストは即座に episodeId を返すので、処理完了後にポーリングで音声を取得します。同期呼び出しでは待ち時間が現実的でないような、長めの入力を想定しています。

ソーステキストの扱い方は 2 つのモードで制御します:

  • smart(デフォルト) -- 先にテキストを整えます。句読点、文法、書式を修正するため、粗い入力や貼り付けたままの入力でも自然に読み上げられます。
  • direct -- 書き換えを行わず、テキストをそのまま合成します。スクリプトが確定済みの場合に使います。

制約:sources はちょうど 1 件、話者もちょうど 1 名、テキストソースは 10 文字以上(上限 20,000 文字)です。

Smart モード(AI による推敲)

curl -X POST "https://api.marswave.ai/openapi/v1/flow-speech/episodes" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {
        "type": "text",
        "content": "welcome to listenhub this text is intentionally rough and punctuation will be improved automatically"
      }
    ],
    "speakers": [
      { "speakerId": "EN-Woman-General-01" }
    ],
    "language": "en",
    "mode": "smart"
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/flow-speech/episodes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sources: [
      {
        type: 'text',
        content: 'welcome to listenhub this text is intentionally rough and punctuation will be improved automatically',
      },
    ],
    speakers: [{ speakerId: 'EN-Woman-General-01' }],
    language: 'en',
    mode: 'smart',
  }),
});
const { data } = await response.json();
console.log('Episode ID:', data.episodeId);
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/flow-speech/episodes',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'sources': [
            {
                'type': 'text',
                'content': 'welcome to listenhub this text is intentionally rough and punctuation will be improved automatically',
            }
        ],
        'speakers': [{'speakerId': 'EN-Woman-General-01'}],
        'language': 'en',
        'mode': 'smart',
    },
)
print('Episode ID:', response.json()['data']['episodeId'])

Direct モード

curl -X POST "https://api.marswave.ai/openapi/v1/flow-speech/episodes" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {
        "type": "text",
        "content": "Welcome to ListenHub. This script is already finalized and should be converted as-is."
      }
    ],
    "speakers": [
      { "speakerId": "EN-Man-General-01" }
    ],
    "language": "en",
    "mode": "direct"
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/flow-speech/episodes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sources: [
      {
        type: 'text',
        content: 'Welcome to ListenHub. This script is already finalized and should be converted as-is.',
      },
    ],
    speakers: [{ speakerId: 'EN-Man-General-01' }],
    language: 'en',
    mode: 'direct',
  }),
});
const { data } = await response.json();
console.log('Episode ID:', data.episodeId);
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/flow-speech/episodes',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'sources': [
            {
                'type': 'text',
                'content': 'Welcome to ListenHub. This script is already finalized and should be converted as-is.',
            }
        ],
        'speakers': [{'speakerId': 'EN-Man-General-01'}],
        'language': 'en',
        'mode': 'direct',
    },
)
print('Episode ID:', response.json()['data']['episodeId'])

URL からコンテンツを読み込む

type を url にし、ページのアドレスを uri で渡します。ListenHub がページを取得し、読み上げ可能な本文を抽出してから合成します。(後方互換のため、uri の代わりに content も引き続き受け付けます。)

curl -X POST "https://api.marswave.ai/openapi/v1/flow-speech/episodes" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {
        "type": "url",
        "uri": "https://example.com/article.html"
      }
    ],
    "speakers": [
      { "speakerId": "EN-Woman-General-01" }
    ],
    "language": "en",
    "mode": "smart"
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/flow-speech/episodes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sources: [{ type: 'url', uri: 'https://example.com/article.html' }],
    speakers: [{ speakerId: 'EN-Woman-General-01' }],
    language: 'en',
    mode: 'smart',
  }),
});
const { data } = await response.json();
console.log('Episode ID:', data.episodeId);
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/flow-speech/episodes',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'sources': [{'type': 'url', 'uri': 'https://example.com/article.html'}],
        'speakers': [{'speakerId': 'EN-Woman-General-01'}],
        'language': 'en',
        'mode': 'smart',
    },
)
print('Episode ID:', response.json()['data']['episodeId'])

リクエストパラメータ

フィールド型必須説明
sourcesarrayはいコンテンツソース。ちょうど 1 件を指定します。
sources[].typestringはいtext または url。
sources[].contentstringtext の場合読み上げるテキスト。最小 10 文字、最大 20,000 文字。ごく短いクリップには代わりに /v1/speech を使ってください。
sources[].uristringurl の場合(推奨)読み込み元のページ URL。url ソースでは uri か content のいずれかが必要です。
speakersarrayはい話者リスト。ちょうど 1 件を指定します。
speakers[].speakerIdstringはいSpeaker ID。
languagestringいいえソースの言語:en、zh、ja。省略時はコンテンツから推定されます。
modestringいいえsmart(AI による推敲)または direct(そのまま)。デフォルトは smart。
speednumberいいえ生成速度の倍率——再生速度ではなく、生成される音声そのものに適用されます。範囲は 0.5〜2.0、小数点以下 2 桁まで。既定値は 1(等速)。

レスポンスにはタスク ID のみが含まれます:

{
  "code": 0,
  "message": "",
  "data": {
    "episodeId": "665f1c2a9b3e4d0012a4c8e1"
  }
}

結果をポーリングする

GET /v1/flow-speech/episodes/{episodeId}

返された episodeId で、processStatus が success になるまでポーリングします。

curl -X GET "https://api.marswave.ai/openapi/v1/flow-speech/episodes/{episodeId}" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"
const response = await fetch(
  `https://api.marswave.ai/openapi/v1/flow-speech/episodes/${episodeId}`,
  { headers: { Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}` } },
);
const { data } = await response.json();
console.log('Status:', data.processStatus);
console.log('Audio URL:', data.audioUrl);
import os
import requests

response = requests.get(
    f'https://api.marswave.ai/openapi/v1/flow-speech/episodes/{episode_id}',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
)
data = response.json()['data']
print('Status:', data['processStatus'])
print('Audio URL:', data.get('audioUrl'))

ジョブが完了すると(processStatus が success):

{
  "code": 0,
  "message": "",
  "data": {
    "episodeId": "665f1c2a9b3e4d0012a4c8e1",
    "createdAt": 1717430000000,
    "processStatus": "success",
    "completedTime": 1717430090000,
    "title": "Article Title",
    "outline": "...",
    "cover": "https://assets.listenhub.app/.../cover.png",
    "audioUrl": "https://assets.listenhub.app/listenhub-public-prod/podcast/665f1c2a9b3e4d0012a4c8e1.mp3",
    "audioStreamUrl": "https://assets.listenhub.app/listenhub-public-prod/podcast/665f1c2a9b3e4d0012a4c8e1.m3u8",
    "subtitlesUrl": "https://assets.listenhub.app/.../665f1c2a9b3e4d0012a4c8e1.srt",
    "scripts": "Full narration script text..."
  }
}
フィールド型説明
episodeIdstringエピソードの識別子。
createdAtinteger作成時刻のタイムスタンプ(ミリ秒)。
processStatusstring現在の状態:pending、success、fail。success になるまでポーリングします。fail はジョブが完了しなかったことを示します。
failCodeinteger失敗時に返され、その理由を示します。
completedTimeinteger完了時刻のタイムスタンプ(ミリ秒)。
titlestring生成されたエピソードのタイトル。
outlinestring生成されたナレーションのアウトライン。
coverstringカバー画像の URL。
audioUrlstringMP3 音声ファイルの URL。
audioStreamUrlstringHLS ストリーミング URL(.m3u8)。
subtitlesUrlstringSRT 字幕ファイルの URL。
scriptsstringナレーションの全文スクリプト。

長文ジョブは通常 1〜2 分で完了します。実用的なポーリング方針は、作成から 30 秒待ってから 10 秒ごとに問い合わせることです。失敗時は processStatus が fail になり、failCode がその理由を示します。


複数話者 Direct(非同期)

POST /v1/flow-speech/episodes/tts

長く、準備済みの複数話者スクリプトをエピソードに変換します。これは /v1/speech の非同期・複数話者版です。各行が自身の speakerId を保持し、テキストはそのまま(direct モードで)合成され、呼び出しはポーリング用の episodeId を返します。複数話者スクリプトが長すぎて 1 回の同期 /v1/speech リクエストでは処理しきれない場合に使います。

curl -X POST "https://api.marswave.ai/openapi/v1/flow-speech/episodes/tts" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Roundtable Discussion",
    "scripts": [
      { "content": "Thanks for joining the roundtable today.", "speakerId": "EN-Man-General-01" },
      { "content": "Happy to be here. Let us dig into the agenda.", "speakerId": "EN-Woman-General-01" }
    ]
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/flow-speech/episodes/tts', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'Roundtable Discussion',
    scripts: [
      { content: 'Thanks for joining the roundtable today.', speakerId: 'EN-Man-General-01' },
      { content: 'Happy to be here. Let us dig into the agenda.', speakerId: 'EN-Woman-General-01' },
    ],
  }),
});
const { data } = await response.json();
console.log('Episode ID:', data.episodeId);
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/flow-speech/episodes/tts',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'title': 'Roundtable Discussion',
        'scripts': [
            {'content': 'Thanks for joining the roundtable today.', 'speakerId': 'EN-Man-General-01'},
            {'content': 'Happy to be here. Let us dig into the agenda.', 'speakerId': 'EN-Woman-General-01'},
        ],
    },
)
print('Episode ID:', response.json()['data']['episodeId'])

リクエストパラメータ

フィールド型必須説明
scriptsarrayはい1 行以上のスクリプト。記載順に合成されます。
scripts[].contentstringはい行のテキスト。空にはできません。全行を合計した長さの上限は 20,000 文字です。
scripts[].speakerIdstringはいこの行の Speaker ID。行ごとに異なる話者を指定できます。
titlestringいいえ任意のエピソードタイトル。省略時は自動生成されます。
speednumberいいえ生成速度の倍率——再生速度ではなく、生成される音声そのものに適用されます。範囲は 0.5〜2.0、小数点以下 2 桁まで。既定値は 1(等速)。

レスポンスは episodeId を返します。結果のポーリングには、長文ジョブと同じステータスエンドポイント GET /v1/flow-speech/episodes/{episodeId} を使います。

{
  "code": 0,
  "message": "",
  "data": {
    "episodeId": "665f1c2a9b3e4d0012a4c8e1"
  }
}

関連ページ

このページの内容