> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inputapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 通用任务与产物下载

> 区分任务受理与完成，查询状态并下载产物。

通用任务 API 用于网关持久化的任务：提交、读取状态、列出产物和下载产物。它不接受任意供应商任务 ID，也不是 OpenAI Batch / Files API。使用提交响应中的公开任务 ID，并保持相同用户与项目权限。

## 提交与读取

按 [REST 运行环境](/zh/guides/http-runtime)准备环境。仅在部署方确认该插件支持旧通用入口时使用 `submit`；`task-request.json` 必须是该插件的规范化请求对象，含 `model`，其他字段由插件决定。不能直接把所有供应商原生请求复制到此入口。通常优先使用已有的 [图片](/zh/guides/image-generation)、[视频](/zh/guides/video-generation)或 [Responses](/zh/guides/background-responses) 协议。

```bash theme={null}
export API_PLUGIN_KEY="YOUR_PLUGIN_KEY"
export API_TASK_REQUEST_FILE="./task-request.json"
node examples/tasks.mjs submit
export API_TASK_ID="TASK_ID_FROM_RESPONSE"
node examples/tasks.mjs status
node examples/tasks.mjs artifacts
export API_ARTIFACT_KEY="ARTIFACT_KEY_FROM_LIST"
node examples/tasks.mjs head
export API_ARTIFACT_OUTPUT="./task-artifact.bin"
node examples/tasks.mjs download
```

上述命令按步骤执行，`submit` 只执行一次，后续粘贴实际 ID。提交的 `queued` 是固定受理回执，甚至即时完成的任务也要查询最终状态。状态查询返回大写 `NOT_START`、`SUBMITTED`、`QUEUED`、`IN_PROGRESS`、`SUCCESS`、`FAILURE`、`NEEDS_REVIEW` 或 `UNKNOWN`。`NEEDS_REVIEW` 需要人工核对，`UNKNOWN` 不是成功；失败原因可能敏感，不直接写公共日志。

示例每次只查询一次，不轮询。`status` 成功退出表示读到有效状态，只有 `completed: true` 才表示任务 `SUCCESS`。应用若增加轮询，应设总截止时间、间隔和次数上限；截止或断连不会取消任务。没有通用取消、删除或列出全部任务的推理端点。

## 产物与下载

`artifacts`、`head`、`download` 会先确认 `SUCCESS`。成功任务仍可能返回空数组，例如插件不提供产物、结果未保留；不能硬编码 `video` 或猜测产物 key。列表含 `key`、`type`、可选 `mime_type`、`content_url`；示例只打印前三项，按列表 key 构造网关内的 Bearer 下载路径，不跟随重定向。

`content_url` 可能含临时签名，应当作临时访问凭据，不记录或拼接 API Key。示例不使用这个 URL；部署有额外 URL 前缀时由 `API_ROOT_URL` 提供。GET 支持整文件，HEAD 只检查可访问性；Range / 条件请求需另按 API 参考验证，HEAD 成功不保证随后下载永远有效。

本例下载最大 20 MiB，只在完整读取后新建文件，拒绝覆盖、空内容、非 200、HTML / JSON 和 MIME 不匹配。因而 JSON 类型的文件产物需要另写明确处理，不适用此媒体下载示例。它不解码验证图片、音频或视频内容，也不自动打开下载结果。

接口：[提交](/zh/api-reference/tasks-submit)、[状态](/zh/api-reference/tasks-retrieve)、[列表](/zh/api-reference/task-artifacts)、[GET](/zh/api-reference/task-artifact-content)、[HEAD](/zh/api-reference/task-artifact-head)。

