菜单路径: 仪表板 > 任务

任务

概述

任务让 ChatGPT、Claude 等 AI 在你自己的服务器(Worker)上执行工作。说一句“在我们的 ERP 里给 A-1042 下 30 件的订单”,AI 就会把工作发给 Worker,并把结果显示在对话中。

  • 3Min API 只负责投递任务并记录状态,实际工作由 Worker 完成
  • Worker 是你自己运行的 HTTP 服务器

何时使用

  • 让内部系统做事 — ERP 下单、查询库存、搜索内部数据库
  • 耗时的工作 — 生成报表、批量转换(最长 24 小时)
  • 只能在自己电脑上运行的脚本 — 用隧道加上公网 URL 即可(见下文

如果目的是保存和收集数据,请使用端点

不擅长写 Worker 代码?可以直接对你正在用的 AI 说:“帮我做一个 3minapi 任务 Worker”。AI 会通过 help 工具读取与本页相同的约定并编写代码。

如何进入

  • 注册 Worker:仪表盘 → 任务Worker 标签 → 注册 Worker
  • 查看运行:仪表盘 → 任务运行记录 标签

工作原理

① 在网页上注册 Worker          (只需一次)
② AI → task_create             创建任务,状态 working
③ 3Min API → POST 到 Worker    带签名的请求(包含 callback_url)
④ Worker → 立即返回 2xx         仅表示“已收到”,不是结果
⑤ Worker → callback_url        进度消息(可选)→ completed 或 failed
⑥ AI → task_get                读取结果
在哪里 做什么
网页仪表盘 注册、编辑、删除 Worker;查看和更换密钥;查看运行记录
AI 对话 查看 Worker 列表、发送任务、查看状态和结果、取消
Worker 服务器 接收请求、处理工作、报告结果

AI 看不到 Worker 的 URL 和密钥,两者都只能在网页上管理。

注册 Worker (网页)

字段 说明 限制
名称 必填。AI 使用的名称。不区分大小写且唯一,注册后不可修改 50 个字符
描述 必填。AI 根据描述选择 Worker 500 个字符
Worker URL 必填。以 POST 接收任务的地址 2,048 字节
认证头 / 认证值 可选。用于 Worker 自身的认证。只填值时,头为 Authorization 128 / 8,192 字节
  • 描述要写具体。 AI 只看名称和描述,看不到 URL。与其写 erp-1,不如写“在我们的 ERP 中登记订单并返回订单号”,写清接收什么、返回什么,AI 才能选中它

两个密钥方向相反

注册后会自动生成两个密钥。在 Worker 详情页(仪表盘 → 任务Worker 标签 → 选择 Worker)通过 显示密钥复制密钥 查看。

密钥 格式 方向 用途
签名密钥 whsec_... 3Min API → Worker 验证请求确实来自 3Min API
回调密钥 tm_task_... Worker → 3Min API 报告时以 Authorization: Bearer 发送
  • 两者是不同的值,不能互换。 端点的 API 密钥(tm_live_tm_test_)也不能用作回调密钥
操作 效果 Worker 需要做的
轮换签名密钥 48 小时内,新任务同时带有新旧两个签名。轮换之前创建的任务的重试(最长约 5 小时)只带旧签名 旧密钥至少保留 6 小时,再在 48 小时内切换
重新生成回调密钥 旧密钥立即失效(包括正在运行的任务) 立即切换

48 小时内再次轮换,最初的密钥会立即失效。仪表盘只显示当前密钥,如需保留旧密钥,请在轮换之前复制

实现 Worker

Worker 必须遵守的五条规则

  1. 在 15 秒内返回 2xx。 工作在响应之后再处理
  2. 即使工作失败,也要以 2xx 接收请求。 失败通过报告告知(5xx 会重试,其他 4xx 会立即失败 — 见响应码
  3. task_id 去重。 同一任务可能到达两次(at-least-once)
  4. 签名验证可选,但建议开启。 不验证的话,任何知道 Worker URL 的人都能发送伪造任务(签名验证
  5. 把结果报告到 callback_url 发送一次 completedfailed。没有报告的任务会在 24 小时后变为 failed状态报告 API

投递请求

3Min API 发送到 Worker URL 的请求。

POST https://worker.example.com/tasks
Content-Type: application/json
webhook-id: 0199a1b2-...
webhook-timestamp: 1757650867
webhook-signature: v1,K3XkZ1n0...
X-3minapi-Task-Id: 0199a1b2-...
X-3minapi-Delivery-Attempt: 1
Authorization: Bearer xxx
请求头 内容
webhook-id 任务 ID(与 task_id 相同)
webhook-timestamp 发送时间(Unix 秒),每次尝试都会更新
webhook-signature 签名。轮换期间为空格分隔的两个值(签名验证
X-3minapi-Task-Id 任务 ID(与 webhook-id 相同)
X-3minapi-Delivery-Attempt 第几次投递尝试(从 1 开始)
注册的认证头 仅在注册时填写了认证头和认证值时发送

请求体:

{
	"type": "task.created",
	"timestamp": "2026-09-12T04:21:07.123Z",
	"data": {
		"task_id": "0199a1b2-...",
		"input": { "sku": "A-1042", "qty": 30 },
		"callback_url": "https://api.3minapi.com/api/v1/tasks/0199a1b2-.../result"
	}
}
字段 内容
task_id 任务 ID,用于去重
input AI 发送的 JSON 对象(值相同,但键的顺序可能不同)
callback_url 该任务的报告地址。 状态和结果都发送到这里

Worker 的响应码

只看状态码,不读取响应体。

响应 结果
2xx 已接收。在收到报告前保持 working
5xx(501、505 除外)、429、15 秒超时、连接错误 在 30 秒、2 分钟、10 分钟、1 小时、4 小时后重试,共 6 次(约 5 小时)。429 的 Retry-After 仅在更长时生效
其他 4xx(401、404、409 等)、501、505 不重试,立即 failed
  • 签名验证失败时返回 401。如果密钥填错,第一个任务就会立即 failed,能很快发现配置问题

签名验证 (可选)

webhook-signature 请求头就是签名。3Min API 用 Worker 的签名密钥计算后附上,Worker 用同样的计算确认是否一致。

签名内容 = "{webhook-id}.{webhook-timestamp}.{原始请求体}"
密钥     = 去掉 whsec_ 前缀后 base64 解码得到的字节
签名     = "v1," + base64( HMAC-SHA256(密钥, 签名内容) )
  • 使用官方 Standard Webhooks 库几行代码即可完成(示例代码)。各语言的包和手动实现方法见验证 Webhook 签名(同一规范)
  • 用原始请求体字节验证。 先解析 JSON 再序列化一定会失败
  • webhook-timestamp 偏差超过 5 分钟时,库会拒绝(防止重放)
  • 收到两个签名时,任意一个匹配即可通过(轮换规则
  • 如果不验证,至少要确认 callback_urlhttps://api.3minapi.com/ 开头 — 回调密钥会发送到这个地址

状态报告 API

把状态发送到投递请求中的 callback_url。进度消息可以发送多次,完成或失败只发送一次

POST https://api.3minapi.com/api/v1/tasks/{task_id}/result
Authorization: Bearer tm_task_...
Content-Type: application/json
  • 地址callback_url 以此格式到达,任务 ID 已填好。不要自己拼接,直接使用
  • 回调密钥:Worker 详情页的 回调密钥复制密钥。保存在 Worker 服务器的环境变量等处

进行中 — 任务保持 working,AI 通过 task_get 查看消息

{ "status": "working", "status_message": "1/2 正在检查库存" }

完成

{
	"status": "completed",
	"result": { "order_id": "PO-20260912-001" },
	"status_message": "订单 PO-20260912-001 已登记"
}

失败

{
	"status": "failed",
	"error": { "code": 1001, "message": "库存不足", "data": { "available": 12 } }
}
字段 规则
status workingcompletedfailed 之一
status_message working 时必填,其他可选。超过 256 个字符的部分会被截断failed 未填时使用 error.message
result completed 时必填。除 null 外的任意 JSON
error failed 时必填。code(整数)+ message(字符串),data 可选
  • 请求体整体须在 100 KB 以内resulterror 不会被截断,超出时整个报告会被 413 拒绝
  • 每次被接收的报告计为 1 次 API 调用。进度消息只在有意义的阶段发送

状态报告 API 响应码

状态码 含义 Worker 的处理
202 已接收,通常几秒内生效
400 请求体格式错误、任务 ID 无效、字符串中含 NUL 字符 修改代码
401 缺少回调密钥或密钥错误(包括端点 API 密钥) 检查密钥
404 不是该 Worker 的任务 检查 callback_url
409 任务已结束(完成、失败、取消) 停止报告
413 请求体超过 100 KB,未记录任何内容 若任务仍为 working,缩小后重新发送
415 Content-Type 不是 JSON 修改请求头
503 临时故障 稍后重新发送

示例代码 (Node.js)

需要 Node.js 18 或更高版本,并在 package.json 中设置 "type": "module"

// npm i express standardwebhooks
import express from 'express';
import { Webhook } from 'standardwebhooks';

// 签名密钥(whsec_...)和回调密钥(tm_task_...)是不同的值
const wh = new Webhook(process.env.TASK_SIGNING_SECRET.replace('whsec_', ''));
const CALLBACK_KEY = process.env.TASK_CALLBACK_KEY;
// 已接收的 task_id — 生产环境请保存到数据库或 Redis
const seen = new Set();

const app = express();

// 签名基于原始字节计算。先解析 JSON 会导致验证失败。
app.post('/tasks', express.raw({ type: 'application/json' }), (req, res) => {
	let envelope;
	try {
		envelope = wh.verify(req.body, {
			'webhook-id': req.header('webhook-id'),
			'webhook-timestamp': req.header('webhook-timestamp'),
			'webhook-signature': req.header('webhook-signature')
		});
	} catch {
		return res.sendStatus(401);
	}

	const { task_id, input, callback_url } = envelope.data;

	// 规则 3 — 已接收的任务不再处理
	if (seen.has(task_id)) return res.sendStatus(202);
	seen.add(task_id);

	// 规则 1 — 先响应,再工作
	res.sendStatus(202);
	run(input, callback_url);
});

async function run(input, callbackUrl) {
	try {
		// 进度消息 — 任务保持 working,AI 通过 task_get 查看
		await report(callbackUrl, { status: 'working', status_message: '1/2 正在检查库存' });
		await checkStock(input); // ← 你的实际工作

		await report(callbackUrl, { status: 'working', status_message: '2/2 正在登记订单' });
		const orderId = await createOrder(input);

		// 完成 — 结果和摘要消息
		await report(callbackUrl, {
			status: 'completed',
			result: { order_id: orderId },
			status_message: `订单 ${orderId} 已登记`
		});
	} catch (err) {
		// 规则 2 — 工作或报告失败时,报告 failed
		await report(callbackUrl, {
			status: 'failed',
			error: { code: 1001, message: String(err?.message ?? err) },
			status_message: '订单登记失败'
		}).catch(console.error);
	}
}

// callbackUrl — 直接使用投递请求中的 callback_url
async function report(callbackUrl, body) {
	for (let attempt = 1; attempt <= 3; attempt++) {
		const res = await fetch(callbackUrl, {
			method: 'POST',
			headers: {
				Authorization: `Bearer ${CALLBACK_KEY}`,
				'Content-Type': 'application/json'
			},
			body: JSON.stringify(body)
		}).catch(() => null);

		// 202 已接收,409 任务已结束 — 无需再发送
		if (res?.status === 202 || res?.status === 409) return;
		// 400、401、404、413 重发结果也一样
		if (res && res.status !== 503) throw new Error(`report rejected: HTTP ${res.status}`);
		// 只有 503 和网络错误才稍后重试
		if (attempt < 3) await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
	}
	throw new Error('report failed: service unavailable');
}

app.listen(8080);

在自己的电脑上运行 Worker

Worker 需要能从互联网访问的公网 URL。如果电脑在路由器后面,请用隧道给本地端口加上公网 HTTPS 地址。

cloudflared tunnel --url http://localhost:8080
ngrok http 8080
  • 安装和使用:Cloudflare Quick Tunnelsngrok 入门
  • 在输出的地址后加上路径(例如 https://xxxx.trycloudflare.com/tasks),注册为 Worker URL
  • 不登录账号的临时地址每次运行都会变化。 每次都要更新 Worker URL,或使用各服务的固定地址功能
  • 电脑关机或隧道断开时,任务通常在重试(约 5 小时)后变为 failed。如果服务对离线地址返回 404,任务会立即 failed

发送任务 (AI)

注册 Worker 后即可在 AI 对话中直接使用,无需知道工具名称。

  • “在我们的 ERP 里给 A-1042 下 30 件的订单”
  • “刚才发的订单怎么样了?”
  • “取消刚才发送的任务”
工具 作用
worker_list 已注册 Worker 的名称和描述(看不到 URL 和密钥)
task_create 用 Worker 名称input(JSON 对象,100 KB 以内)创建任务
task_get 单个任务的状态、消息、结果或错误
task_list 最近的任务列表(不含结果正文)
task_cancel 取消运行中的任务(最多 100 个)
  • 向同一 Worker 在 2 分钟内(视时机最长约 4 分钟)发送相同的 input,会返回已有任务。已完成和已取消的任务也会原样返回,只有失败的任务会重新创建
  • 取消不会通知 Worker。 只会停止剩余的投递尝试,已收到任务的 Worker 之后的报告会得到 409

查看状态

状态 含义 是否会变化
working 投递中,或 Worker 正在处理
completed Worker 报告完成 最终
failed Worker 报告失败、投递失败或超过 24 小时 最终
cancelled 已取消 最终

failed 的原因记录在 status_message 中。

原因 status_message 示例
Worker 报告失败 Worker 发送的消息(没有则为 error.message
重试用尽 The task could not be delivered to the worker after 6 attempts (no response within 15s).
立即失败的响应 The task could not be delivered to the worker after 1 attempt (HTTP 401).
超过 24 小时 Worker did not respond within 24 hours.

查看位置:

  • AItask_gettask_list。Worker 的 result 不经摘要原样传递
  • 网页运行记录 标签。按时间段(7、30、60 天,Free 为 7 天)和状态筛选,点击行可查看输入、结果、错误和投递。投递最终失败时,投递 中会显示尝试次数、最后响应和原因

限制

项目
Worker 数量 Free 1 个 / 付费方案无限制
任务数量·并发 无限制(在每月 API 调用额度内)
期限 创建后 24 小时 — 超过则 failed(每小时检查一次,最长约 25 小时)
历史保留 付费方案至少 60 天 / Free 在 Worker 注册 7 天后删除 Worker 及其全部运行记录
input 大小 100 KB
报告请求体 100 KB(status_message 超过 256 个字符会被截断)
投递超时·重试 15 秒、共 6 次,约 5 小时
签名密钥轮换宽限期 48 小时
重新生成回调密钥 旧密钥立即失效
API 调用计数 成功的任务工具调用 1 次、被接收的报告 1 次各计 1 次。投递和重试计 0 次。超出额度时报告仍会被接收
投递保证 at-least-once(同一任务可能到达两次)

隐私说明

  • Worker 的 resulterror不经脱敏原样进入 AI 对话
  • 3Min API 不会识别自由格式 JSON 中的个人信息,请勿在结果中放入超出需要的个人信息

常见问题

  • 所有任务都立即 failed:查看运行详情中的 投递 → 最后响应。如果是 401,可能是把回调密钥填到了签名密钥的位置,或者轮换后立即删除了旧密钥
  • 任务一直是 working:Worker 只返回了 2xx,却没有发送报告。请在 Worker 日志中查看状态报告 API 的响应码
  • 报告返回 401:确认使用的是以 tm_task_ 开头的回调密钥,而不是重新生成之前的密钥
  • 报告返回 409:任务已结束或已取消,无需再发送
  • 报告返回 413:请求体超过 100 KB。请缩小 result 后重新发送
  • 发送相同请求却没有创建新任务:2 分钟内相同的 input 会返回已有任务。可以在 input 中加入日期等信息加以区分