> This page is for 平台, version V4 实验版.
> For other versions, use one of these documentation indexes:
> - V4 (default): https://next.developer.frame.io/platform/v4/llms.txt
> - V4 实验版: 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.

# 自定义操作

Frame.io 操作提供对常见媒体操作的快速访问权限，如下载、重命名和复制项目，还允许与第三方工具和服务集成，使其直接显示在 [Frame.io](https://next.frame.io/) 的用户界面中。

## 关于操作

随着自定义操作的推出，开发者可以在 Frame.io V4 中配置和管理自己的操作。自定义操作利用与 [Webhook](https://developer.adobe.com/frameio/guides/Webhooks/) 相同的底层事件系统，为开发者提供了一种替代机制，可以将其资产连接到 Frame.io 帐户中对用户最重要的工具。

操作可以由 Frame.io 工作区中启用该操作的任何成员用户执行。 在执行一项操作时，Frame.io 会将负载发送到您提供的 URL。 接收应用程序使用 HTTP 状态代码响应来确认接收，或使用自定义回调在 Frame.io UI 中渲染其他表单字段。 接收应用程序可以是您自己托管的项目集、服务，甚至是 Workfront Fusion 或 Zapier 等低代码/无代码 IPaaS 工具。

使用自定义操作，可以直接将集成作为可编程的 UI 组件构建到 [Frame.io](https://next.frame.io/) 中。这使工作流程能够利用与 Webhook 相同的底层事件路由，由应用程序内的用户触发。 您可以创建用户触发的单步或多步表单，作为另一个表单或基本响应返回到 Frame.io。 当用户在资产上点击自定义操作时，Frame.io 会将负载发送到您提供的 URL。 接收应用程序使用 HTTP 状态代码响应来确认收到，或使用自定义回调进行响应，该回调可以在 Frame.io 中渲染其他 UI。

## V4 中的操作改进

利用从旧版用户吸取的经验，我们在 Frame.io V4 中对操作功能集进行了一些增强：

#### 新字段类型

之前仅限于文本和单选字段，现在我们也支持多选、文本区域（用于更大的文本框）和布尔值字段（用于单选按钮）。

#### 可点击链接

文本字段不方便用户复制/粘贴 URL。 使用我们全新链接字段，即可轻松实现一键复制。

#### 动态模态

根据返回的数据量，您可以信任操作的模态动态调整大小，使其以最佳方式适应表单中的信息，包括在需要时提供可滚动模态。

#### 多资产操作

配置您的操作以在一个请求中定位多达 100 个资产。

\


新增

#### 混合资产类型

操作不仅限于一种资产类型，还可以跨文件、文件夹和版本堆栈的组合触发。

#### [应用内反馈表单](https://next.frame.io/settings/actions)

我们希望了解开发者和最终用户如何使用操作，因此我们在 Web 的设置页面中添加了一个反馈表单。

## 迁移操作

在将包含之前在旧版 Frame.io 中创建的自定义操作的 Frame.io V4 帐户迁移到该帐户时，需要注意以下几点。

### 操作状态

帐户迁移到 Frame.io V4 后，在早期版本中创建的所有自定义操作的状态都将为“null”且会自动禁用。 这样，用户就有机会先将操作更新为使用 V4 API 然后再启用，因为任何未更新的操作都将失败。 要识别此状态下的操作，请访问操作设置页面并参考“状态”列，或者如果使用了 API，则检查 is\_active 字段。

### 可操作资源：文件、文件夹和版本堆栈

鉴于在 Frame.io V4 API 中资产类型被分离为独立资源，在解释操作负载中收到的资源 ID 时可能需要考虑某些行为。 单个文件的行为很直接，因为 ID 将反映执行操作的特定文件。 同样，对于文件夹，您将收到执行操作的文件夹 ID；但是，根据您的具体用例，您在定义操作的行为时有多种选择。 如果您想与文件夹资源本身进行交互，请使用文件夹 ID 对 Frame.io API 进行后续调用。 或者，您可能希望获取该文件夹的次项，以便对其中的资产进行进一步处理。 在对版本堆栈执行操作时，您的负载将包含“头部资产”的 ID，它是堆栈中最顶层的文件，也是 Frame.io UI 中显示的内容。

您可以在我们的[迁移指南](/docs/resources/migration)中了解更多关于 Frame.io 旧版 API 和 V4 之间差异的信息。

> **Info**
>
> 使用 API 配置[自定义操作](/api-reference/custom-actions/actions-show)。

自定义操作需要：

| 字段名称 | 描述                                         |
| ---- | ------------------------------------------ |
| 名称   | 您为自定义操作选择的名称。 它将显示在 Frame.io 中可用自定义操作的菜单中。 |
| 描述   | 解释操作的作用，以供参考（描述不会显示在 Frame.io Web 应用程序中）。  |
| 事件   | 内部事件键，用于帮助您区分标准 Webhook 事件和您自己的事件。         |
| URL  | 事件的传送目标位置。                                 |
| 工作区  | 将使用自定义操作的工作区。                              |

## 配置您的自定义操作

当用户触发自定义操作时，Frame.io 会将负载发送到您提供的 URL。 接收应用程序可以使用 HTTP 状态代码响应来确认收到，或使用在 [Frame.io](https://next.frame.io/) 中渲染更多 UI 的自定义回调响应。

> **Warning**
>
> 需要帐户管理员权限才能创建工作区的自定义操作。 如果您没有访问权限，请让您的管理员修改您的权限。

### 多资产配置

多资产支持由配置驱动，且必须从 [Web](https://next.frame.io/settings/actions) 端操作的配置模态中明确启用。 这可以在创建新操作期间完成，也可以在更新现有操作时完成。

启用多资产支持后，负载格式会立即切换。 旧版负载和支持多资产的负载互不兼容。

## 来自 Frame.io 的负载

当用户单击您的自定义操作时，负载将发送到您在 URL 字段中设置的 URL。 使用此负载来标识：

#### 操作上下文

* 点击了哪项自定义操作

* 点击了哪些资源

* 哪个用户执行了操作

* 触发了哪种事件类型

#### 组织上下文

* 哪个帐户与自定义操作关联

* 哪个工作区与自定义操作关联

* 哪个项目包含这些资源

#### 负载 - 单资产或多资产支持

自定义操作最初会使用包含一个资产的 `resource` 对象在每个请求中仅接受一个资产。 启用多资产支持后，负载会使用包含一个或多个资产的 `resources` 列表（一个请求中最多有 100 个资产）。

```json
  POST /your/url
  {
    "account_id": "1a94720e-f7f5-4c41-ab62-d11eebe3d504",
    "action_id": "0aeb9feb-f8eb-4100-a8c2-b39b66d354ab",
    "data": {
        "description": "Pretty cool video.",
        "title": "Hey there!"
    },
    "interaction_id": "985559de-865a-40e5-a687-2bbed4497eaa",
    "project": {
        "id": "3e34e7cb-5c21-49f5-8ca9-a1419584c2ea"
    },
    "resources": [
        {
            "id": "fe41c5a8-1b8d-417d-a7df-0b88bc19b476",
            "type": "file"
        },
        {
            "id": "fe81fb2b-1316-4a76-9cf6-0630f474fc39",
            "type": "file"
        }
    ],
    "type": "some.event",
    "user": {
        "id": "68093c1c-4b05-41ce-a3c9-e64d195b1935"
    },
    "workspace": {
        "id": "629086c0-f6aa-4d63-a284-f39bcd415b9f"
    }
  }
```

#### 旧版负载 - 仅支持单个资产

```json
  POST /your/url
  {
      "account_id": "1a94720e-f7f5-4c41-ab62-d11eebe3d504",
      "action_id": "0e38b93a-9f10-4bc7-90bc-bd07a16e510a",
      "data": {
          "description": "Wow look at this.",
          "title": "Hey there!!"
      },
      "interaction_id": "dff6d369-7150-41f9-8e6f-4f0895f6b416",
      "project": {
          "id": "3e34e7cb-5c21-49f5-8ca9-a1419584c2ea"
      },
      "resource": {
          "id": "fe81fb2b-1316-4a76-9cf6-0630f474fc39",
          "type": "file"
      },
      "type": "some.event",
      "user": {
          "id": "68093c1c-4b05-41ce-a3c9-e64d195b1935"
      },
      "workspace": {
          "id": "629086c0-f6aa-4d63-a284-f39bcd415b9f"
      }
  }                                  
```

### 从旧版负载迁移

> **计划弃用旧版负载**
>
> 我们已计划弃用旧版负载，强烈建议用户将其服务迁移至处理新的负载。
>
> 通过启用配置标志并更新您的负载处理，操作可以无缝过渡以支持多资产负载。
>
> 1. 将单数 `resource` 对象的用法替换为 `resources` 列表 2。 更新代码以遍历 `resources` 列表
>
> 2. 在操作配置中启用多资产标志

| 字段名称             | 描述                                                           |
| ---------------- | ------------------------------------------------------------ |
| `account_id`     | 操作的唯一帐户 ID。                                                  |
| `action_id`      | 操作的唯一 ID。                                                    |
| `interaction_id` | Frame.io 生成的唯一标识符，用于在多个请求中跟踪您的事务，例如链式消息或表单回调。 在操作的每个序列中保持不变。 |
| `project_id`     | 操作的唯一项目 ID。                                                  |
| `resource.id`    | 您从中触发操作的资源 ID。                                               |
| `resource.type`  | 您从中触发操作的资源类型。                                                |
| `type`           | 配置操作时在 `event` 字段中提供的名称。                                     |
| `user.id`        | 触发操作的用户 ID。                                                  |
| `workspace.id`   | 使用操作的工作区 ID。                                                 |
| `data`           | 包含表单字段名称和用户选择值的键值对。 您的应用程序会接收此信息，从而了解用户做出的选择。                |

## 交互、重试和超时

`interaction_id` 是用于跟踪交互随时间演变情况的唯一标识符。 如果您不需要响应用户，那么返回 200 状态代码即可。 虽然不是必须的，但我们建议包含操作结果的信息，例如成功消息或错误警报。 自定义操作支持消息回调。

> **Note**
>
> Frame.io 期待在不到 10 秒内得到响应，且在等待成功回复时会最多重试 5 次。 理想情况下会立即响应，异步操作会在通过自定义操作触发后发生。

## 创建消息回调

在对 Webhook 事件的 HTTP 响应中，您可以返回一个 JSON 对象，描述将在 Frame.io UI 中返回给发起用户的消息。

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

消息让您可以直接在 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"
        }
      ]
    }
  ]
}
```

当用户提交表单时，您将收到一个事件，发送到与初始 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"
      }
   ]
}
```