```javascript theme={null}
import { readFile, writeFile, access } from 'node:fs/promises';
import { request, requestJSON, readBody } from './http-client.mjs';

try {
  const mode = process.argv[2] || 'status';
  if (!['submit', 'status', 'artifacts', 'head', 'download'].includes(mode)) throw new Error('Invalid mode.');
  const task = process.env.API_TASK_ID;
  const idPattern = /^[A-Za-z0-9_-][A-Za-z0-9._~-]{0,127}$/;
  if (mode === 'submit') {
    const plugin = process.env.API_PLUGIN_KEY;
    if (!/^[A-Za-z0-9_-]{1,30}$/.test(plugin || '')) throw new Error('Set API_PLUGIN_KEY.');
    const raw = await readFile(process.env.API_TASK_REQUEST_FILE);
    if (!raw.length || raw.length > 1024 * 1024) throw new Error('Invalid request file size.');
    const body = JSON.parse(raw);
    if (!body || typeof body !== 'object' || Array.isArray(body)
      || typeof body.model !== 'string' || !body.model.trim()) throw new Error('Provide a plugin-specific JSON object with model.');
    const result = await requestJSON(`/v1/tasks/${encodeURIComponent(plugin)}`, body);
    if (typeof result.task_id !== 'string' || !idPattern.test(result.task_id)
      || result.id !== result.task_id || result.status !== 'queued') throw new Error('Invalid submission receipt.');
    console.log(JSON.stringify({ task_id: result.task_id, accepted: true }));
  } else {
    if (!idPattern.test(task || '')) throw new Error('Set API_TASK_ID.');
    const path = `/v1/tasks/${encodeURIComponent(task)}`;
    const result = await requestJSON(path);
    const states = ['NOT_START', 'SUBMITTED', 'QUEUED', 'IN_PROGRESS', 'SUCCESS', 'FAILURE', 'NEEDS_REVIEW', 'UNKNOWN'];
    if (result.task_id !== task || !states.includes(result.status)) throw new Error('Invalid task status.');
    if (mode === 'status') {
      console.log(JSON.stringify({ task_id: task, status: result.status, completed: result.status === 'SUCCESS' }));
    } else {
      if (result.status !== 'SUCCESS') throw new Error('Task is not successful; do not resubmit automatically.');
      const listing = await requestJSON(`${path}/artifacts`);
      if (listing.task_id !== task || !Array.isArray(listing.artifacts) || listing.artifacts.length > 64) throw new Error('Invalid artifacts.');
      const seen = new Set();
      for (const item of listing.artifacts) {
        if (!item || typeof item.key !== 'string' || !idPattern.test(item.key) || seen.has(item.key)
          || !['image', 'audio', 'video', 'file'].includes(item.type) || typeof item.content_url !== 'string' || !item.content_url
          || (item.mime_type !== undefined && (typeof item.mime_type !== 'string' || /[\r\n]/.test(item.mime_type)))) throw new Error('Invalid artifact entry.');
        seen.add(item.key);
      }
      if (mode === 'artifacts') {
        // Avoid logging signed URLs. Use authenticated gateway paths below.
        console.log(JSON.stringify(listing.artifacts.map(({ key, type, mime_type }) => ({ key, type, mime_type }))));
      } else {
        const artifact = listing.artifacts.find((item) => item.key === process.env.API_ARTIFACT_KEY);
        if (!artifact) throw new Error('Choose API_ARTIFACT_KEY from the listing.');
        const output = process.env.API_ARTIFACT_OUTPUT || 'task-artifact.bin';
        if (mode === 'download') {
          try { await access(output); throw new Error('Output already exists.'); }
          catch (error) { if (error.code !== 'ENOENT') throw error; }
        }
        // Never send the API key to a provider URL or a signed content_url.
        const response = await request(`${path}/artifacts/${encodeURIComponent(artifact.key)}/content`,
          { method: mode === 'head' ? 'HEAD' : 'GET' });
        if (response.status !== 200) throw new Error('Expected a complete response, not a partial file.');
        if (mode === 'head') {
          console.log(JSON.stringify({ available: true, content_type: response.headers.get('content-type') }));
        } else {
          const type = (response.headers.get('content-type') || '').split(';')[0].trim();
          if (!type || type === 'text/html' || type === 'application/json' || type.endsWith('+json')
            || (artifact.mime_type && type !== artifact.mime_type.split(';')[0].trim())) throw new Error('Unexpected artifact content type.');
          const bytes = await readBody(response, 20 * 1024 * 1024);
          if (!bytes.length) throw new Error('Empty artifact.');
          await writeFile(output, bytes, { flag: 'wx' });
          console.log('Artifact saved.');
        }
      }
    }
  }
} catch {
  console.error('Task request failed or returned invalid data. No automatic resubmission; check task status.');
  process.exitCode = 1;
}
```

[Read in English](/en/guides/tasks)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.