> This page is for プラットフォーム, version レガシー.
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 Experimental: https://next.developer.frame.io/platform/v4-experimental/llms.txt
> - レガシー: https://next.developer.frame.io/platform/v2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

# カスタムアクションの概要

<Info title="サンプルアプリ">
独自のカスタムアクションアプリを構築したい場合は、サンプルアプリを使用して開始できます。
</Info>
* [JavaScript](https://github.com/Frameio/custom-actions-example-app) 
* [Python](https://github.com/Frameio/custom-actions-app-python)

カスタムアクションは、統合をプログラミング可能な UI コンポーネントとして Frame.io に直接構築する方法です。これにより、[Webhook](doc:webhooks) と同じ、基盤となるイベントルーティングを活用して、ユーザーがアプリ内でトリガーできる多彩なワークフロークラスが実現します。現在、カスタムアクションはアセットに対して使用でき、次の画像に示すように、任意のアセットで使用可能なコンテキスト／右クリックドロップダウンメニューに表示されます。

![actions-1](/_fern-img/69bc65d3924a350e0c1d5a8062e41b15cbc3b01f1f3b8ec5050356b76aa1989a.webp)

アセットは、S3 内のファイルとその Frame.io でのコンテキストの包括的な表現です。これには、トランスコード、ユーザー／チーム／プロジェクトのコンテキスト、メタデータが含まれます。ユーザーがアセットでカスタムアクションをクリックすると、指定された URL に Frame.io がペイロードを送信します。受信アプリケーションは、受信確認として HTTP ステータスコードで応答したり、Frame.io で追加の UI をレンダリングできるカスタムコールバックで応答したりできます。

## カスタムアクションのセットアップ
<Info title="権限を確認してください">
チーム用のカスタムアクションを作成するには、チームマネージャーの権限が必要です。アクセス権がない場合は、管理者に権限の変更を依頼してください。
</Info>

カスタムアクションは、[developer.frame.io](https://developer.frame.io/) の「[カスタムアクション](https://developer.frame.io/actions)」エリアで設定できます。アクションには以下が必要です。

| フィールド名| 説明|
|----------|----------|
| 名前| カスタムアクション用に選択した名前。Frame.io で、使用可能なカスタムアクションのメニューにはこれが表示されます。|
| 説明| 参照用にアクションの動作を説明します（この説明は Frame.io web アプリには表示されません）。|
| イベント| 標準 Webhook イベントと独自イベントの区別に役立つ内部イベントキー。|
| URL| イベントの配信先。|
| チーム| カスタムアクションを使用するチーム。|

## クリック - Frame.io から返されるペイロードの内容
ユーザーがカスタムアクションをクリックすると、「URL」フィールドに指定した URL にペイロードが送信されます。

```json
POST /your/url
{
  "action_id": "2444cccc-7777-4a11-8ddd-05aa45bb956b",
  "interaction_id": "aafa3qq2-c1f6-4111-92b2-4aa64277c33f",
  "project": {
    "id": "a7d6a74b-d45d-4019-9069-24651e0b9f64"
  },
  "resource": {
    "id": "9q2e5555-3a22-44dd-888a-abbb72c3333b",
    "type": "asset"
  },
  "team": {
    "id": "54353cd2-c6ee-4aa1-954a-ce9e19602aa9"
  },
  "type": "my.action",
  "user": {
    "id": "fb57eee0-79f2-4bc7-9b70-99fbc175175c"
  }
}
```
このペイロードを使用して、以下を識別できます。

* クリックされたカスタムアクション
* クリックされたリソース
* アクションを実行したユーザー

| フィールド名| 説明|
|----------|----------|
| `action_id`| このアクションの一意の ID。特定のアクションで常に同じになります。|
| `interaction_id`| これは Frame.io によって生成される一意の識別子で、トランザクションの追跡に使用できます。この識別子は、コールバックフォームを含めて、アクションのどの単一シーケンスでも同じです。|
| `type`| アクションの設定時に「イベント」フィールドに入力したイベント名。|
| `resource.id`| アクションをトリガーしたリソース（通常はアセット）の ID。|
| `resource.type`| アクションをトリガーしたリソースの種類（通常は **asset）。|

<Info title="やり取りについて">
`interaction_id` は、時間とともに変化するやり取りの追跡に役立つ一意の識別子として提供されます。ユーザーへのレスポンスが不要の場合は、ステータスコード 200 を返すだけで完了です。オプションですが、簡単な成功メッセージやエラーアラートなど、アクションの結果に関する情報を含めることをお勧めします。カスタムアクションは、メッセージコールバックをサポートしています。
</Info>

<Info title="再試行とタイムアウト">
アプリケーションは 5 秒未満での応答を予期しており、成功レスポンスを待機している間に再試行を最大 5 回試みます。カスタムアクションを介してトリガーされた後、すぐにレスポンスを返し、すべてのアクションを非同期的に実行するのが理想的です。
</Info>


## メッセージコールバックの作成
Webhook イベントへの HTTP レスポンスでは、Frame.io UI で開始ユーザーに返されるメッセージを記述する JSON オブジェクトを返すことができます。メッセージを作成し、それがどのように表示されるかを確認したい場合は、[カスタムアクションビルダー](https://developer.frame.io/app/custom-actions/builder)をお試しください。これを使用すると、メッセージのコールバックやフォームを設定し、それが Frame.io web アプリでどのように表示されるかを確認できます。

オブジェクトの例を次に示します。

```json
{
  "title": "Success!",
  "description": "The thing worked! Nice."
}
```

これにより、次のようなアラートがユーザーに表示されます。
![actions-3](/_fern-img/a7fe1ec18a4467f8b450b31d1e3a70ab26beece9f5ed828166f4d35973e55c3c.webp)

メッセージは、アクションライフサイクルのループを閉じる簡単な方法であり、アクションを起こしたユーザーに対し、コンテキストの切り替えを求めることなく、可変コンテキストを提供します。

多くのユースケースはこれで十分ですが、初期ペイロードとそれに続く Frame.io API 呼び出しでは、受信アプリケーションに十分なコンテキストが提供されない場合があります。このようなシナリオ向けに、**フォームコールバック**もサポートされています。
## フォームコールバックの作成
プロセスを開始する前に、さらに情報が必要だとします。例えば、追加の詳細や設定が必要なコンテンツをシステムにアップロードしている場合について考えてみましょう。レスポンスでフォームについて「説明」し、それをユーザーに実際に表示できます。それに対し、ユーザーが入力します。そして、すぐに送り返されます。

アクションを開始したユーザーが入力して送信できるフォームを Frame.io UI でレンダリングするサンプルフォームを次に示します。

```json
{
  "title": "Need some more info!",
  "description": "Getting ready to submit this file!",
  "fields": [
    {
      "type": "text",
      "label": "Title",
      "name": "title",
      "value": "MyVideo.mp4"
    },
    {
      "type": "select",
      "label": "Captions",
      "name": "captions",
      "options": [
        {
          "name": "Off",
          "value": "off"
        },
        {
          "name": "On",
          "value": "on"
        }
      ]
    }
  ]
}
```

![actions-form](/_fern-img/4538043046be16b30b0230c21291dde8ae51554dc1da719bec8b9f7561c7c8a1.webp)

ユーザーがこのフォームを送信すると、最初の POST と同じ URL でイベントが返されます。

```json
POST /your/url​
{
  "type": "your-specified-event-name",
  "interaction_id": "the-same-id-as-before",
  "action_id": "unique-id-for-this-custom-action",
  "data":{
    "title": "MyVideo.mp4",
    "captions": "off"
  }
}
```
フォームに追加したすべてのカスタムフィールドは、Frame.io によって送信された JSON ペイロードの `data` セクションに示されます

`interaction_id` を使用して、最初のリクエストとこの新しいフォームデータをマッピングします。ここでも、必要に応じて、メッセージ（または別のフォーム）を返すこともできます。

アクション、フォーム、メッセージをつなぐことで、外部システムのビジネスロジックを使用して、Frame.io でアセットワークフロー全体を効果的にプログラムできます。

イマジネーションを発揮しましょう。可能性は無限大です。

### フォームの詳細情報
フォームでもメッセージと同様、フォーム上部に表示される `title` および `description` 属性をサポートしています。このほか、各フォームフィールドは次の基本属性を受け付けます。

* `type` -- Frame.io UI に、予期するデータ型、コンポーネント、レンダリングを指示します。
* `label` -- UI にフィールドの上のヘッダーとして表示されます。
* `name` -- 後続のペイロードでフィールドの識別に使用されるキーです。
* `value` -- フィールドに事前入力する値です。

### サポートされているフィールドの種類
**テキストフィールド**
は、追加パラメーターのないシンプルなテキストフィールドです。

```json
{  
  "type": "text",
  "label": "Title",
  "name": "title",
  "value": "MyVideo.mp4"
}
```

**テキストエリア**
は、追加パラメーターのないシンプルなテキストエリアです。

```json
{  
  "type": "textarea",
  "label": "Description",
  "name": "description",
  "value": "This video is really, really popular."
}
```

**選択リスト**
では、ユーザーが選択できる候補リストを定義します。`options` リストを含める必要があります。各メンバーには、人間が読んでわかる `name` とマシンが解析可能な `value` が含まれている必要があります。

```json
{
  "type": "select",
  "label": "Captions",
  "name": "captions",
  "value": "off",
  "options": [
       {
         "name": "Off",
         "value": "off"
       },
       {
         "name": "On",
         "value": "on"
      }
   ]
}
```

## カスタムアクションと Frame.io 権限モデル
Webhook とカスタムアクションには特別な権限モデルがあり、これらはチームやアカウントに存在する特定のユーザーにではなく、__チーム__に属します。  これは以下を意味します。

* 管理者またはチームマネージャーであれば、チームにカスタムアクションを作成できる。
* 管理者またはチームマネージャーであれば、チームに存在するカスタムアクションを変更または削除できる。  変更すると、変更の結果がすべてのユーザーにただちに示されます。

## セキュリティ
デフォルトで、すべてのカスタムアクションには、作成時に生成された署名鍵があります。これは設定できません。このキーを使用して、リクエストが Frame.io から発生していることを確認できます。

### 検証
`POST` リクエストには以下が含まれます。

| 名前| 説明|
|----------|----------|
| `X-Frameio-Request-Timestamp`| カスタムアクションがトリガーされた時刻。|
| `X-Frameio-Signature`| 計算された署名。|

**タイムスタンプ**は、リクエストが Frame.io のネットワークを出る際に署名された時刻です。これは、リプレイ攻撃を防ぐために使用できます。この時刻が現地時間から 5 分以内であることを確認することをお勧めします。

**署名**は、カスタムアクションの初回作成時に提供された署名鍵を使用する HMAC SHA-256 ハッシュです。

#### 署名の検証
1. HTTP ヘッダーから署名を抽出します。
2. バージョン、配信時刻、リクエスト本文を組み合わせて、署名するメッセージを作成します。
  * `v0:timestamp:body`
3. 署名シークレットを使用して、HMAC SHA256 署名を計算します。
  * 注意：指定された署名には、接頭辞 `v0=` が付きます。現在、Frame.io で署名リクエスト用に用意されているのはこれだけです。計算された署名には、この接頭辞を追加する必要があります。
4. 比較します。

**`Python`**

```python title="Python"
import hmac
import hashlib

def verify_signature(curr_time, req_time, signature, body, secret):
    """
    Verify webhook/custom action signature
    :Args:
        curr_time (float): Current epoch time
        req_time (float): Request epoch time
        signature (str): Signature provided by the frame.io API for the given request
        body (str): Custom Action body from the received POST
        secret (str): The secret for this Custom Action that you saved when you first created it
    """
    if int(curr_time) - int(req_time) < 500:
        message = 'v0:{}:{}'.format(req_time, body)
        calculated_signature = 'v0={}'.format(hmac.new(
            bytes(secret, 'latin-1'),
            msg=bytes(message, 'latin-1'),
            digestmod=hashlib.sha256).hexdigest())
        if calculated_signature == signature:
            return True
    return False
```