本文へ移動
Euphona

開発者向け

Euphona のエンジンを API で

api.euphonaai.com/v1で提供する、1つのREST API仕様。SSEでリアルタイムに進捗を確認できる非同期ジョブ、署名付きのファイル転送、プレフィックス付きのキー、無料のテストモード。独自に12か月のサポート期間を設けた、ファーストパーティの製品です。

キーはStudioの設定で作成します。eu_test_…はすぐに無料で、eu_live_…はカードを登録すれば同じ画面から作成でき、最初のジョブから課金されます。テストモードでは、仕様のすべてが動作します。

すべてはジョブ

curl -X POST https://api.euphonaai.com/v1/masters \
  -H "Authorization: Bearer $EUPHONA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"file_id": "file_8Kd2mQTz3XaV7pLw"}'

HTTP/2 202
{
  "id": "job_7Vd2mHs5Yq8Tz3Xa",
  "object": "job",
  "type": "mastering",
  "status": "queued",
  "stage": null,
  "metering": {
    "pool": "payg",
    "charged_minutes": 0,
    "test_mode": false
  }
}

設計

設計の考え方

譲れない4つの決定です。どれも、最初の障害を乗り越えられる連携と、そうでない連携とを分けるものだからです。

すべてはジョブ

機能のエンドポイントは、202とジョブリソースを返します。同期処理のエンドポイントはなく、今後も作りません。3分の曲をマスタリング品質で処理することは、リクエストとレスポンスの1往復で済むものではありません。そうでないふりをするAPIは、あらゆる連携に間違った書き方を教えることになります。

オーディオはAPIを通らず、迂回する

POST /v1/filesは、files.euphonaai.com上の署名付きで再開可能なアップロード先を返します。結果は、有効期限付きの署名付きダウンロードURLとして返されます。API自体はJSONのコントロールプレーンで、オーディオのデータを1バイトも運びません。

このAPIは製品そのもの。副産物ではない

Euphona Studioは、別のファーストパーティAPIを使っています。これは意図的です。2つは1つのエンジンを共有する別々の製品なので、アプリ側が望む変更がこの仕様に及ぶことはなく、この仕様の12か月のサポート期間がアプリ側の負担になることもありません。正直に言えば、その結果として、アプリにはできてこのAPIにはできないことがあり得ます。それが重要な場合はお問い合わせください。予期せぬ驚きではなく、ロードマップの項目になります。

ドキュメントが仕様そのもの

OpenAPI 3.1のドキュメントが唯一の正です。サーバーのルートと型はそこから生成され、デプロイを止めるテストが、提供中のルートと宣言されたオペレーションが一致していることを保証します。そのため、ドキュメントの内容と実際の提供内容のずれは、私たちが見張ると約束するものではなく、ビルドの失敗になります。同じドキュメントから生成するSDKは、開発者プレビューとともに提供します。

手順

連携の全体像

ファイルを作成し、署名付きのアップロード先にデータを送り、ジョブを送信して、SSEで完了まで見届けます。4つのステップのうち、機能ごとに異なるのは3つ目だけです。

1:ファイルを作成し、アップロード先を取得する

curl -X POST https://api.euphonaai.com/v1/files \
  -H "Authorization: Bearer $EUPHONA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"filename":"take-07.wav","bytes":58210044,"content_type":"audio/wav"}'

{
  "id": "file_8Kd2mQ...",
  "object": "file",
  "status": "awaiting_upload",
  "upload": { "protocol": "...", "url": "https://files.euphonaai.com/...", "expires_at": "..." }
}

2:データは私たちではなく、files.に送る

# upload.protocolを読み取ってください。決め打ちはしないでください。
# 再開可能なアップロードの仕組みは、仕様の範囲内での実装上の選択であり、
# メジャーバージョンを上げずに変わることがあります。だからこそ、ドキュメントではなくフィールドなのです。

3:ジョブを見守る

curl -N https://api.euphonaai.com/v1/jobs/job_4Rb.../events \
  -H "Authorization: Bearer $EUPHONA_API_KEY"

event: job.progress
data: {"status":"analyzing","stage":"analyze","progress":{"fraction":0.4}}

event: job.completed
data: {"status":"succeeded","result":{...},"metering":{"pool":"payg","charged_minutes":3.4}}

4:ジョブ自体と突き合わせる

curl https://api.euphonaai.com/v1/jobs/job_4Rb... \
  -H "Authorization: Bearer $EUPHONA_API_KEY"

{"id":"job_4Rb...","status":"succeeded","result":{...},
 "metering":{"pool":"payg","charged_minutes":3.4}}

# ジョブリソースは、ステートマシンの現時点での正しい状態です。ストリームで何が見えても、
# 何を見逃しても、行動する前に読み直してください。切断されたSSE接続は
# Last-Event-IDで再接続し、何も失いません。