### 复选框

没有额外参数的简单复选框。

```json
{ 
   "type": "boolean", 
   "name": "enabled", 
   "label": "Enabled", 
   "value": "false"
}
```

### 链接

没有额外参数的简单链接。

```json
{
  "type": "link",
  "name": "videoLink",
  "label": "Video Link",
  "value": "https://www.youtube.com/watch?v=XtX1zv9CEVc"
}
```

## Frame.io 权限模型

自定义操作具有特殊的权限模型：它们属于工作区，而不属于帐户中存在的任何特定用户。 这意味着：

#### 创建和管理

* 任何管理员都可以在工作区上创建自定义操作。

* 任何管理员都可以修改或删除团队上存在的自定义操作。

#### 实时更新

* 一旦修改，所有用户都会立即看到更改的结果。

## 安全性和验证

默认情况下，所有自定义操作在创建期间都会生成一个签名密钥。 此项不可配置。 此密钥可用于验证请求是否来自 Frame.io。 `POST` 请求中包含以下内容：

| 名称                            | 描述             |
| ----------------------------- | -------------- |
| `X-Frameio-Request-Timestamp` | 您的自定义操作被触发的时间。 |
| `X-Frameio-Signature`         | 计算得出的签名。       |

#### 时间戳验证

**时间戳**是请求在离开 Frame.io 网络时被签名的时间。 这可用于防止重放攻击。 我们建议验证此时间与本地时间相差是否在 5 分钟以内。

#### 签名验证

**签名**是使用首次创建自定义操作时提供的签名密钥的 HMAC SHA-256 哈希值。

### 验证签名

#### 提取签名

从 HTTP 标头中提取签名。

#### 创建要签名的消息

通过合并版本、交付时间和请求体 `v0:timestamp:body` 来创建一个用于签名的消息。

#### 计算 HMAC SHA256

使用您的签名密钥计算 HMAC SHA256 签名。

#### 比较签名

将您计算的签名与提供的签名进行比较！

> **Note**
>
> 提供的签名以 `v0=` 为前缀。 目前 Frame.io 只有这一种用于请求签名的版本。 您需要在计算的签名中添加此前缀。

**`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
```

## 反馈

我们很乐意听取开发者和最终用户的意见，了解你们希望如何在 Frame.io V4 中使用操作。 请务必[联系我们](https://forum.frame.io/)，提出您的问题、想法和用例，从而帮助我们确定优先级。