API と SDK
SENCA のノードは、HTTP の管理 API(/api/v1)で操作します。管理画面(/console/)も裏では同じ API を呼んでいます。
ここでは、どのエンドポイントにも共通する決まりごとと、イベント(SSE)、JavaScript の SDK(@senca/sdk)、Rust のクライアント(senca-client)の使い方を説明します。
エンドポイントごとのメソッド・パス・必要な権限は API リファレンス に、ルームのシグナリング(WebSocket のメッセージ形式)は シグナリング にあります。
API の定義ファイルは docs/api/openapi.yaml で、ノードの実際の動作に合わせて書いています。
リソースごとの使い方は、それぞれのページを見てください。配信と再生は 取り込みと再生、録画は 録画、
入力・出力は 入力と出力、パイプラインは パイプライン、ノードとグリッドは
クラスターとグリッド、ルームは リアルタイムのルーム、メディアパイプラインは メディアパイプライン です。
このページの要点:
- すべての
/api/v1にAuthorization: Bearerが必要です。テナントは認証情報から決まります。権限(スコープ)が足りなければ 403 になります。 - 未知のフィールドや未知のクエリを送ると 400 になります(黙って無視することはしません)。
- 作成の再送には
Idempotency-Key、更新の衝突防止にはETagとIf-Match(PATCH では必須)を使います。 - エラーは problem+json で返り、プログラムでは
codeで判別します。retryableが true なら再送してかまいません。 - 変更は
GET /api/v1/events(SSE)で受け取れます。JavaScript と Rust のクライアントを使えば、これらの決まりを自動で処理してくれます。
共通のルール
基本
| 項目 | 値 |
|---|---|
| ベースパス | /api/v1(--http で指定したアドレスで待ち受けます。--https を指定すれば TLS でも同じパスです) |
| 本文 | JSON(UTF-8)。時刻は UTC の RFC 3339(2026-10-25T00:00:00.000Z) |
| 本文の上限 | 1 MiB。超えると、problem+json ではなく text/plain の 413 を返して接続を閉じます |
| 応答ヘッダー | すべての応答に X-Request-Id と Cache-Control: no-store が付きます |
X-Request-Id | リクエストの X-Request-Id が 64 文字以内の英数字と - _ . であれば、その値を使います。そうでなければ req_... の形の値を作ります |
| パス | 空の区切り(//)・.・.. を含むパスは 404 になります |
| メソッド | 既知のリソースに定義されていないメソッド(HEAD を含む)は 405(METHOD_NOT_ALLOWED)になります。未知のパスは 404 です |
| CORS | /api/v1 は CORS ヘッダーを返しません。ブラウザーからは、同じオリジン(管理画面など)か、アプリのサーバーを経由して呼び出してください |
認証
すべての /api/v1(/capabilities を含む)に Authorization: Bearer <値> ヘッダーが必要です。/healthz・/readyz・署名付きの再生パス
(/s/...)には不要です。
値が senca_ で始まれば API トークン、それ以外は JWT として扱います。値の長さは 8192 バイトまでです。
認証に失敗すると 401(AUTH_FAILED、WWW-Authenticate: Bearer)が返ります。期限切れ・未知のトークンといった失敗の理由は応答に含まれず、ノードのログにだけ記録されます。
API トークン
最初にノードを起動したとき、トークンが 1 つも無ければ、ノードは tenant:admin と node:admin を持つトークン(名前 bootstrap-admin、
テナント default、期限なし)を作り、値を {data-dir}/control/bootstrap-admin-token(パーミッション 0600)に書き出します。クラスターでは
ID が最も小さいノードがこれを作ります。
以降のトークンは、このトークンを使って POST /api/v1/tokens(tenant:admin)で作ります。値(token)が返るのは作成時の応答だけで、サーバーにはハッシュしか残りません。
TOKEN=$(cat data/control/bootstrap-admin-token)
curl -s -X POST http://127.0.0.1:8080/api/v1/tokens \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"ci","scopes":["stream:read","stream:write"],"expires_at":"2027-01-01T00:00:00Z"}'
| フィールド | ルール |
|---|---|
name | 1〜64 文字(違反すると 400 INVALID_CONFIG) |
scopes | 1 つ以上。未知の権限を指定すると 400 になります。自分が持っていない権限は付与できません(403 FORBIDDEN) |
expires_at | 任意。RFC 3339 で指定します。過去の時刻は 400 になります。期限を過ぎたトークンで認証すると 401 になります |
一覧と取得ではトークンの値は返りません。不要になったトークンは DELETE /tokens/{id} で失効させます。ただし、テナントで最後の tenant:admin トークンは削除できません(409 CONFLICT)。
トークンの保管と入れ替えの手順は セキュリティ にあります。
JWT(外部の認証基盤のトークンをそのまま使う場合)
senca-node --jwks /etc/senca/jwks.json --jwt-issuer https://idp.example/ --jwt-audience senca [--jwt-tenant-claim tenant]
--jwks を使うときは、--jwt-issuer と --jwt-audience も指定してください。検証用の鍵はノード上の JWKS ファイルからだけ読み込み、トークンに書かれた URL へ鍵を取りに行くことはありません。
使えるアルゴリズムは EdDSA(Ed25519)・ES256・RS256(RSA は 2048 ビット以上)です。アルゴリズムは鍵で決まり、ヘッダーの alg が鍵と合わなければ拒否します。
JWKS の鍵には kid が必須で、use を書くなら sig にします。alg=none・HS 系・crit 付きのトークンは受け付けません。
トークンでは、kid が JWKS にあること、exp(必須)、nbf、iss の一致、aud の一致(文字列または配列)を検査します。時計のずれは 60 秒まで許容します。
テナントはクレーム tenant(名前は --jwt-tenant-claim で変えられます)で決まり、無いか空なら拒否します。操作した人(主体)は sub で表します。
権限は scope(空白区切り)か scp(文字列または配列)から読み、未知の権限は無視します。
権限とテナント
| 権限 | 主な操作 |
|---|---|
stream:read | ストリーム・予約・入力・出力・パイプライン・ルーム・メディアパイプラインの読み取り、POST /streams/{id}/plan、POST /schedules/check、POST /pipelines/{id}/validate、SSE |
stream:write | 上のリソースの作成・更新・削除、鍵の再発行、録画の開始・停止・PATCH /recordings/{id}、ルームの参加トークンの発行・参加者の操作・メッセージ送信 |
subscribe | POST /streams/{id}/playback-url(ライブの署名付き URL) |
recording:read | 録画の一覧・取得・VOD の URL・サムネイル・MP4 のエクスポート |
recording:delete | DELETE /recordings/{id} |
tenant:admin | トークン・シークレット(/secrets)の管理。node:admin 以外のすべての権限を含みます |
node:admin | テナントをまたぐ運用操作(/nodes・/devices・/grid、ドレイン、デバイスの停止と再開)と、SSE のテナントに属さないイベントの受信 |
publish | WHIP での配信(POST /whip/{stream_id} で、ストリームキーの代わりに使えます。同じテナントのストリームだけが対象です) |
操作ごとに必要な権限は、OpenAPI の拡張フィールド x-senca-scope に書いてあります。null は認証だけで足りるという意味で、GET /capabilities がこれに当たります。
権限が足りなければ 403(FORBIDDEN)になり、title に必要な権限の名前が入ります。
テナントは認証情報から決まります。 API トークンなら作成者のテナント、JWT ならテナントのクレームです。本文でテナントを指定するフィールドはありません。 他のテナントのリソースは 404 に見えます。API トークンは自分のテナントにしか作れないので、新しいテナントを使い始めるには、JWT のテナントのクレームに新しい名前を入れます。
本文の検査
本文のあるリクエストには Content-Type: application/json を付けてください(; charset=... が付いていてもかまいません)。無ければ 415(UNSUPPORTED_MEDIA_TYPE)です。
フィールド名の綴りを間違えると、黙って無視されるのではなく 400 になります(INVALID_REQUEST。details.reason に解析エラーの内容が入ります)。型の誤りも同じ扱いです。
値の範囲・組み合わせ・参照先の誤りは、ほとんどが 400 INVALID_CONFIG になります。
Idempotency-Key
ストリームを作る POST を送ったところでタイムアウトした。届いたのかどうかわからないまま再送すると、ストリームが 2 つできてしまうかもしれません。
これを防ぐのが Idempotency-Key ヘッダーです。POST に付けておくと、24 時間は同じリクエストに同じ応答を返し、処理は 1 回しか行いません。
キーは 1〜255 文字の表示可能な ASCII(空白なし)で、違反すると 400 です。キーはテナントごとに区別されます。
同じリクエストかどうかは、メソッド・パス・クエリ・本文の SHA-256 で判定し、ヘッダーは見ません。
同じキーで中身の違うリクエストを送ったときや、別の主体が同じキーを使ったときは、409(IDEMPOTENCY_CONFLICT)になります。
再送への応答は、保存しておいたステータスコード・本文・Location・ETag に Idempotency-Replayed: true を付けたものです。
4xx の応答も保存するので、同じキーで再送すれば同じエラーが返ります。一方 5xx は保存しないので、再送すると処理をやり直します。256 KiB を超える応答も保存しません。
保存する本文からは秘密情報を消し、stream_key・token・secret を null にして、代わりに stream_key_redacted: true などを入れます。
つまり秘密情報は最初の応答でしか見られず、再送で得た応答から取り出すことはできません。
同じキーのリクエストはノードの中で順番に処理されるので、同時に再送しても処理は 1 回だけです。
ETag と If-Match(楽観的排他制御)
2 人がほぼ同時に同じストリームを更新すると、後から書いた方が先の変更を知らずに上書きしてしまいます。 SENCA はこれを楽観的排他制御で防ぎます。「読んだときから誰も変更していなければ更新する」という方式です。
リソースの応答(作成・取得・更新)に付く ETag は "{generation}"(例 "3")で、W/"3" の形も受け付けます。
PATCH では、この値を If-Match に入れて送るのが必須です。 無ければ 428(PRECONDITION_REQUIRED)、形式が違うか古ければ 412(PRECONDITION_FAILED)になります。
DELETE の If-Match は任意です。検査するのは /streams・/recordings・/schedules だけで、付けた値が古ければ 412 になります。
入力・出力・シークレット・パイプライン・ルーム・メディアパイプライン・トークンの DELETE では、If-Match を見ません。
# 取得してから If-Match を付けて更新する
ETAG=$(curl -s -D - -o /dev/null -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/streams/$ID | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
curl -s -X PATCH http://127.0.0.1:8080/api/v1/streams/$ID \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H "If-Match: $ETAG" \
-d '{"metadata":{"owner":"studio-a"}}'
412 が返ったら、リソースを取得し直してから変更をやり直してください。
一覧とページング
一覧の応答は {"items": [...], "next_cursor": "..." | null} で、ID の昇順に並びます。
1 ページの件数は limit(1〜1000、既定 100)で決め、次のページは前の応答の next_cursor を cursor に入れて取ります。next_cursor が null なら最後のページです。
範囲外の limit は 400 です。
一覧でも、未知のクエリパラメーターは 400 になります。使える絞り込みは、GET /recordings の stream・state、GET /schedules の
stream・state、GET /media-pipelines の room だけです。
ページングの無い一覧もあり、GET /secrets は全件を返して next_cursor: null、GET /nodes は items だけを返します。
curl -s -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/api/v1/streams?limit=50'
curl -s -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/api/v1/streams?limit=50&cursor=<next_cursor>'
エラー(problem+json)
エラーは Content-Type: application/problem+json(RFC 9457 の形式)で返します。
{
"type": "urn:senca:error:precondition-failed",
"title": "資源が更新されています(If-Match が古い)",
"status": 412,
"code": "PRECONDITION_FAILED",
"request_id": "req_...",
"retryable": false
}
title は人が読むための日本語の説明なので、プログラムでは code で判別してください。details はコードごとの補足情報で、無ければ省略されます。
retryable が true なら、同じリクエストを再送してかまいません。503・429・504 で retryable が true のときは Retry-After が付きます
(429 なら次に受け付けられるまでの秒数、それ以外は 1)。リクエストの数の上限による 429 は、どの /api/v1 の操作でも返ることがあります(セキュリティ)。
/api/v1 以外のバージョン(/api/v2/... など)を指定すると、422 UNSUPPORTED_PROTOCOL_VERSION(details.supported: ["v1"])になります。
不具合を問い合わせるときは、request_id(= X-Request-Id)を添えてください。
code | ステータス | retryable | 主な場面 |
|---|---|---|---|
INVALID_CONFIG | 400 | false | 値の検査エラー(範囲・組み合わせ・参照先) |
INVALID_REQUEST | 400 | false | 本文の解析エラー・未知のフィールド・未知のクエリ・limit の範囲外・Idempotency-Key の形式 |
UNSUPPORTED_PROTOCOL_VERSION | 422 | false | /api/v1 以外のバージョンのパス |
UNSUPPORTED_CODEC | 422 | false | 静止画のデコード(GET /streams/{id}/snapshot)ができない、ルームの処理が 422 を返した、E2EE のルームやそこからエクスポートしたストリームをサーバーで処理しようとした(details.reason: "e2ee") |
UNSUPPORTED_MEDIA_TYPE | 415 | false | Content-Type が application/json でない |
AUTH_FAILED | 401 | false | 認証の失敗 |
FORBIDDEN | 403 | false | 権限が足りない、持っていない権限をトークンに付与しようとした |
NOT_FOUND | 404 | false | 存在しない、他のテナントのリソースである、未知のパス |
METHOD_NOT_ALLOWED | 405 | false | リソースに無いメソッドを使った |
CONFLICT | 409 | false | 現在の状態と矛盾する(使用中のシークレットの削除、最後の管理者トークンの削除など) |
IDEMPOTENCY_CONFLICT | 409 | false | 同じ Idempotency-Key で別のリクエストを送った |
PRECONDITION_FAILED | 412 | false | If-Match が古い、または形式が違う |
PRECONDITION_REQUIRED | 428 | false | PATCH に If-Match が無い |
QUOTA_EXCEEDED | 403 | false | 上限に達した(同じストリームで同時に録画できるのは 8 本まで、など) |
RATE_LIMITED | 429 | true | リクエストの数の上限を超えた(details.limit は ip・token・auth_failures・connect) |
CAPACITY_UNAVAILABLE | 503 | ほとんどは true | クラスターが変更を受け付けられない(リーダーの交代中や混雑時)、ルームが別のノードにある、--webrtc-udp が無い、コーデックワーカーが無い(このときは false) |
INPUT_TIMEOUT | 504 | true | 静止画を求めたが、配信のキーフレームが 5 秒以内に届かない |
OWNERSHIP_LOST | 409 | false | クラスターで、別のノードが配信を受信しているストリームの録画の開始や静止画を求めた(details.owner_api のノードにリクエストを送り直してください) |
PAYLOAD_TOO_LARGE | 413 | false | 録画が大きすぎて MP4 にエクスポートできない、クラスターに記録する変更が大きすぎる |
INTERNAL_ERROR | 500 | true | コントロールプレーンへの書き込みに失敗したなど |
表の retryable は既定値で(true なのは RATE_LIMITED・CAPACITY_UNAVAILABLE・INPUT_TIMEOUT・INTERNAL_ERROR)、個々のエラーで変わることがあります。type は urn:senca:error:{code を小文字にし、_ をハイフンにしたもの} です。
OpenAPI の定義
docs/api/openapi.yaml(OpenAPI 3.1)に、すべての操作・スキーマ・ステータスコードを書いています。説明は日本語です。
このサイトの API リファレンス は、この定義からエンドポイントを分類ごとにまとめたものです。
ノード自身は定義ファイルを配信しないので、リポジトリのファイルを OpenAPI 3.1 に対応したビューアーで開いてください。たとえば Docker の Swagger UI なら次のとおりです。
docker run --rm -p 127.0.0.1:8081:8080 -e SWAGGER_JSON=/spec/openapi.yaml \
-v "$PWD/docs/api:/spec:ro" swaggerapi/swagger-ui
# http://127.0.0.1:8081/ を開く
ただし /api/v1 は CORS ヘッダーを返さないため、ビューアーの「Try it out」から別オリジンのノードへリクエストを送ることはできません。実際に試すときは curl か SDK を使ってください。
定義ファイルには、独自の拡張フィールドが 3 つあります。
| フィールド | 意味 |
|---|---|
x-senca-scope | その操作に必要な権限(null は認証だけが必要) |
x-senca-undefined-method-status: 405 | 定義されていないメソッドへの応答 |
x-senca-event-data | SSE の data のスキーマ(SseEventData) |
今のところ対応していない機能(非同期処理の状態を表す operation リソースなど)は、定義ファイルの冒頭にまとめてあります。 JavaScript の SDK と Rust のクライアントの型は、この定義ファイルから生成しています。
イベント(SSE)
GET /api/v1/events は、変更を Server-Sent Events(text/event-stream。サーバーから送り続ける HTTP の通知)で配信します。必要な権限は stream:read です。
curl -sN -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/events
event: stream.created
id: 42
data: {"type":"stream.created","resource_id":"...","occurred_at":"2026-11-03T01:02:03.000Z","data":{"name":"studio-a"}}
流れてくるイベントの多くは、クラスターで合意された(単一ノードでは保存された)設定の変更から作られる「制御のイベント」です。
SSE の id は変更の記録(制御ログ)の中での位置を表す整数で、data は {type, resource_id, occurred_at, data}、occurred_at は送信した時刻です。
届くのは自分のテナントのイベントだけです。テナントに属さないイベント(ノード・グリッド・stream.owner)は、node:admin を持つ主体にだけ届きます。
サーバーにはイベントの種類で絞り込む機能が無いので、必要なものだけ扱いたいときはクライアント側で捨ててください。SDK はこれを自動で行います(下の JavaScript・Rust の説明)。
接続を保つため、15 秒ごとにコメント行(keep-alive)が送られます。401・403 は他の API と同じく problem+json で返り、403 の title は「権限 stream:read が必要です」です。
Last-Event-ID と resync
ノードは直近のイベントを残しています(既定 4096 件、--events-retain)。単一ノードでは、再起動した後も、制御ログのスナップショットより後の分を読み直して残します。
接続が切れたら、最後に受け取った id を Last-Event-ID に付けて再接続してください。その位置より後のイベントのうちノードが残している分が先に届き、続けて新しいイベントが届きます。
Last-Event-ID を付けなければ、接続した後のイベントだけが届きます。
id はそれまでに適用した変更の数で、クラスターでは同じイベントがどのノードでも同じ id になります。そのため、別のノードへ再接続しても同じ Last-Event-ID で続きを受け取れます。
接続したノードの適用が Last-Event-ID に追いついていなければ、ノードは 3 秒まで待ちます。
指定した位置がノードの残している範囲より古いときは、最初に event: resync(id は現在の位置、data: {})が届き、その後は新しいイベントが続きます。
受信側の読み取りが遅れて通知があふれたときも、残している分から続きを送ります。残している分にも無ければ、event: resync を送って接続を閉じます。
resync を受け取ったら、一覧の API で状態を取り直し、受け取った id を Last-Event-ID にして再接続してください。
イベントの一覧
制御のイベント(id あり)は次のとおりです。
| 種類 | テナント | data |
|---|---|---|
stream.created | あり | {name} |
stream.updated | あり | {generation} |
stream.deleted | あり | {} |
stream.owner | なし(node:admin) | {node}(ストリームを担当するノードが替わった。クラスターとグリッド) |
recording.updated | あり | {stream, state, generation} |
recording.deleted | あり | {} |
schedule.created・schedule.updated・schedule.deleted | あり | {} |
schedule.run | あり | {state, occurrence, run_state, recording} |
node.updated | なし(node:admin) | {draining} |
input.updated・output.updated | あり | {stream, generation, enabled} |
input.deleted・output.deleted | あり | {} |
secret.created | あり | {name} |
secret.deleted | あり | {} |
pipeline.updated | あり | {name, desired_state} |
pipeline.deleted | あり | {} |
room.updated | あり | {auto}(ルームの作成・自動作成・更新) |
room.deleted | あり | {} |
media_pipeline.updated | あり | {room, desired_state}(作成・更新) |
media_pipeline.deleted | あり | {} |
node.down・node.up | なし(node:admin) | {}(resource_id はノードの ID) |
device.down・device.up | なし(node:admin) | {device}(定期的な検出でわかった状態の変化) |
device.disabled・device.enabled | なし(node:admin) | {device}(手動での停止・再開) |
grid.decisions | なし(node:admin) | {decisions}(配置の決定。resource_id は空) |
ルームのイベント(リアルタイムのルーム)は次のとおりです。
| 種類 | Webhook での名前 | data.data |
|---|---|---|
room.started | room.created | {settings, auto_created} |
room.stopped | room.destroyed | {reason} |
room.connection.created | connection.created | {participant} |
room.connection.updated | connection.updated | {connection_id, metadata} |
room.connection.destroyed | connection.destroyed | {connection_id, client_id?, reason, duration_ms} |
room.track.published | track.published | {track} |
room.track.unpublished | track.unpublished | {track_id, connection_id} |
room.spotlight.changed | spotlight.changed | {focused} |
ルームのイベントは制御のイベントと違い、合意ログに記録されません。SSE の id を持たないので、Last-Event-ID で再開できず、取りこぼしても再送されません。
また、届くのはそのイベントが起きたノードに接続している購読者だけです(カスケードの扱いは リアルタイムのルーム)。
クラスターのすべてのルームのイベントを受け取りたいなら、各ノードの /api/v1/events を購読してください。
data は {type, resource_id(ルームの ID), event_id(イベントの ID。重複の除去に使います), node(イベントを出したノードの ID), occurred_at, data} です。
内側の data には上の表の内容だけが入り、Webhook での名前の type は入りません。
ルームのイベントもテナントで絞り込まれ、node:admin でも他のテナントのルームのイベントは届きません。ルームの設定の変更は、制御のイベント room.updated で通知されます。
event: room.connection.created
data: {"type":"room.connection.created","resource_id":"daily","event_id":"...","node":"...","occurred_at":"...","data":{"participant":{"connection_id":"...","client_id":"alice",...}}}
JavaScript の SDK(@senca/sdk)
apps/sdk-js にあります。ブラウザーで使うリアルタイムのルームの機能(Senca.connect())と、ブラウザーと Node.js 20 以上で使える管理 API のクライアント(SencaClient)が入っています。
実行時に必要な依存パッケージはありません。ソースは JSDoc で型を付けた ES modules で、ビルドせずにそのまま動きます。型定義は types/、API の一覧は
apps/sdk-js/API.md、詳しい説明は apps/sdk-js/README.md にあります。
npm には公開していないので、リポジトリからインストールしてください(例: npm install /path/to/senca/apps/sdk-js)。
読み込むときは、すべてを使うなら @senca/sdk、管理 API だけなら @senca/sdk/rest、シグナリングの型だけなら @senca/sdk/protocol を指定します。
管理 API(SencaClient)
import { SencaClient, ApiError } from '@senca/sdk';
const senca = new SencaClient({ baseUrl: 'https://node.example', token: process.env.SENCA_TOKEN });
const s = await senca.streams.create({ name: 'studio-a' }); // Idempotency-Key を自動で付ける
for await (const x of senca.streams.list({ limit: 100 })) console.log(x.name); // cursor を自動でたどる
const cur = await senca.streams.get(s.id);
await senca.streams.update(s.id, { metadata: { a: 'b' } }, { ifMatch: cur }); // ETag・generation・リソースのどれでも指定できる
await senca.streams.modify(s.id, (c) => ({ metadata: { ...c.metadata, n: '1' } })); // 412 なら取得し直してやり直す(既定 3 回)
try {
await senca.streams.update(s.id, { name: 'x' }, { ifMatch: 1 });
} catch (e) {
if (e instanceof ApiError && e.code === 'PRECONDITION_FAILED') { /* 他のクライアントが先に更新した */ }
}
SencaClient に渡せるオプション(ClientOptions)は次のとおりです。
| 項目 | 既定 | 意味 |
|---|---|---|
baseUrl | (必須) | https://node.example(/api/v1 を補います)または https://node.example/api/v1 |
token | なし | API トークンまたは JWT。関数を渡すと、リクエストのたびに呼び出します |
retry | true | 再試行の設定(maxAttempts 3(初回を含む)・initialDelayMs 200・maxDelayMs 5000・factor 2・jitter 0.2)。false で再試行しません |
timeoutMs | 30000 | 1 回のリクエストのタイムアウト(SSE には適用しません) |
headers | {} | すべてのリクエストに付けるヘッダー |
idempotency | true | POST に Idempotency-Key(UUID v4)を自動で付けます |
fetch | globalThis.fetch | fetch の実装を差し替えるときに使います |
- 使えるリソースとメソッド:
streams(rotateKey・playbackUrl・startRecording・plan・snapshot・snapshots.list/get/delete)・recordings(stop・playbackUrl・thumbnail・exportMp4。作成はstreams.startRecording)・schedules(check・runs)・tokens・inputs・outputs(create(streamId, body)・list(streamId))・secrets・pipelines(validate・control)・nodes(drain・undrain・devices.disable/enable)・rooms(createToken・participants(id).list/get/updateMetadata/disconnect・sendMessage・stats)・mediaPipelines(list({room})・start・stop)。クライアント自体のメソッドとしてcapabilities()・devices()・grid()・events()があります。型の無いリクエストはsenca.request(method, path, opts)で送れます。 - 共通のメソッド:
create・list(Paginator。for awaitで全件を取得)・listPage(1 ページ)・get・update(ifMatchが必須。無いとTypeError)・modify・delete(ifMatchは任意)。 通信の失敗、408・429・502・503・504、retryable: trueのエラーのときは、Retry-Afterに従い、待ち時間を倍々に延ばしながら再試行します。 ただし応答が無かった失敗を再試行するのは、GET・DELETE・PUT と、Idempotency-Key 付きの POST だけです。PATCH は 429・503 のときだけ再試行します。
problem+json のエラーは ApiError(status・code・title・type・requestId・retryable・retryAfterMs・details・problem)として投げられます。
通信の失敗では status: 0、code: 'NETWORK_ERROR' | 'TIMEOUT' です。
ほかに、SencaClient.etagOf(resource)、SencaClient.newIdempotencyKey()(プロセスをまたいで同じキーで再送するときに使います)、
SencaClient.operations(operationId からメソッド・パス・権限を調べられる表)も用意しています。
SSE
for await (const ev of senca.events({ types: ['stream.*', 'room.connection.*'], signal })) {
if (ev.type === 'resync') await reloadAll();
else console.log(ev.type, ev.data.resource_id);
}
ブラウザーの EventSource は Authorization ヘッダーを送れないので、SDK は fetch でイベントを読み取ります。
types には、完全一致か、末尾の * による前方一致で種類を書きます。絞り込みはクライアント側で行い、resync は絞り込みに関係なく必ず届きます。
接続が切れると、SDK は最後の id を Last-Event-ID にして再接続します。間隔は 1 秒から倍々に延ばして最大 30 秒で、reconnect: false にすれば再接続しません。
401・403・404 のときは再接続せず、ApiError で終わります。
リアルタイムのルーム(Senca.connect()、ブラウザー)
import { Senca } from '@senca/sdk';
const camera = await navigator.mediaDevices.getUserMedia({ video: { width: 1280, height: 720 }, audio: true });
const session = await Senca.connect({
url: signalingUrl, // 参加トークンの応答に含まれる signaling_url
room: 'daily',
token: () => fetch('/my-backend/room-token').then((r) => r.text()), // 再接続のたびに取得し直す
publish: { stream: camera, simulcast: true },
dataChannels: ['#chat'],
});
for await (const track of session.remoteTracks()) {
document.body.append(track.attach(document.createElement('video')));
}
参加トークンは、アプリのサーバーが POST /api/v1/rooms/{id}/tokens(senca.rooms.createToken(room, {role, client_id, metadata}))で発行し、
token と signaling_url だけをブラウザーへ渡します。管理 API のトークンは、決してブラウザーに渡さないでください。
Session には、participants・tracks(随時更新される Map)、events()・remoteTracks()・messages()(非同期イテレーター)、waitFor()、
publish()・send()・stats()・close() があります。
シグナリング v1 の SDP のやり取り、受信スロットの管理、再ネゴシエーション、ping、ICE の再起動、リダイレクト、再接続(0.5 秒から倍々に延ばして最大 10 秒、10 回まで)は SDK が自動で行います。
エラーは SencaError(code・fatal・closeCode・request)として届きます。
ルームの機能と制約は リアルタイムのルーム、プロトコルは シグナリング を見てください。
Rust のクライアント(senca-client)
crates/senca-client にある、tokio で動く非同期のクライアントです。HTTP/1.1 と TLS には SENCA 自身の実装を使うので、外部の HTTP クライアントには依存しません。
crates.io には公開していないため、Cargo.toml の依存関係に path か git で追加してください。詳しくは crates/senca-client/README.md にあります。
use senca_client::{EventsOptions, ListOptions, ProblemCode, SencaClient, ServerEvent};
use senca_client::models::{StreamCreate, StreamPatch};
let client = SencaClient::new("https://senca.example", std::env::var("SENCA_TOKEN")?)?;
let s = client.streams().create(&StreamCreate { name: "studio-a".into(), ..Default::default() }).await?;
let key = s.ingest.stream_key.clone(); // ストリームキーはこの応答でしか受け取れない
let patch = StreamPatch { name: Some("studio-b".into()), ..Default::default() };
let s = match client.streams().update(&s.id, &patch, &s).await { // 前の応答(ETag)をそのまま If-Match に使う
Ok(v) => v,
Err(e) if e.code() == Some(ProblemCode::PreconditionFailed) => { /* 取得し直して再試行 */ todo!() }
Err(e) => return Err(e.into()),
};
let mut it = client.streams().list_with(ListOptions::default().limit(100));
while let Some(x) = it.next().await {
println!("{}", x?.name);
}
let mut events = client.events(EventsOptions::default().types(["stream.*", "recording.*"]));
while let Some(ev) = events.next().await {
match ev? {
ServerEvent::Event(e) => println!("{} {}", e.event, e.data.resource_id),
ServerEvent::Resync { .. } => { /* 一覧を取得し直す */ }
}
}
client.streams().delete(&s.id, s.if_match()).await?;
Rust のクライアントで使えるリソースとメソッドは次のとおりです。
| リソース | メソッド |
|---|---|
streams() | create・create_with・list・list_with・list_page・get・update・delete・rotate_key・playback_url・start_recording・plan・snapshot・snapshots・snapshot_by_id・delete_snapshot |
recordings() | list・list_with(ListOptions::stream)・list_page・get・update・delete・stop・playback_url・thumbnail・export_mp4 |
schedules() | create・create_with・list・list_with・list_page・check・get・update・delete・runs |
tokens() | create・create_with・list・list_with・list_page・get・delete |
inputs()・outputs() | create(stream_id, ..)・create_with・list(stream_id)・list_with・get・update・delete |
secrets() | create・create_with・list・delete |
pipelines() | create・create_with・list・list_with・list_page・get・update・delete・validate・control |
nodes() | list・get・drain・undrain・disable_device・enable_device |
rooms() | create・create_with・list・list_with・list_page・get・update・delete・issue_token・send_message・stats・participants・participants_with・participant・update_participant・disconnect |
media_pipelines() | create・create_with・list・list_with(ListOptions::room)・list_page・get・update・set_state・delete |
| クライアント | capabilities・grid・devices・events・healthz・readyz・fetch_signed(署名付きの再生パス。307 をたどります)・request_json(型の無いリクエスト) |
- バージョン情報付きの応答:
get・create・updateはVersioned<T>(value・etag・replayed・request_id。Derefで中身を参照できます)を返します。updateのif_matchには、&Versioned<T>・IfMatch::Etag・IfMatch::Generation(n)・u64のどれでも渡せます。 - Idempotency-Key: POST には毎回新しいキーを付け、再試行でも同じキーを使います。
create_with(.., RequestOptions::idempotency_key("..."))でキーを指定すれば、プロセスをまたいだ再送でも同じ応答が返ります(Versioned::replayedが true になります)。 - エラー: API のエラーは
Error::Api(ApiError)になります。ApiErrorにはcode(ProblemCode。未知の値はUnknownになり、元の値はraw_codeに入ります)・title・status・retryable・request_id・details・retry_afterがあります。ほかにConnect・Transport・Timeout・Decode・InvalidUrl・InvalidArgumentのエラーがあります。 - 再試行(
RetryPolicy): 既定では、最初のリクエストの後に最大 3 回再試行します。待ち時間は 200 ms から倍々に延ばし(上限 10 秒、±20 % のゆらぎ)、Retry-Afterがあればそれ以上待ちます。ClientBuilder::retry(RetryPolicy::NONE)で再試行しなくなります。PATCH はretryable・429・503 のときだけ再試行します。 - SSE:
EventsOptionsで、types・last_event_id・reconnect(既定 true)・initial_backoff(既定 1 秒)・max_backoff(既定 30 秒)を指定します。 接続が切れたらLast-Event-IDを付けて再接続します。401・403 では再接続しません。 - クライアントの構築:
SencaClient::builder(url).token(..).timeout(..)(既定 30 秒).user_agent(..).root_certificates_pem(pem).build()。 TLS では、既定で OS の認証局を使います。 - モデル:
senca_client::modelsの型は、openapi.yaml から生成しています。応答に含まれる未知のフィールドは 読み飛ばし、未知の enum の値はUnknownになります。*Patchの null を取りうる項目はOption<Option<T>>です(None= 変更しない、Some(None)= null を送って値を消す)。
関連するページ
- API リファレンス(すべてのエンドポイントの一覧)・シグナリング
- クラスターでの書き込み(どのノードに送ってもかまいません。書き込みはリーダーへ転送されます): クラスターとグリッド
- ルームと、SSE で届くルームのイベント: リアルタイムのルーム
- メディアパイプライン: メディアパイプライン
- トークンと秘密情報の扱い: セキュリティ、運用(ログ・監視): 運用
この文書の原文は、リポジトリの docs/guide/ja/15-api-and-sdks.md にあります。内容は現在の版(v0.0.1)に合わせています。