跳转到内容

Relay API 参考

Relay API 允许外部服务将数据推送到与你的 Eidos Space 关联的中转队列中。消息会暂存在云端,等你的 Eidos 桌面版下次上线时自动投递到本地。

API 设计与 Cloudflare Queues 保持一致。

核心原则:没有显式的重试端点 —— 不确认消息 = 自动重试。

https://api.eidos.space/v1/relay/channels/{channelId}/messages

所有请求必须携带 Relay API Token,在 Eidos.space 控制台 生成。

Authorization: Bearer {token}

将一条消息推送到中转队列。

端点: POST /

Terminal window
curl -X POST https://api.eidos.space/v1/relay/channels/{channelId}/messages \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"body": { "event": "order.created", "orderId": "123" }
}'

请求体字段:

字段类型必填说明
bodyany消息体,可以是任意可序列化的 JSON 值
content_typestring"json"(默认)、"text""bytes""v8"
delay_secondsnumber消息被投递前的延迟秒数
metadataobject附加到消息上的自定义键值元数据

响应:

{
"success": true,
"errors": [],
"messages": [],
"result": {
"id": "550e8400-e29b-41d4-a716-446655440000"
}
}

在一次请求中推送多条消息。

端点: POST /batch

Terminal window
curl -X POST https://api.eidos.space/v1/relay/channels/{channelId}/messages/batch \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "body": { "event": "user.signup", "id": 1 } },
{ "body": { "event": "user.signup", "id": 2 } }
]
}'

请求体字段:

字段类型必填说明
messagesarray消息对象数组,每条消息的结构与单条发送相同
delay_secondsnumber应用于所有未单独指定延迟的消息的默认延迟

响应:

{
"success": true,
"errors": [],
"messages": [],
"result": {
"ids": [
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
]
}
}

从中转队列中取回待处理的消息。Eidos 桌面版会在内部自动调用此接口,你也可以直接调用来构建自定义消费者。

端点: POST /pull

Terminal window
curl -X POST https://api.eidos.space/v1/relay/channels/{channelId}/messages/pull \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"batch_size": 10,
"visibility_timeout_ms": 30000
}'

请求体字段:

字段类型必填说明
visibility_timeout_msnumber消息被拉取后,在队列中对其他消费者隐藏的时长(毫秒)。默认:30000
batch_sizenumber单次拉取的消息数量。默认:10,最大:100

响应:

{
"success": true,
"errors": [],
"messages": [],
"result": {
"message_backlog_count": 42,
"messages": [
{
"body": { "event": "user.created" },
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp_ms": 1689615013586,
"attempts": 1,
"metadata": {
"key": "value"
},
"lease_id": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..."
}
]
}
}

消息字段:

字段类型说明
bodyany消息体
idstring唯一消息 ID
timestamp_msnumber发布时间戳(毫秒)
attemptsnumber投递尝试次数
metadataobject自定义元数据
lease_idstring用于确认/重试的租约 ID

确认消息已成功处理。被确认的消息将从云端永久删除。

端点: POST /ack

Terminal window
curl -X POST https://api.eidos.space/v1/relay/channels/{channelId}/messages/ack \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"lease_ids": ["LEASE_ID_1", "LEASE_ID_2"]
}'

请求体字段:

字段类型必填说明
lease_idsstring[]拉取接口返回的 lease_id 数组

响应:

{
"success": true,
"errors": [],
"messages": [],
"result": {
"acked_count": 1
}
}

通过 WebSocket 连接接收中继中有新消息时的实时通知。这消除了轮询的需要。

端点: GET /subscribe

GET https://api.eidos.space/v1/relay/channels/{channelId}/messages/subscribe
Upgrade: websocket
Authorization: Bearer {token}

WebSocket 事件:

连接时,你会收到欢迎事件:

{ "type": "subscribed", "channelId": "your-channel" }

当有人向此中继发送消息时,你会收到:

{ "type": "new_message", "channelId": "your-channel" }

通过 WebSocket 连接同时接收多个频道的实时通知。

端点: GET /v1/relay/subscribe?channels={channelId1},{channelId2}

GET https://api.eidos.space/v1/relay/subscribe?channels={channelId1},{channelId2}
Upgrade: websocket
Authorization: Bearer {token}

WebSocket 事件:

连接时,你会收到欢迎事件:

{ "type": "subscribed", "channels": ["channelId1", "channelId2"] }

当有人向其中任一频道发送消息时,你会收到与单频道订阅相同的通知,其中包含 channelId 以便你知道哪个频道有新消息:

{ "type": "new_message", "channelId": "channelId1" }

┌──────────┐ send ┌──────────┐ pull ┌──────────┐
│ 生产者 │ ─────────── │ Relay │ ─────────── │ 消费者 │
└──────────┘ └──────────┘ └────┬─────┘
┌──────────────────────────────────┤
│ ack(在 visibility_timeout 内) │
▼ │
┌──────────┐ │
│ 已删除 │ │
└──────────┘ │
┌──────────────────────────────────┘
│ 未 ack / 超时
┌──────────┐
│ 重新入队 │ (attempts++)
└────┬─────┘
│ attempts < maxRetries
└──────────────────────┐
┌──────────┐
│ 死信队列 │ (可选)
└──────────┘

拉取的消息如果未在 visibility_timeout_ms 内被确认,会自动重新进入队列供下次拉取。消息的 attempts 计数器会递增。当 attempts 达到 max_retries 时,消息会被丢弃或发送到死信队列。

这确保了”至少一次投递”的语义,无需单独的重试端点。


所有错误都遵循以下格式:

{
"success": false,
"errors": [
{
"code": 400,
"message": "Missing required field: body"
}
],
"messages": [],
"result": null
}

// 1. 发送消息
const sendRes = await fetch("/v1/relay/channels/my-channel/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
body: { event: "order.created", orderId: "123" },
}),
});
const { result: { id } } = await sendRes.json();
// 2. 拉取消息
const pullRes = await fetch("/v1/relay/channels/my-channel/messages/pull", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
batch_size: 10,
visibility_timeout_ms: 30000,
}),
});
const { result: { messages } } = await pullRes.json();
// 3. 处理消息
for (const msg of messages) {
await processMessage(msg.body);
}
// 4. 确认所有消息
await fetch("/v1/relay/channels/my-channel/messages/ack", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
lease_ids: messages.map((m) => m.lease_id),
}),
});
const res = await fetch("/v1/relay/channels/my-channel/messages/batch", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: [
{ body: { type: "email", to: "user1@example.com" } },
{ body: { type: "email", to: "user2@example.com" } },
{ body: { type: "email", to: "user3@example.com" } },
],
}),
});

限制说明
delay_seconds最大 86400 (24 小时)消息可被消费前的最大延迟时间
visibility_timeout_ms最大 43200000 (12 小时)消息被拉取后对其他消费者隐藏的最大时间
batch_size最大 100单次拉取请求的最大消息数量
消息大小128 KB单条消息的最大大小(body + metadata)

| 最大重试次数 | 3 | 消息被丢弃前的最大投递尝试次数 | | 消息保留时间 | 14 天 | 消息在队列中保留的最长时间 |

功能免费版Spark 版
最大存储10 MB100 MB
最大频道数5100
最大消息大小128 KB128 KB
消息保留时间3 天14 天
参数默认值说明
visibility_timeout_ms30000 (30 秒)消息未被确认前重新可见的时间
batch_size10单次拉取请求返回的消息数量
content_type”json”消息的默认内容类型
  • 单用户最大存储: 100 MB(所有频道)
  • 最大消息大小: 1 MB(SQLite 保护限制)
  • 单用户最大频道数: 100(Spark 版)