リファレンス

共通エンドポイント

すべての機能が共有するプラットフォームのエンドポイントです。機能ごとに作成用のエンドポイントが1つ加わり、そこから返されるジョブは、以下のすべてを通ります。ライフサイクルも、イベントも、レポートも同じです。

v1仕様のエンドポイント
メソッドパス内容
POST/v1/filesファイルを作成し、署名付きで再開可能なアップロード先を取得します。
GET/v1/files/{id}取り込みの状態、測定した長さ、サンプルレート、チャンネル数、フォーマット。
DELETE/v1/files/{id}ファイルと、そこから作られたものを削除します。ジョブが使用中の間は拒否されます。
GET/v1/files/{id}/download有効期限付きの署名付きダウンロードURL。
GET/v1/jobs統合された読み取り用エンドポイント。カーソルでページ分割されます。
GET/v1/jobs/{id}状態、ステージ、進捗、課金、測定レポート。
POST/v1/jobs/{id}/cancelキャンセルします。処理ステージが始まる前なら課金されません。
POST/v1/jobs/{id}/inputawaiting_inputのジョブからの問いに答えて、再開します。課金対象外です。
GET/v1/jobs/{id}/eventsジョブが終わるまでのサーバー送信イベント。
POST/v1/projectsファイルとジョブをまとめます。Track Graphはプロジェクトごとです。
GET/v1/projects/{id}1つのプロジェクトと、そのファイルIDとジョブID。
GET/v1/usage利用枠の状態と、2つの課金プール。
GET/v1/webhook-endpointsこのキーのモードのWebhookエンドポイント。
POST/v1/webhook-endpointshttps のURLを登録します。何かが保存される前に、署名付きチャレンジに応答します。
GET/v1/webhook-endpoints/{id}エンドポイント1件。シークレットは二度と表示されません。
PATCH/v1/webhook-endpoints/{id}イベント、説明、URLを変更します。新しいURLは再度検証されます。
DELETE/v1/webhook-endpoints/{id}削除します。未配信のイベントは破棄されます。
POST/v1/webhook-endpoints/{id}/rotate-secret新しい署名シークレット。古いものも移行期間中は署名を続けます。
GET/v1/events30日分のイベントと、各配信の結果。
GET/v1/events/{id}配信されたとおりのイベント1件と、その配信状況。

Webhook

ジョブの完了を受け取る

ジョブが完了したとき、またはあなたの回答を待っているときに届く署名付きの通知です。ストリームを開いたままにしたり、ポーリングしたりする必要はありません。

イベント
job.completed、job.failed、job.canceled、job.awaiting_input。いずれもイベントで、そのdataはポーリングで返るものとまったく同じジョブリソースです。
ワークスペースのイベント
file.expiringは、保存されているアップロードに削除されうる日付が付いたときに送られ、dataはそのファイルです。usage.thresholdは、利用枠が上限の80%または100%に達したときに送られます。どちらもテスト・本番どちらのモードのエンドポイントにも届き、ファイルごとに1回、またはしきい値と期間ごとに1回です。
登録
webhooks:manageスコープを持つキーが必要です。新しいキーにはこのスコープはありません。URLはhttpsを使い、公開アドレスにのみ名前解決される必要があります。また、何かが保存される前に、署名付きチャレンジをそのまま返す2xxレスポンスで応答する必要があります。配信はIPv4でのみ送られるため、ホストにはIPv4アドレスが必要です。
配信
少なくとも1回届くため、Euphona-Event-Idで重複を除いてください。10秒以内に2xx以外が返った場合は、指数バックオフで24時間再試行し、その後はデッドレターとしてGET /v1/eventsに残します。リダイレクトには従わず、順序も保証されません。ジョブと突き合わせてください。
署名
Euphona-Signatureには、タイムスタンプと、タイムスタンプ・ドット・未加工のボディに対するHMAC-SHA256(キーはエンドポイントのシークレット)が含まれます。パースする前に検証し、定数時間で比較し、手元の時計と5分以上ずれたタイムスタンプは拒否してください。
送信元アドレス
配信は65.21.69.162から送られます。ファイアウォールで受信トラフィックを絞っている場合はこれらを許可してください。それでも署名は必ず検証してください。リクエストの送信元アドレスは、誰が送ったかの証明にはなりません。

配信を検証する(Node.js)

import { createHmac, timingSafeEqual } from "node:crypto";

