Skip to content

任务和网络

媒体生成 (视频,图像,音乐) 非同步:你创建一个任务 (工作) 并随时获得结果.有两种方法来确定任务完成了:调查 (投票) 和网页 (推).下面是如何组织任务和如何可靠地获得结果.

任务生命周期

POST /media/generate  →  status: processing  ──►  done   (result_url готов)
        │                                     └─►  failed (error, средства возвращены)
        └── (опционально) callback_url → пуш-вебхук на done/failed
  1. 创建. POST /media/generate 模型和参数 返回 id 和调查链接:
json
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
  "poll": "https://nordrouter.com/media/job/beefb531-…" }
  1. **等待.**任务是通过 processingdone (或 failed). 图像 秒,视频和音乐 从几十秒到几分钟.

  2. **结果.**完成任务中已填写result_url (链接到nordrouter.com);文件只保存有限时间 (retention_daysGET /media/models) 下载才能保存.

答案格式 GET /media/job/:id (和Webhook体) 类似于:

json
{ "id": "beefb531-…", "status": "done",
  "result_url": "https://nordrouter.com/media/file/beefb531-…",
  "cost_usd": 0.052, "error": null }
  • statusprocessing | done | failed.
  • result_url 只有在 done 填写,否则是 null.
  • result_url_2 是音乐模特 (苏诺) 的第二个曲线,否则是 null.
  • cost_usd 实际价值 (按事实计算;失败的发电 不收费 储备被退还).
  • error 错误文字在 failed时,否则是 null.

方法1: 投票

最简单的方法是定期询问GET /media/job/:id,直到status成为done/failed:

bash
curl https://nordrouter.com/media/job/beefb531-…

查询每23秒一次,查询始终有效,不需要公开地址,适合脚本,笔记本电脑,CI.

方法2:Webhuki (推)

为了避免查询,请将callback_url发送到POST /media/generate中,我们将在完成任务时自行发送POST**给您:

bash
curl https://nordrouter.com/media/generate \
  -H "Authorization: Bearer sk-nr-YOUR-KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "image/nano-banana-2",
    "input": { "prompt": "…" },
    "callback_url": "https://your-server.com/webhooks/nordrouter",
    "callback_secret": "любая-ваша-строка"
  }'

如果 callback_url被接受,创建答案将包含 "webhook": "registered". callback_url在创建任务时会被检查**:如果它不合适 (不合 https,私人/本地地地址) 请求将原因带回来 400 问题不会创建.

网的身体

当任务到达done/failed时,你的callback_url将到达POST的体格,与GET /media/job/:id的体格,加上 event的字段.

json
{ "event": "media.completed", "id": "beefb531-…", "status": "done",
  "result_url": "https://nordrouter.com/media/file/beefb531-…",
  "cost_usd": 0.052, "error": null }
  • event media.completed (成功) 或 media.failed (错误).

签名检查

如果您输入了 callback_secret,标题X-NR-Signature包含sha256=<hmac> HMAC-SHA256 来自****查询的原始体**,关键 =您的秘密. 检查它,以确保这是我们的查询,而不是假冒:

python
import hmac, hashlib

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)  # сравнение за константное время
javascript
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

计算HMAC从*字节到JSON解析,否则签名就不会合适.

接收器在10行内就准备好了

接待Webhook的工作服务器检查签名,并回答2xx. 秘密是你在callback_secret发送的秘密.

Node.js / Express:

javascript
import express from "express";
import crypto from "node:crypto";

const SECRET = "любая-ваша-строка";           // = ваш callback_secret
const app = express();

app.post("/webhooks/nordrouter",
  express.raw({ type: "application/json" }),   // сырое тело — обязательно для подписи
  (req, res) => {
    const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
    const got = req.headers["x-nr-signature"] || "";
    if (expected.length !== got.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got)))
      return res.sendStatus(401);             // подпись не сошлась — не наш запрос
    const job = JSON.parse(req.body);
    if (job.event === "media.completed") console.log("готово:", job.result_url, "$" + job.cost_usd);
    res.sendStatus(200);                       // ответьте 2xx, иначе будет повтор
  });

app.listen(3000);

Python / FastAPI:

python
import hmac, hashlib
from fastapi import FastAPI, Request, Response

SECRET = b"любая-ваша-строка"                  # = ваш callback_secret
app = FastAPI()

@app.post("/webhooks/nordrouter")
async def hook(request: Request):
    raw = await request.body()                 # сырые байты — обязательно для подписи
    expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-NR-Signature", "")):
        return Response(status_code=401)       # подпись не сошлась — не наш запрос
    job = await request.json()
    if job["event"] == "media.completed":
        print("готово:", job["result_url"], "$", job["cost_usd"])
    return Response(status_code=200)           # ответьте 2xx, иначе будет повтор

检查本地

callback_url 必须是公开的. https为了在笔记本电脑上开发,请起道,例如. ngrok http 3000 ,并传递给他们发行的 https- 像地址一样 callback_url.

交付和可靠性

  • 直到4次尝试,加剧间歇 (≈3 → 6 → 9 c),每次15 c. 答 2xx,否则会重复.
  • 请直接回复callback_url.
  • 交付 最好的努力:如果所有4次尝试都失败 (你的服务器躺下),Webhook 将以后重新发送.因此:

网络不取代调查.

网络是加速,而不是保证. GET /media/job/:id 作为备用途径:如果按时没有到达,请问自己状态. 权力在您的边缘:相同的任务可以像Webhook一样出现,并被调查发现. id.

要求是: callback_url

  • 只有https://.
  • 只有公共**地址.本地和私人地址 (localhost, *.local, 10.x, 172.16–31.x, 192.168.x, 127.x, 169.254.x, 100.64–127.x, IPv6 loopback/ULA/link-local) 被拒绝 这是SSRF保护.

选择什么

问问 | 网页 | | 问问 网页 | |---|---|---| | 公共 https-地址 | 不需要 | 不需要 需要 | | 获取的延迟 | 在调查间隔之前 |几乎立即 | | 保障 | 你控制了 | best-effort + 调查作为备份 | | 在方便的地方 | 脚本,CI,笔记本电脑 | 服务器,销售背包, |

**建议:**在后台上 webhook + 调查作为保险;在脚本/笔记本电脑上只是调查.