{"openapi":"3.0.0","info":{"title":"qx-worker API","version":"1.0.0"},"tags":[{"name":"scripts","description":"同步脚本注册与元数据"},{"name":"tasks","description":"任务 CRUD、启停、手动触发与同步状态"},{"name":"deliveries","description":"Webhook 持久化投递与补偿重放"}],"components":{"schemas":{"ScriptMeta":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"idField":{"type":"string"},"noiseKeys":{"type":"array","items":{"type":"string"}},"authFields":{"type":"array","items":{"$ref":"#/components/schemas/FieldDef"}},"paramFields":{"type":"array","items":{"$ref":"#/components/schemas/FieldDef"}}},"required":["name","description","idField","authFields","paramFields"]},"FieldDef":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"required":{"type":"boolean"},"default":{"nullable":true}},"required":["key","label","required"]},"ErrorJson":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"Task":{"type":"object","properties":{"id":{"type":"integer"},"scriptName":{"type":"string"},"name":{"type":"string"},"auth":{"type":"object","additionalProperties":{"nullable":true}},"params":{"type":"object","additionalProperties":{"nullable":true}},"webhookTarget":{"type":"string"},"cron":{"type":"string"},"enabled":{"type":"boolean"},"lastSuccessfulSyncAt":{"anyOf":[{"type":"string"},{"nullable":true}]},"createdAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]}},"required":["id","scriptName","name","auth","params","webhookTarget","cron","enabled","lastSuccessfulSyncAt","createdAt","updatedAt"]},"ValidationErrorJson":{"type":"object","properties":{"error":{"nullable":true}}},"CreateTaskBody":{"type":"object","properties":{"scriptName":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"auth":{"type":"object","additionalProperties":{"nullable":true},"default":{}},"params":{"type":"object","additionalProperties":{"nullable":true},"default":{}},"webhookTarget":{"type":"string","format":"uri"},"cron":{"type":"string","default":"*/10 * * * *"},"enabled":{"type":"boolean","default":true}},"required":["scriptName","name","webhookTarget"]},"SyncRun":{"type":"object","properties":{"id":{"type":"integer"},"taskId":{"type":"integer","nullable":true},"startedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]},"finishedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"pagesFetched":{"type":"integer"},"inserted":{"type":"integer"},"updated":{"type":"integer"},"failed":{"type":"integer"},"note":{"type":"string","nullable":true},"lastError":{"type":"string","nullable":true}},"required":["id","taskId","startedAt","finishedAt","pagesFetched","inserted","updated","failed","note","lastError"]},"UpdateTaskBody":{"type":"object","properties":{"name":{"type":"string","minLength":1},"auth":{"type":"object","additionalProperties":{"nullable":true}},"params":{"type":"object","additionalProperties":{"nullable":true}},"webhookTarget":{"type":"string","format":"uri"},"cron":{"type":"string"},"enabled":{"type":"boolean"}}},"WebhookLog":{"type":"object","properties":{"id":{"type":"integer"},"taskId":{"type":"integer","nullable":true},"orderId":{"type":"integer","nullable":true},"externalId":{"type":"string"},"url":{"type":"string"},"status":{"type":"string"},"httpStatus":{"type":"integer","nullable":true},"attempt":{"type":"integer"},"responseBody":{"type":"string","nullable":true},"error":{"type":"string","nullable":true},"durationMs":{"type":"integer","nullable":true},"createdAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]}},"required":["id","taskId","orderId","externalId","url","status","httpStatus","attempt","responseBody","error","durationMs","createdAt"]},"OrderListItem":{"type":"object","properties":{"id":{"type":"integer"},"taskId":{"type":"integer","nullable":true},"externalId":{"type":"string"},"dataHash":{"type":"string"},"version":{"type":"integer"},"createdAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]}},"required":["id","taskId","externalId","dataHash","version","createdAt","updatedAt"]},"Order":{"type":"object","properties":{"id":{"type":"integer"},"taskId":{"type":"integer","nullable":true},"externalId":{"type":"string"},"payload":{"type":"string"},"dataHash":{"type":"string"},"version":{"type":"integer"},"createdAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]}},"required":["id","taskId","externalId","payload","dataHash","version","createdAt","updatedAt"]},"PushOrderWebhookBody":{"type":"object","properties":{"kind":{"type":"string","enum":["insert","update"]}},"default":{}},"WebhookDeliveryListItem":{"type":"object","properties":{"id":{"type":"integer"},"taskId":{"type":"integer"},"externalId":{"type":"string"},"kind":{"type":"string"},"url":{"type":"string"},"status":{"type":"string"},"sweepAttempts":{"type":"integer"},"lastHttpStatus":{"type":"integer","nullable":true},"lastError":{"type":"string","nullable":true},"firstFailedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"lastAttemptAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"nextRetryAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"deliveredAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"createdAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]}},"required":["id","taskId","externalId","kind","url","status","sweepAttempts","lastHttpStatus","lastError","firstFailedAt","lastAttemptAt","nextRetryAt","deliveredAt","createdAt","updatedAt"]},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"integer"},"taskId":{"type":"integer"},"externalId":{"type":"string"},"kind":{"type":"string"},"url":{"type":"string"},"payload":{"type":"string"},"status":{"type":"string"},"sweepAttempts":{"type":"integer"},"lastHttpStatus":{"type":"integer","nullable":true},"lastError":{"type":"string","nullable":true},"firstFailedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"lastAttemptAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"nextRetryAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"deliveredAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"},{"nullable":true}]},"createdAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string","format":"date-time"}]}},"required":["id","taskId","externalId","kind","url","payload","status","sweepAttempts","lastHttpStatus","lastError","firstFailedAt","lastAttemptAt","nextRetryAt","deliveredAt","createdAt","updatedAt"]},"RetryDeliveryBody":{"type":"object","properties":{"force":{"type":"boolean"}},"default":{}}},"parameters":{}},"paths":{"/api/scripts":{"get":{"tags":["scripts"],"summary":"列出已注册的同步脚本","responses":{"200":{"description":"脚本 meta 列表","content":{"application/json":{"schema":{"type":"object","properties":{"scripts":{"type":"array","items":{"$ref":"#/components/schemas/ScriptMeta"}}},"required":["scripts"]}}}}}}},"/api/scripts/{name}":{"get":{"tags":["scripts"],"summary":"按名称获取单个脚本元数据","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"name","in":"path"}],"responses":{"200":{"description":"脚本 meta","content":{"application/json":{"schema":{"type":"object","properties":{"script":{"$ref":"#/components/schemas/ScriptMeta"}},"required":["script"]}}}},"404":{"description":"未找到脚本","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks":{"post":{"tags":["tasks"],"summary":"创建或更新任务（upsert）","description":"按 `(scriptName, name)` 唯一键创建或更新同步任务；已存在则更新 `auth`/`params` 等字段。`enabled` 与 `cron` 变化会同步到调度器。","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskBody"}}}},"responses":{"200":{"description":"已更新（同 scriptName + name 的任务已存在）","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"201":{"description":"已创建","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"参数或脚本无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorJson"}}}}}},"get":{"tags":["tasks"],"summary":"列出任务","parameters":[{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"},{"schema":{"type":"string","nullable":true},"required":false,"name":"q","in":"query"}],"responses":{"200":{"description":"任务列表","content":{"application/json":{"schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["tasks","total","page","pageSize"]}}}}}}},"/api/tasks/{id}":{"get":{"tags":["tasks"],"summary":"获取任务详情","description":"含最近运行记录与 24h Webhook 统计。","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"任务详情","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"},"recentRuns":{"type":"array","items":{"$ref":"#/components/schemas/SyncRun"}},"webhook24h":{"type":"object","properties":{"success":{"type":"integer"},"failed":{"type":"integer"}},"required":["success","failed"]}},"required":["task","recentRuns","webhook24h"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}},"put":{"tags":["tasks"],"summary":"更新任务","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaskBody"}}}},"responses":{"200":{"description":"已更新","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"参数无效","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}},"delete":{"tags":["tasks"],"summary":"删除任务","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"已删除","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/trigger":{"post":{"tags":["tasks"],"summary":"手动触发一次同步","description":"向同步队列投递一页同步任务，返回 `runId`。","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"已入队","content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"integer"}},"required":["runId"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/start":{"post":{"tags":["tasks"],"summary":"启动任务（启用定时同步）","description":"将 `enabled` 设为 true 并注册 Cron，与在 UI 中「启用」等价。","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"已启用","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/pause":{"post":{"tags":["tasks"],"summary":"暂停任务（停用定时同步）","description":"将 `enabled` 设为 false 并停止 Cron；已在队列中的同步作业仍可能被 worker 消费。","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"已暂停","content":{"application/json":{"schema":{"type":"object","properties":{"task":{"$ref":"#/components/schemas/Task"}},"required":["task"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/sync-status":{"get":{"tags":["tasks"],"summary":"任务同步状态","description":"返回 Cron 是否已挂载、上次成功同步时间、进行中的 Run、同步队列中与本任务相关的 job 数量等。","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"同步状态","content":{"application/json":{"schema":{"type":"object","properties":{"taskId":{"type":"integer"},"enabled":{"type":"boolean"},"cron":{"type":"string"},"cronScheduled":{"type":"boolean"},"lastSuccessfulSyncAt":{"anyOf":[{"type":"string"},{"nullable":true}]},"activeRun":{"allOf":[{"$ref":"#/components/schemas/SyncRun"},{"nullable":true}]},"queue":{"type":"object","properties":{"waiting":{"type":"integer"},"active":{"type":"integer"},"delayed":{"type":"integer"}},"required":["waiting","active","delayed"]},"recentRuns":{"type":"array","items":{"$ref":"#/components/schemas/SyncRun"}}},"required":["taskId","enabled","cron","cronScheduled","lastSuccessfulSyncAt","activeRun","queue","recentRuns"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/runs":{"get":{"tags":["tasks"],"summary":"任务同步运行记录","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"运行列表","content":{"application/json":{"schema":{"type":"object","properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/SyncRun"}}},"required":["runs"]}}}}}}},"/api/tasks/{id}/logs":{"get":{"tags":["tasks"],"summary":"任务相关 Webhook 日志","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"日志列表","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"$ref":"#/components/schemas/WebhookLog"}}},"required":["logs"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/orders":{"get":{"tags":["tasks"],"summary":"任务关联同步订单分页列表（不含 payload）","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"订单列表","content":{"application/json":{"schema":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/OrderListItem"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["orders","total","page","pageSize"]}}}},"404":{"description":"任务未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/orders/{orderId}":{"get":{"tags":["tasks"],"summary":"任务下单条订单详情（含 payload）","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"orderId","in":"path"}],"responses":{"200":{"description":"订单","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"}},"required":["order"]}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/orders/{orderId}/push-webhook":{"post":{"tags":["tasks"],"summary":"按订单 payload 强制入队 Webhook（调试；默认 kind=update）","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"orderId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushOrderWebhookBody"}}}},"responses":{"200":{"description":"已入队","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"无法入队","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"任务或订单未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/deliveries":{"get":{"tags":["deliveries"],"summary":"Webhook 持久化投递列表（分页，不含 payload）","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":false,"name":"taskId","in":"query"},{"schema":{"type":"string","enum":["pending","success","dead"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1},"required":false,"name":"page","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"pageSize","in":"query"}],"responses":{"200":{"description":"列表","content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryListItem"}},"total":{"type":"integer"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["deliveries","total","page","pageSize"]}}}}}}},"/api/deliveries/{id}":{"get":{"tags":["deliveries"],"summary":"单条投递详情（含 payload JSON 字符串）","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"详情","content":{"application/json":{"schema":{"type":"object","properties":{"delivery":{"$ref":"#/components/schemas/WebhookDelivery"}},"required":["delivery"]}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/deliveries/{id}/retry":{"post":{"tags":["deliveries"],"summary":"单条重放（不增加 sweepAttempts）；body.force=true 时可重发已 success","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetryDeliveryBody"}}}},"responses":{"200":{"description":"已入队","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"400":{"description":"不可重放","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}},"/api/tasks/{id}/deliveries/retry-failed":{"post":{"tags":["deliveries"],"summary":"批量重放该任务下 pending 或 dead 的投递","parameters":[{"schema":{"type":"string","pattern":"^\\d+$"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"已尝试入队","content":{"application/json":{"schema":{"type":"object","properties":{"retried":{"type":"integer"},"failed":{"type":"integer"}},"required":["retried","failed"]}}}},"400":{"description":"无效 id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}},"404":{"description":"任务未找到","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorJson"}}}}}}}}}