放单 API / Publishing API
返回放单平台 / Publisher只需放单卡密,无需注册、登录或生成密钥。支持卡密查询、呼叫接单人、快捷聊天、指定放单、进度查询、撤回与删除。
Use a publishing CDK, without an account or API key. Check credits, call takers, exchange quick messages, publish to selected takers, track, withdraw and delete orders.
认证与流程 / Authentication & Workflow
Base URL: https://chataiapi.online/fandan/api/v1 X-Fandan-CDK: YOUR_PUBLISHING_CDK Content-Type: application/json
所有接口使用同一请求头。POST 也兼容 JSON 中的 ownerCdk;同时传入时必须一致。GET 不接受 URL 中的卡密。次数用完仍可查询、呼叫和聊天,但不能创建新订单。被删除或类型错误的卡密无效。
Use the same header for all endpoints. POST also accepts ownerCdk in JSON; if both are provided they must match. Never pass CDKs in URLs. Exhausted cards can still query, call and chat, but cannot create new orders. Deleted cards and non-publishing cards are rejected.
- 查询
/cdk和/takers,选好接单人。 / Check credits and available takers. - 调用
/calls通知指定接单人,再通过/chat确认对方在线并可以接单。 / Call selected takers and confirm readiness in chat. - 生成支付长链,提交
/orders,可指定一个或多个接单人。 / Generate the payment link and publish, optionally targeting one or more takers. - 每 3–5 秒查询订单进度,完成或失败后停止轮询。 / Poll every 3–5 seconds until a terminal state.
在线不代表空闲。选择多人仍只创建一笔订单,先领取者处理。接口不会生成支付长链或二维码,需要自行准备支付长链。
Online does not mean available. Selecting multiple takers creates one order, claimed by the first eligible taker. This API does not generate payment links or QR codes; supply your own payment link.
接口一览 / Endpoints
| Method | Path | 用途 / Purpose |
|---|---|---|
| GET | /cdk | 剩余次数、已用次数 / Remaining and used credits |
| GET | /takers?page=1 | 已开通权限的接单人 ID、脱敏名称、在线与呼叫状态 / Eligible taker IDs, masked names, presence and call availability |
| GET | /online-takers | 兼容旧版在线人数统计 / Legacy online account count |
| POST | /orders | 创建订单 / Publish |
| GET | /orders | 本卡密订单,支持 page、status、requestId / Card-scoped orders with filters |
| GET | /orders/{id} | 查询一笔订单 / Order detail |
| POST | /orders/{id}/withdraw | 撤回待接订单并退回一次 / Withdraw an unclaimed order and refund one credit |
| POST | /orders/{id}/delete | 删除已撤回、完成或失败的记录 / Delete a withdrawn, completed or failed record |
| GET | /calls | 机器人状态、可呼叫名单、冷却与最近一次 API 呼叫 / Bot state, recipients, cooldown and latest API call |
| POST | /calls | 呼叫指定 Telegram 用户 / Notify selected Telegram recipients |
| GET | /calls/{id} | 查询本卡密 API 呼叫结果 / Card-scoped API call result |
| GET | /chat?after=MESSAGE_ID | 公共快捷聊天与可发送选项 / Shared chat and available message kinds |
| POST | /chat | 发送放单端快捷消息 / Send a publisher quick message |
订单和接单人列表固定每页 50 条,page 从 1 开始,返回 total、page、pageSize。所有时间为 Unix 毫秒。接单人 ID 与呼叫 recipientIds、放单 targetTakerIds 通用。
Orders and takers use pages of 50, starting at page 1, with total/page/pageSize. All timestamps are Unix milliseconds. Taker IDs are shared by call recipientIds and order targetTakerIds.
{"cdk":{"role":"owner","remaining":20,"used":3,"canPublish":true},"updatedAt":1800000000000}
/takers 返回 id, name, online, canTakeOrders, canReceiveCall, callCooldownUntil。在线状态以最近 15 秒心跳为准;/online-takers 保留旧版行为,包含尚未开通接单权限的在线接单账号,因此可能与可接单名单数量不同。
Takers include id, name, online, canTakeOrders, canReceiveCall and callCooldownUntil. Presence uses the last 15 seconds of heartbeats. The legacy online-takers count includes online taker accounts without claim permission, so it may differ from the eligible list.
发布与管理订单 / Orders
订单发布后开始 5 分钟倒计时。超过 claimDeadlineAt 仍无人接单,系统自动撤销并保留失败记录:status=failed、failureReason=unclaimed_timeout,expiredAt 为超时时间。正常退次后 refunded=true。已接单的订单继续原倒计时,到时自动走原提交检测和结算流程,并非直接成功。autoSubmitStartedAt、autoSubmitFinishedAt 和 autoSubmitErrorCode 记录自动提交结果。重试同一 requestId 不会重新计时或重新放单。
A 5-minute countdown starts at publication. At claimDeadlineAt, unclaimed orders are cancelled and retained as failed records with failureReason=unclaimed_timeout and expiredAt set. After the credit is refunded, refunded=true. Claimed orders keep the original deadline and are automatically submitted through the existing checks and settlement; success is not assumed. autoSubmitStartedAt, autoSubmitFinishedAt and autoSubmitErrorCode record the attempt. Retrying the same requestId never resets the timer or republishes an order.
POST /orders
{
"requestId": "business-order-001",
"paymentUrl": "https://payments.stripe.com/YOUR_PAYMENT_LINK",
"accessToken": "YOUR_ACCOUNT_ACCESS_TOKEN",
"targetTakerIds": ["TAKER_ID_A", "TAKER_ID_B"]
}
requestId 必填,1–64 位字母、数字、下划线或短横线。paymentUrl 和 accessToken 为必填字符串。省略 targetTakerIds 或传 null 表示全部接单人;空数组无效。指定的人必须已开通接单权限。
requestId is required: 1–64 letters, digits, underscores or hyphens. paymentUrl and accessToken are required strings. Omit targetTakerIds or use null for all takers; an empty array is invalid. Selected accounts must have claim permission.
{
"order": {
"id": "ORDER_ID", "status": "open", "publishRequestId": "business-order-001",
"credentialEmail": "example@example.com", "targetTakerIds": ["TAKER_ID_A", "TAKER_ID_B"],
"targetTakerNames": ["ali**", "bob*"], "takerName": null,
"createdAt": 1800000000000, "updatedAt": 1800000000000,
"claimDeadlineAt": 1800000300000, "expiredAt": null,
"claimedAt": null, "submittedAt": null, "completedAt": null,
"failedAt": null, "withdrawnAt": null, "failureReason": null,
"refunded": false, "refundedAt": null, "plusCheck": null,
"canWithdraw": true, "canDelete": false
},
"cdk": {"role":"owner","remaining":19,"used":4,"canPublish":true},
"replayed": false
}
首次成功 201,扣一次。相同卡密、requestId 和内容重试返回 200 / replayed=true,不重复创建或扣次;目标 ID 的顺序和重复项不影响判断。换内容返回 409,原订单已删除返回 410。旧版不带 targetTakerIds 的请求继续兼容。
First creation returns 201 and consumes one credit. Identical retries with the same card/requestId return 200 and replayed=true without charging again. Target order and duplicates are ignored. Different content returns 409; a deleted original returns 410. Legacy requests without targetTakerIds remain compatible.
查询只返回本卡密的网页及 API 订单,不返回 Access Token、原始卡密或支付长链。邮箱无法从 Token 提取时可能为空,邮箱字段不是身份验证结果。plusCheck 只包含 ok、isPlus、checkedAt、errorCode,可能为 null。
Queries return website and API orders for this card only, without access tokens, raw CDKs or payment links. Email can be empty and is not proof of identity. plusCheck contains only ok/isPlus/checkedAt/errorCode and may be null.
| Status | 含义 / Meaning |
|---|---|
| open | 待接单 / Open |
| claimed | 已接单、待提交 / Claimed |
| submitted | 已提交、待确认 / Submitted |
| completed | 已完成 / Completed |
| failed | 失败,参见 failureReason、refunded / Failed; check reason and refund fields |
| withdrawn | 已撤回 / Withdrawn |
GET /orders?page=1&status=open
GET /orders?requestId=business-order-001
GET /orders/ORDER_ID
POST /orders/ORDER_ID/withdraw {}
POST /orders/ORDER_ID/delete {}
撤回仅限未领取订单,成功退回一次;重复撤回返回当前结果,不重复退款。待接订单须先撤回再删除,处理中不能删除。重复 API 删除返回 ok=true / replayed=true;删除不再退次。订单发布 requestId 即使删除后也不能复用。
Only unclaimed orders can be withdrawn; retries never refund twice. Withdraw open orders before deletion. In-progress orders cannot be deleted. Repeating a successful API deletion returns ok=true/replayed=true and does not refund. A publication requestId cannot be reused after deletion.
呼叫接单人 / Call Takers
POST /calls
{"requestId":"call-001","recipientIds":["TAKER_ID_A"]}
HTTP 202
{"job":{"id":"JOB_ID","createdAt":1800000000000,"mode":"live",
"total":1,"pending":1,"sent":0,"failed":0,"skipped":0,"unknown":0},"replayed":false}
GET /calls/JOB_ID
必须明确选择一位或多位接单人,不会默认广播。呼叫不扣次数,不要求先有订单。接单人须绑定 Telegram 并开启接收。调用者和接收人均有 30 秒冷却;接口调用者按卡密计。202 仅代表排队成功,sent 表示 Telegram 接受发送,不代表已读或已上线。
Select explicit recipients; there is no default broadcast. Calls cost no credits and do not require an order. Recipients must link Telegram and enable notifications. Callers and recipients have 60-second cooldowns; API callers are scoped by card. 202 means queued. sent means Telegram accepted delivery, not that the taker read it or came online.
相同 requestId 和名单重试不会重复通知,返回当前 job。换名单为 409。skipped 代表发送前失效、解绑或暂停;unknown 表示发送结果不确定,不自动补发。调用记录保留最近 100 项及仍在队列中的任务(全站共享保留窗口);记录过期返回 404,过期任务的幂等重试返回 410。卡密删除后,尚未发送的通知会跳过。
Identical retries return the current job without notifying again; a changed list returns 409. skipped means eligibility/binding changed before delivery. unknown is an uncertain delivery and is not automatically resent. The system retains the latest 100 jobs plus queued work globally; expired history returns 404 and its idempotent retry returns 410. Deleting a card cancels its unsent API notifications.
快捷聊天 / Quick Chat
GET /chat
POST /chat
{"requestId":"message-001","kind":"generating_qr"}
GET /chat?after=LAST_MESSAGE_ID
| kind | 中文 / English |
|---|---|
| ask | 你现在可以接单吗? / Can you take an order now? |
| generating_qr | 我正在生成二维码,请不要走开。 / I am generating the QR code. Please stay online. |
| publishing | 好的,我现在放单,请准备接单。 / I will publish now. Please be ready to claim the order. |
| thanks | 收到,谢谢。 / Got it, thank you. |
只支持 options 中的快捷消息,不支持自定义文本。接单人的“好的,我等待你生成二维码”等回复会出现在 messages 中。每条 text 包含 zh/en,由调用方选择显示。聊天为公共频道,不是指定接单人的私聊,不能通过 senderName 确定接单人身份;请勿自动把任意公共回复当作指定用户的确认。
Only predefined options are accepted. Taker replies, including waiting-for-QR, appear in messages. Each text includes zh/en. Chat is public, not private to targeted takers, and masked senderName does not establish identity. Do not treat arbitrary public replies as confirmation from a particular taker.
首次发送 201,相同 requestId 与 kind 重试 200,不重复发言。每张卡密新消息间隔至少 2 秒。GET 返回 messages、cursor、reset、options。仅保留最近 10 条内存消息,服务重启会清空。after 已过期时 reset=true,返回当前窗口。重复请求的成功响应不代表消息仍在公共窗口内。
New messages return 201; identical requestId/kind retries return 200 without resending. New messages need a 2-second interval per card. GET returns messages/cursor/reset/options. Only the last 10 messages are held in memory and restart clears them. An expired after cursor returns reset=true with the current window. A successful replay does not imply the message is still in that window.
调用示例 / Examples
curl 'https://chataiapi.online/fandan/api/v1/cdk' \
-H "X-Fandan-CDK: $FANDAN_OWNER_CDK"
curl 'https://chataiapi.online/fandan/api/v1/orders' \
-H "X-Fandan-CDK: $FANDAN_OWNER_CDK" \
-H 'Content-Type: application/json' \
--data '{"requestId":"order-001","paymentUrl":"https://payments.stripe.com/YOUR_LINK","accessToken":"YOUR_TOKEN"}'
Python 客户端仅使用标准库。request_id 应保存为业务订单的稳定编号,网络超时后使用原编号和原内容重试,不要每次生成新编号。批量放单可循环调用,每笔使用不同编号。
The Python client uses only the standard library. Persist a stable business request_id and reuse it with identical content after timeouts. Never generate a new ID for a retry. For batches, loop over orders with a distinct ID for each.
import os
from fandan_publish_client import PublisherClient, PublishAPIError
client = PublisherClient(os.environ["FANDAN_OWNER_CDK"])
print(client.cdk_status())
takers = client.takers()["takers"]
# Choose IDs explicitly; do not broadcast automatically.
chosen = ["TAKER_ID_A"]
# Optional: client.call("call-business-001", chosen)
# Optional: client.send_chat("msg-business-001", "generating_qr")
try:
result = client.publish(
"business-order-001", os.environ["PAYMENT_URL"],
os.environ["ACCOUNT_ACCESS_TOKEN"], target_taker_ids=chosen,
)
print(client.order(result["order"]["id"])["order"]["status"])
except PublishAPIError as error:
print(error.status, error.code)
错误与重试 / Errors & Retries
{"error":"Human-readable explanation","errorCode":"requestConflict"}
| HTTP | 处理 / Handling |
|---|---|
| 400 | 参数、目标接单人、消息类型或卡密头/正文不一致;修正后重试。 / Invalid payload/targets/message kind or mismatched CDKs; correct the request. |
| 401 | 卡密缺失、已删除或非放单卡密。 / Invalid publishing CDK. |
| 403 | 新订单卡密次数不足或操作无权限。 / Insufficient credits or forbidden operation. |
| 404 | 资源不存在、不属于本卡密或呼叫历史过期。 / Missing, foreign or expired resource. |
| 409 | requestId 内容冲突、订单状态不允许或接收人不可用。 / Request conflict, invalid order state or unavailable recipient. |
| 410 | 原订单/任务记录已删除,原 requestId 不能复用。 / Original record removed; requestId is reserved. |
| 429 | 呼叫冷却、队列满或聊天过快;按冷却状态等待。 / Cooldown, queue capacity or chat rate limit; wait. |
| 500–504 | 服务异常;退避后使用相同 requestId/内容重试。 / Service/transport failure; back off and retry the same ID/content. |
常用 errorCode:invalidCdk、cdkMismatch、invalidPayload、invalidOrderTargets、invalidStatus、invalidPage、requestConflict、originalRemoved、cdkExhausted、orderNotFound、orderStateConflict、withdrawFailed、telegramInvalidRecipient、telegramRecipientUnavailable、telegramUnavailable、callRateLimited、callNotFound、invalidChatKind、chatRateLimited。旧字段与旧路径保留,新字段为增量添加;客户端应忽略未知字段。当前采用轮询,不提供 webhook 或批量原子提交。
Existing paths and fields remain compatible; new fields are additive. Ignore unknown fields. Use polling; webhooks and atomic bulk creation are not provided. Automatic client transport retries cover network errors and HTTP 502/503/504, never 4xx.