// 届いたとおりの未加工のボディを渡してください。パースして再シリアライズした
// JSONは、署名されたメッセージではありません。
export function verifyEuphonaSignature(rawBody, header, secret, now = Date.now() / 1000) {
  const parts = header.split(",").map((part) => part.trim().split("="));
  const t = Number(parts.find(([key]) => key === "t")?.[1]);
  const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value);
  if (!Number.isFinite(t) || Math.abs(now - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((value) => {
    const given = Buffer.from(value, "hex");
    return given.length === expected.length && timingSafeEqual(given, expected);
  });
}

運用

深夜3時に困ること

連携を書く前に読んでおくべき6つの挙動です。夜中に起こされてからでは遅すぎます。

キー
eu_live_…とeu_test_…。ワークスペースが所有し、作成時に1回だけ表示され、保存時はハッシュ化されます。最大30日間の重複期間を設けてローテーションできるので、ローテーションで障害が起きることはありません。テスト用のキーは専用の月間上限の範囲内で無料で、専用のスケジューリングクラスで実行されます。マスターではなくプレビューを返すため、何も購入せずに連携を最初から最後まで構築できます。
エラー
常にRFC 9457のapplication/problem+jsonで、機械可読で安定したcode、人が読むためのdetail、リクエストIDを含みます。分岐は文章ではなく、必ずcodeで行ってください。文章はローカライズされますが、コードは仕様の一部です。
冪等性
すべてのPOSTにIdempotency-Keyを付けます。同じキーと同じボディなら、作成したリソースが存在する限り元のレスポンスを再送します。同じキーで異なるボディは拒否されます。そのため、タイムアウト後に再試行しても、課金される2つ目のジョブが始まるのではなく、元のジョブが返されます。
制限
キーごとのスライディングウィンドウで、標準のRateLimit-*ヘッダーを返します。従量課金では1分あたり60リクエスト、利用量コミットメント契約ではそれ以上です。リミッターが制御するのはリクエストの頻度だけで、スループットは利用枠とキューによって決まります。
メーターは1つではなく2つ
APIキーによるジョブは従量課金のプールに計上され、Studioのセッションによるジョブはプランの処理時間を消費します。どちらのプールももう一方を減らすことはなく、GET /v1/usageは両方を報告します。利用上限額はデフォルトでオンです。ストレージの利用枠は2つではなく1つです。キーでアップロードしたファイルとAPIのジョブの結果は、Studioのすべてのものと合わせて、ワークスペースのプランのストレージの利用枠(無料プランでは2 GB)に計上され、収まらないアップロードやジョブはstorage_fullで拒否されます。
Request-Id
成功でも失敗でも、すべてのレスポンスに付きます。サポートへの問い合わせでお伝えいただければ、ジョブから各ステージまで、実際に何が起きたかを端から端まで追跡できます。
エラー
codetitle
unauthenticated認証が必要
invalid_key無効なAPIキー
key_revoked取り消されたAPIキー
insufficient_scopeスコープの不足
email_verification_requiredメールアドレスの確認が必要
malformed_request不正な形式のリクエスト
validation_failedリクエストの検証に失敗
unsupported_media_type非対応のメディアタイプ
idempotency_key_reuse別のボディでの冪等キーの再利用
not_found見つかりません
job_not_cancelableキャンセルできないジョブ
job_not_awaiting_input入力待ちではないジョブ
file_not_ready準備ができていないファイル
file_expired有効期限切れのファイル
file_in_use使用中のファイル
file_disabled法的な理由で利用できないファイル
unsupported_format非対応のオーディオ形式
unsupported_sample_rate非対応のサンプルレート
unsupported_channel_count非対応のチャンネル数
file_too_long長すぎるオーディオ
file_too_short短すぎるオーディオ
file_too_large大きすぎるファイル
corrupt_mediaデコードできないオーディオ
rate_limitedレート制限の超過
too_many_active_jobs実行中のジョブ数の超過
quota_exceeded利用枠の超過
spend_cap_reached利用上限額に到達
test_mode_cap_reachedテストモードの上限に到達
storage_fullストレージの空き不足
purchase_required購入が必要
payment_method_required支払い方法が必要
format_not_in_planこのプランに含まれない形式
webhook_url_not_httpsWebhook URLはHTTPSが必須
webhook_url_not_publicWebhook URLは公開アドレスが必須
webhook_verification_failedWebhookの所有権確認に失敗
internal_error内部エラー
service_unavailableサービス利用不可
processing_failed処理の失敗
capacity_unavailable処理能力の不足
capability_paused一時停止中
workspace_suspendedワークスペースの停止
uploads_blockedアップロードの一時停止
reauthentication_required本人確認が必要

料金

費用

処理した1分ごとに、機能の倍率を掛けて、毎月請求します。サブスクリプションは不要で、APIを使うワークスペースは無料プランでもかまいません。テストモードは無料です。

毎月最初の100分
$0.35
1,000分まで
$0.25
10,000分まで
$0.15
それ以上、または利用量コミットメント
相談する