拉取 Outbox
curl --request GET \
--url https://api.dev.akria.net/api/v1/outbox/{channel} \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.dev.akria.net/api/v1/outbox/{channel}"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.dev.akria.net/api/v1/outbox/{channel}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dev.akria.net/api/v1/outbox/{channel}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.dev.akria.net/api/v1/outbox/{channel}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.dev.akria.net/api/v1/outbox/{channel}")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dev.akria.net/api/v1/outbox/{channel}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"items": [
{
"id": 1,
"signal_id": "550e8400-e29b-41d4-a716-446655440000",
"channel": "ghost",
"payload": {
"title": "AI 板块出现集中放量",
"html": "<p><strong>⚠️ AI 板块出现集中放量</strong></p>...",
"status": "draft",
"tags": ["signal", "high-priority"],
"meta_title": "AI 板块出现集中放量",
"custom_excerpt": "多个AI相关代币在5分钟内成交量激增",
"meta_description": "多个AI相关代币在5分钟内成交量激增"
},
"lease_until": "2026-02-02T10:01:05.123456Z",
"lease_owner": "worker-abc123"
}
],
"count": 1
}
Core API V1
GET 拉取 Outbox
下游服务拉取待投递内容,使用租约机制防止重复领取
GET
/
api
/
v1
/
outbox
/
{channel}
拉取 Outbox
curl --request GET \
--url https://api.dev.akria.net/api/v1/outbox/{channel} \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.dev.akria.net/api/v1/outbox/{channel}"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.dev.akria.net/api/v1/outbox/{channel}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.dev.akria.net/api/v1/outbox/{channel}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.dev.akria.net/api/v1/outbox/{channel}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.dev.akria.net/api/v1/outbox/{channel}")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.dev.akria.net/api/v1/outbox/{channel}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"items": [
{
"id": 1,
"signal_id": "550e8400-e29b-41d4-a716-446655440000",
"channel": "ghost",
"payload": {
"title": "AI 板块出现集中放量",
"html": "<p><strong>⚠️ AI 板块出现集中放量</strong></p>...",
"status": "draft",
"tags": ["signal", "high-priority"],
"meta_title": "AI 板块出现集中放量",
"custom_excerpt": "多个AI相关代币在5分钟内成交量激增",
"meta_description": "多个AI相关代币在5分钟内成交量激增"
},
"lease_until": "2026-02-02T10:01:05.123456Z",
"lease_owner": "worker-abc123"
}
],
"count": 1
}
下游服务(Ghost / Rocket.Chat)拉取待投递内容。
核心特性
租约机制
自动标记为
leased,防止重复领取并发安全
使用
SELECT ... FOR UPDATE SKIP LOCKED 确保并发安全租约超时
过期的
leased 项可以重新拉取预格式化 Payload
直接可用于投递,无需额外处理
认证
此端点不需要 API Key 认证,可以直接访问。
请求参数
Path Parameters
| 参数 | 类型 | 必填 | 说明 | 可选值 |
|---|---|---|---|---|
channel | string | ✅ | 渠道名称 | ghost, rocketchat |
Query Parameters
| 参数 | 类型 | 必填 | 默认值 | 说明 | 范围 |
|---|---|---|---|---|---|
limit | integer | ❌ | 10 | 返回数量 | 1-100 |
lease_seconds | integer | ❌ | 60 | 租约时长(秒) | 1-3600 |
lease_owner | string | ❌ | null | 租约所有者(可选,默认随机生成) | 最大 64 字符 |
响应
成功响应(200 OK)
{
"items": [
{
"id": 1,
"signal_id": "550e8400-e29b-41d4-a716-446655440000",
"channel": "ghost",
"payload": {
"title": "AI 板块出现集中放量",
"html": "<p><strong>⚠️ AI 板块出现集中放量</strong></p>...",
"status": "draft",
"tags": ["signal", "high-priority"],
"meta_title": "AI 板块出现集中放量",
"custom_excerpt": "多个AI相关代币在5分钟内成交量激增",
"meta_description": "多个AI相关代币在5分钟内成交量激增"
},
"lease_until": "2026-02-02T10:01:05.123456Z",
"lease_owner": "worker-abc123"
}
],
"count": 1
}
字段说明
字段说明
id: Outbox 记录 ID(用于 ACK)signal_id: 对应的信号 UUIDchannel: 渠道名称payload: 预格式化的内容(可以直接投递)lease_until: 租约过期时间lease_owner: 租约所有者
空响应(没有待处理项)
{
"items": [],
"count": 0
}
错误响应
400 Bad Request - 无效的 channel
400 Bad Request - 无效的 channel
{
"detail": "Channel must be 'ghost' or 'rocketchat'"
}
channel 参数必须是 ghost 或 rocketchat,其他值会被拒绝。使用示例
# 拉取 Ghost 渠道的 outbox
curl "https://api.dev.akria.net/api/v1/outbox/ghost?limit=10&lease_seconds=60"
# 拉取 Rocket.Chat 渠道的 outbox
curl "https://api.dev.akria.net/api/v1/outbox/rocketchat?limit=5&lease_seconds=120"
# 指定租约所有者
curl "https://api.dev.akria.net/api/v1/outbox/ghost?limit=10&lease_seconds=60&lease_owner=worker-1"
import requests
# 拉取 Ghost 渠道
url = "https://api.dev.akria.net/api/v1/outbox/ghost"
params = {
"limit": 10,
"lease_seconds": 60,
"lease_owner": "worker-1"
}
response = requests.get(url, params=params)
data = response.json()
for item in data["items"]:
print(f"Outbox ID: {item['id']}")
print(f"Signal ID: {item['signal_id']}")
print(f"Payload: {item['payload']}")
# 使用 payload 投递到 Ghost API
const axios = require('axios');
async function pullOutbox(channel = 'ghost', limit = 10) {
const url = `https://api.dev.akria.net/api/v1/outbox/${channel}`;
const params = {
limit,
lease_seconds: 60,
lease_owner: 'worker-1'
};
try {
const response = await axios.get(url, { params });
return response.data.items;
} catch (error) {
console.error('Error pulling outbox:', error.response.data);
return [];
}
}
// 使用
pullOutbox('ghost', 10).then(items => {
items.forEach(item => {
// 使用 item.payload 投递到 Ghost API
console.log(`Processing outbox ${item.id}`);
});
});
注意事项
租约机制
租约机制
租约时长:
lease_seconds 应该设置为处理时间的 2-3 倍,确保有足够时间完成处理。- 租约过期:如果处理超时,租约过期后可以重新拉取
- 立即 ACK:处理完成后立即发送 ACK,释放资源
并发安全
并发安全
- 使用
SELECT ... FOR UPDATE SKIP LOCKED确保并发安全 - 多个 worker 可以同时拉取,不会重复处理同一项
最佳实践
最佳实践
定期拉取
建议每 10-30 秒拉取一次
控制数量
每次拉取 10-20 条,避免数据库压力
使用 lease_owner
便于追踪和调试
立即 ACK
处理完立即 ACK,不要等待租约过期
相关文档
团队使用手册
手把手教程,适合团队使用
ACK 确认
下游服务投递完成后发送 ACK 确认
创建信号
接收来自分析层的已审计信号
Authorizations
API 认证密钥
Path Parameters
渠道名称
Available options:
ghost, rocketchat Query Parameters
返回数量
Required range:
1 <= x <= 100租约时长(秒)
Required range:
1 <= x <= 3600租约所有者(可选)
Maximum string length:
64