> ## Documentation Index
> Fetch the complete documentation index at: https://docs.akria.net/llms.txt
> Use this file to discover all available pages before exploring further.

# GET 拉取 Outbox

> 下游服务拉取待投递内容，使用租约机制防止重复领取

下游服务（Ghost / Rocket.Chat）拉取待投递内容。

## 核心特性

<CardGroup cols={2}>
  <Card title="租约机制" icon="lock">
    自动标记为 `leased`，防止重复领取
  </Card>

  <Card title="并发安全" icon="shield-check">
    使用 `SELECT ... FOR UPDATE SKIP LOCKED` 确保并发安全
  </Card>

  <Card title="租约超时" icon="clock">
    过期的 `leased` 项可以重新拉取
  </Card>

  <Card title="预格式化 Payload" icon="file-code">
    直接可用于投递，无需额外处理
  </Card>
</CardGroup>

***

## 认证

<Info>
  此端点**不需要** API Key 认证，可以直接访问。
</Info>

***

## 请求参数

### 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）

<ResponseExample>
  ```json theme={null}
  {
    "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
  }
  ```
</ResponseExample>

<AccordionGroup>
  <Accordion title="字段说明">
    * **`id`**: Outbox 记录 ID（用于 ACK）
    * **`signal_id`**: 对应的信号 UUID
    * **`channel`**: 渠道名称
    * **`payload`**: 预格式化的内容（可以直接投递）
    * **`lease_until`**: 租约过期时间
    * **`lease_owner`**: 租约所有者
  </Accordion>
</AccordionGroup>

### 空响应（没有待处理项）

<ResponseExample>
  ```json theme={null}
  {
    "items": [],
    "count": 0
  }
  ```
</ResponseExample>

### 错误响应

<AccordionGroup>
  <Accordion title="400 Bad Request - 无效的 channel">
    <ResponseExample>
      ```json theme={null}
      {
        "detail": "Channel must be 'ghost' or 'rocketchat'"
      }
      ```
    </ResponseExample>

    <Warning>
      `channel` 参数必须是 `ghost` 或 `rocketchat`，其他值会被拒绝。
    </Warning>
  </Accordion>
</AccordionGroup>

***

## 使用示例

<CodeGroup>
  ```bash cURL theme={null}
  # 拉取 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"
  ```

  ```python Python theme={null}
  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
  ```

  ```javascript JavaScript theme={null}
  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}`);
    });
  });
  ```
</CodeGroup>

***

## 注意事项

<AccordionGroup>
  <Accordion title="租约机制">
    <Tip>
      **租约时长**：`lease_seconds` 应该设置为处理时间的 2-3 倍，确保有足够时间完成处理。
    </Tip>

    * **租约过期**：如果处理超时，租约过期后可以重新拉取
    * **立即 ACK**：处理完成后立即发送 ACK，释放资源
  </Accordion>

  <Accordion title="并发安全">
    * 使用 `SELECT ... FOR UPDATE SKIP LOCKED` 确保并发安全
    * 多个 worker 可以同时拉取，不会重复处理同一项
  </Accordion>

  <Accordion title="最佳实践">
    <CardGroup cols={2}>
      <Card title="定期拉取" icon="clock">
        建议每 10-30 秒拉取一次
      </Card>

      <Card title="控制数量" icon="list">
        每次拉取 10-20 条，避免数据库压力
      </Card>

      <Card title="使用 lease_owner" icon="user">
        便于追踪和调试
      </Card>

      <Card title="立即 ACK" icon="check-circle">
        处理完立即 ACK，不要等待租约过期
      </Card>
    </CardGroup>
  </Accordion>
</AccordionGroup>

***

## 相关文档

<CardGroup cols={2}>
  <Card title="团队使用手册" icon="book" href="../../../services/core-api-v1/docs/API_USAGE_GUIDE.md">
    手把手教程，适合团队使用
  </Card>

  <Card title="ACK 确认" icon="check-circle" href="/technical/api-reference/core-api-outbox-ack">
    下游服务投递完成后发送 ACK 确认
  </Card>

  <Card title="创建信号" icon="send" href="/technical/api-reference/core-api-signals">
    接收来自分析层的已审计信号
  </Card>
</CardGroup>


## OpenAPI

````yaml GET /api/v1/outbox/{channel}
openapi: 3.1.0
info:
  title: Core API V1
  description: Core API V1 - Pull 模式核心网关服务，用于接收信号并管理 Outbox
  version: 1.0.0
servers:
  - url: https://api.dev.akria.net
    description: Dev 环境
security:
  - apiKey: []
paths:
  /api/v1/outbox/{channel}:
    get:
      tags:
        - outbox
      summary: 拉取 Outbox
      description: 下游服务（Ghost / Rocket.Chat）拉取待投递内容。使用租约机制防止重复领取。
      parameters:
        - name: channel
          in: path
          required: true
          description: 渠道名称
          schema:
            type: string
            enum:
              - ghost
              - rocketchat
        - name: limit
          in: query
          description: 返回数量
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 100
        - name: lease_seconds
          in: query
          description: 租约时长（秒）
          schema:
            type: integer
            default: 60
            minimum: 1
            maximum: 3600
        - name: lease_owner
          in: query
          description: 租约所有者（可选）
          schema:
            type: string
            maxLength: 64
      responses:
        '200':
          description: 成功响应
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutboxPullResponse'
        '400':
          description: 无效的 channel
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                detail: Channel must be 'ghost' or 'rocketchat'
components:
  schemas:
    OutboxPullResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/OutboxItem'
          description: Outbox 项列表
        count:
          type: integer
          description: 返回数量
    ErrorResponse:
      type: object
      properties:
        detail:
          type: string
          description: 错误详情
    OutboxItem:
      type: object
      properties:
        id:
          type: integer
          description: Outbox 记录 ID（用于 ACK）
        signal_id:
          type: string
          format: uuid
          description: 对应的信号 UUID
        channel:
          type: string
          enum:
            - ghost
            - rocketchat
          description: 渠道名称
        payload:
          type: object
          description: 预格式化的内容（可以直接投递）
          additionalProperties: true
        lease_until:
          type: string
          format: date-time
          nullable: true
          description: 租约过期时间
        lease_owner:
          type: string
          nullable: true
          description: 租约所有者
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API 认证密钥

````