ClawSkills logoClawSkills

Tally

通过托管 OAuth 集成 Tally API。管理表单、提交、工作区和 Webhook。 当用户想要创建或管理 Tally 表单时,请使用此技能

介绍

# Tally

通过托管的 OAuth 身份验证访问 Tally API。为您的 Tally 账户管理表单、提交记录、工作区和 Webhook。

## 快速开始

```bash # List your forms python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/tally/forms') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('User-Agent', 'Maton/1.0') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

## 基础 URL

``` https://gateway.maton.ai/tally/{native-api-path} ```

将 `{native-api-path}` 替换为实际的 Tally API 端点路径。网关将请求代理到 `api.tally.so` 并自动注入您的 OAuth 令牌。

## 身份验证

所有请求都需要在 Authorization header 和 User Agent header 中包含 Maton API 密钥:

``` Authorization: Bearer $MATON_API_KEY User-Agent: Maton/1.0 ```

**环境变量:** 将您的 API 密钥设置为 `MATON_API_KEY`:

```bash export MATON_API_KEY="YOUR_API_KEY" ```

### 获取您的 API 密钥

1. 登录或创建账户,访问 [maton.ai](https://maton.ai) 2. 前往 [maton.ai/settings](https://maton.ai/settings) 3. 复制您的 API 密钥

## 连接管理

在 `https://ctrl.maton.ai` 管理您的 Tally OAuth 连接。

### 列出连接

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections?app=tally&status=ACTIVE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### 创建连接

```bash python <<'EOF' import urllib.request, os, json data = json.dumps({'app': 'tally'}).encode() req = urllib.request.Request('https://ctrl.maton.ai/connections', data=data, method='POST') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Content-Type', 'application/json') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### 获取连接

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

**响应:** ```json { "connection": { "connection_id": "cd54e2b0-f1d0-435e-a97d-f2d6a5c474bf", "status": "ACTIVE", "creation_time": "2026-02-07T21:00:31.222600Z", "last_updated_time": "2026-02-07T21:00:37.821240Z", "url": "https://connect.maton.ai/?session_token=...", "app": "tally", "metadata": {} } } ```

在浏览器中打开返回的 `url` 以完成 OAuth 授权。

### 删除连接

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections/{connection_id}', method='DELETE') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### 指定连接

如果您有多个 Tally 连接,请使用 `Maton-Connection` header 指定要使用的连接:

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://gateway.maton.ai/tally/forms') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') req.add_header('Maton-Connection', 'cd54e2b0-f1d0-435e-a97d-f2d6a5c474bf') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

如果省略,网关将使用默认(最旧的)活动连接。

## API 参考

### 用户

#### 获取当前用户

```bash GET /tally/users/me ```

**响应:** ```json { "id": "w2lBkb", "firstName": "John", "lastName": "Doe", "email": "[email protected]", "organizationId": "n0Ze8Q", "subscriptionPlan": "FREE", "createdAt": "2026-02-07T20:58:54.000Z", "updatedAt": "2026-02-07T22:50:35.000Z" } ```

### 表单

#### 列出表单

```bash GET /tally/forms ```

**查询参数:** - `page` - 页码(默认:1) - `limit` - 每页项目数(默认:50)

**响应:** ```json { "items": [ { "id": "GxdRaQ", "name": "Contact Form", "workspaceId": "3jW9Q1", "organizationId": "n0Ze8Q", "status": "PUBLISHED", "hasDraftBlocks": false, "numberOfSubmissions": 42, "createdAt": "2026-02-09T08:36:00.000Z", "updatedAt": "2026-02-09T08:36:17.000Z", "isClosed": false } ], "page": 1, "limit": 50, "total": 1, "hasMore": false } ```

#### 获取表单

```bash GET /tally/forms/{formId} ```

**响应:** ```json { "id": "GxdRaQ", "name": "Contact Form", "workspaceId": "3jW9Q1", "status": "PUBLISHED", "blocks": [ { "uuid": "11111111-1111-1111-1111-111111111111", "type": "FORM_TITLE", "groupUuid": "22222222-2222-2222-2222-222222222222", "groupType": "FORM_TITLE", "payload": {} }, { "uuid": "33333333-3333-3333-3333-333333333333", "type": "INPUT_TEXT", "groupUuid": "44444444-4444-4444-4444-444444444444", "groupType": "INPUT_TEXT", "payload": {} } ], "settings": null } ```

#### 创建表单

```bash POST /tally/forms Content-Type: application/json

{ "status": "DRAFT", "workspaceId": "3jW9Q1", "blocks": [ { "type": "FORM_TITLE", "uuid": "11111111-1111-1111-1111-111111111111", "groupUuid": "22222222-2222-2222-2222-222222222222", "groupType": "FORM_TITLE", "title": "My Form", "payload": {} }, { "type": "INPUT_TEXT", "uuid": "33333333-3333-3333-3333-333333333333", "groupUuid": "44444444-4444-4444-4444-444444444444", "groupType": "INPUT_TEXT", "title": "Your name", "payload": {} } ] } ```

**块类型:** - `FORM_TITLE` - 表单标题块 - `INPUT_TEXT` - 单行文本输入 - `INPUT_EMAIL` - 邮箱输入 - `INPUT_NUMBER` - 数字输入 - `INPUT_PHONE_NUMBER` - 电话号码输入 - `INPUT_DATE` - 日期选择器 - `INPUT_TIME` - 时间选择器 - `INPUT_LINK` - URL 输入 - `TEXTAREA` - 多行文本输入 - `MULTIPLE_CHOICE` - 单选按钮 - `CHECKBOXES` - 复选框组 - `DROPDOWN` - 下拉选择 - `LINEAR_SCALE` - 量表评分 - `RATING` - 星级评分 - `FILE_UPLOAD` - 文件上传 - `SIGNATURE` - 签名字段 - `PAYMENT` - 支付字段 - `HIDDEN_FIELDS` - 隐藏字段

**注意:** 块的 `uuid` 和 `groupUuid` 必须是有效的 UUID(GUID)。

#### 更新表单

```bash PATCH /tally/forms/{formId} Content-Type: application/json

{ "name": "Updated Form Name", "status": "PUBLISHED" } ```

**状态值:** - `DRAFT` - 表单为草稿 - `PUBLISHED` - 表单已发布

#### 删除表单

```bash DELETE /tally/forms/{formId} ```

将表单移至回收站。

### 表单问题

#### 列出问题

```bash GET /tally/forms/{formId}/questions ```

**响应:** ```json { "questions": [ { "uuid": "33333333-3333-3333-3333-333333333333", "type": "INPUT_TEXT", "title": "Your name" } ], "hasResponses": true } ```

### 表单提交

#### 列出提交记录

```bash GET /tally/forms/{formId}/submissions ```

**查询参数:** - `page` - 页码(默认:1) - `limit` - 每页项目数(默认:50) - `startDate` - 按开始日期过滤(ISO 8601) - `endDate` - 按结束日期过滤(ISO 8601) - `afterId` - 获取此 ID 之后的提交记录(游标分页)

**响应:** ```json { "page": 1, "limit": 50, "hasMore": false, "totalNumberOfSubmissionsPerFilter": { "all": 42, "completed": 40, "partial": 2 }, "questions": [ { "uuid": "33333333-3333-3333-3333-333333333333", "type": "INPUT_TEXT", "title": "Your name" } ], "submissions": [ { "id": "sub123", "respondentId": "resp456", "formId": "GxdRaQ", "createdAt": "2026-02-09T10:00:00.000Z", "isCompleted": true, "responses": [ { "questionId": "33333333-3333-3333-3333-333333333333", "value": "John Doe" } ] } ] } ```

#### 获取提交记录

```bash GET /tally/forms/{formId}/submissions/{submissionId} ```

#### 删除提交记录

```bash DELETE /tally/forms/{formId}/submissions/{submissionId} ```

### 工作区

#### 列出工作区

```bash GET /tally/workspaces ```

**响应:** ```json { "items": [ { "id": "3jW9Q1", "name": "My Workspace", "createdByUserId": "w2lBkb", "createdAt": "2026-02-09T08:35:53.000Z", "updatedAt": "2026-02-09T08:35:53.000Z" } ], "page": 1, "limit": 50, "total": 1, "hasMore": false } ```

#### 获取工作区

```bash GET /tally/workspaces/{workspaceId} ```

**响应:** ```json { "id": "3jW9Q1", "name": "My Workspace", "createdByUserId": "w2lBkb", "createdAt": "2026-02-09T08:35:53.000Z", "members": [ { "id": "w2lBkb", "firstName": "John", "lastName": "Doe", "email": "[email protected]" } ] } ```

#### 创建工作区

```bash POST /tally/workspaces Content-Type: application/json

{ "name": "New Workspace" } ```

**注意:** 创建工作区需要 Pro 订阅。

#### 更新工作区

```bash PATCH /tally/workspaces/{workspaceId} Content-Type: application/json

{ "name": "Updated Workspace Name" } ```

#### 删除工作区

```bash DELETE /tally/workspaces/{workspaceId} ```

将工作区及其所有表单移至回收站。

### 组织用户

#### 列出用户

```bash GET /tally/organizations/{organizationId}/users ```

**响应:** ```json [ { "id": "w2lBkb", "firstName": "John", "lastName": "Doe", "email": "[email protected]", "createdAt": "2026-02-07T20:58:54.000Z" } ] ```

#### 移除用户

```bash DELETE /tally/organizations/{organizationId}/users/{userId} ```

### 组织邀请

#### 列出邀请

```bash GET /tally/organizations/{organizationId}/invites ```

#### 创建邀请

```bash POST /tally/organizations/{organizationId}/invites Content-Type: application/json

{ "email": "[email protected]", "workspaceIds": ["3jW9Q1"] } ```

#### 取消邀请

```bash DELETE /tally/organizations/{organizationId}/invites/{inviteId} ```

### Webhook

#### 列出 Webhook

```bash GET /tally/webhooks ```

**注意:** 列出 Webhook 可能需要特定权限。

#### 创建 Webhook

```bash POST /tally/webhooks Content-Type: application/json

{ "formId": "GxdRaQ", "url": "https://your-endpoint.com/webhook", "eventTypes": ["FORM_RESPONSE"] } ```

**Webhook 事件类型:** - `FORM_RESPONSE` - 当提交新的表单响应时触发

#### 更新 Webhook

```bash PATCH /tally/webhooks/{webhookId} Content-Type: application/json

{ "url": "https://new-endpoint.com/webhook" } ```

#### 删除 Webhook

```bash DELETE /tally/webhooks/{webhookId} ```

#### 列出 Webhook 事件

```bash GET /tally/webhooks/{webhookId}/events ```

#### 重试 Webhook 事件

```bash POST /tally/webhooks/{webhookId}/events/{eventId} ```

## 分页

Tally 使用基于页码的分页:

```bash GET /tally/forms?page=1&limit=50 ```

响应包含分页信息:

```json { "items": [...], "page": 1, "limit": 50, "total": 100, "hasMore": true } ```

对于提交记录,也可以使用 `afterId` 进行基于游标的分页。

## 代码示例

### JavaScript

```javascript const response = await fetch( 'https://gateway.maton.ai/tally/forms', { headers: { 'Authorization': `Bearer ${process.env.MATON_API_KEY}`, 'User-Agent': 'Maton/1.0' } } ); const data = await response.json(); console.log(data.items); ```

### Python

```python import os import requests

response = requests.get( 'https://gateway.maton.ai/tally/forms', headers={ 'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}', 'User-Agent': 'Maton/1.0' } ) data = response.json() print(data['items']) ```

### 创建表单并获取提交记录

```python import os import requests import uuid

headers = { 'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}', 'User-Agent': 'Maton/1.0' }

# Create a simple form form_data = { 'status': 'DRAFT', 'blocks': [ { 'type': 'FORM_TITLE', 'uuid': str(uuid.uuid4()), 'groupUuid': str(uuid.uuid4()), 'groupType': 'FORM_TITLE', 'title': 'Contact Form', 'payload': {} }, { 'type': 'INPUT_EMAIL', 'uuid': str(uuid.uuid4()), 'groupUuid': str(uuid.uuid4()), 'groupType': 'INPUT_EMAIL', 'title': 'Your email', 'payload': {} } ] }

response = requests.post( 'https://gateway.maton.ai/tally/forms', headers=headers, json=form_data ) form = response.json() print(f"Created form: {form['id']}")

# Get submissions for a form response = requests.get( f'https://gateway.maton.ai/tally/forms/{form["id"]}/submissions', headers=headers ) submissions = response.json() print(f"Total submissions: {submissions['totalNumberOfSubmissionsPerFilter']['all']}") ```

## 注意事项

- 表单和工作区 ID 是短字母数字字符串(例如 `GxdRaQ`) - 块的 `uuid` 和 `groupUuid` 字段必须是有效的 UUID(GUID) - 创建工作区需要 Pro 订阅 - 该 API 处于公开测试阶段,可能会有变动 - 速率限制:每分钟 100 次请求 - 请使用 Webhook 而非轮询来获取实时提交通知 - 重要:将 curl 输出通过管道传递给 `jq` 或其他命令时,在某些 shell 环境中,像 `$MATON_API_KEY` 这样的环境变量可能无法正确展开

## 错误处理

| 状态 | 含义 | |--------|---------| | 400 | 缺少 Tally 连接或验证错误 | | 401 | Maton API 密钥无效或缺失 | | 403 | 权限不足 | | 404 | 资源未找到 | | 429 | 速率受限(100 次/分钟) | | 4xx/5xx | 来自 Tally API 的透传错误 |

### 故障排除:API 密钥问题

1. 检查是否设置了 `MATON_API_KEY` 环境变量:

```bash echo $MATON_API_KEY ```

2. 通过列出连接来验证 API 密钥是否有效:

```bash python <<'EOF' import urllib.request, os, json req = urllib.request.Request('https://ctrl.maton.ai/connections') req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}') print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2)) EOF ```

### 故障排除:应用名称无效

1. 确保您的 URL 路径以 `tally` 开头。例如:

- 正确:`https://gateway.maton.ai/tally/forms` - 错误:`https://gateway.maton.ai/forms`

## 资源

- [Tally API 介绍](https://developers.tally.so/api-reference/introduction) - [Tally API 参考](https://developers.tally.so/llms.txt) - [Tally 帮助中心](https://help.tally.so/) - [Maton 社区](https://discord.com/invite/dBfFAcefs2) - [Maton 支持](mailto:[email protected])

更多产品