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

# POST ACK 确认

> 下游服务投递完成后发送 ACK 确认，更新状态

下游服务投递完成后发送 ACK 确认，更新 outbox 状态和信号状态。

## 核心特性

<CardGroup cols={2}>
  <Card title="成功/失败确认" icon="check-circle">
    支持成功和失败两种状态
  </Card>

  <Card title="自动重试机制" icon="refresh">
    失败后自动重试（最多 3 次）
  </Card>

  <Card title="状态聚合" icon="merge">
    自动更新信号的整体投递状态
  </Card>

  <Card title="错误记录" icon="file-text">
    记录失败原因，便于排查
  </Card>
</CardGroup>

***

## 认证

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

***

## 请求参数

### Path Parameters

| 参数        | 类型     | 必填 | 说明   | 可选值                   |
| --------- | ------ | -- | ---- | --------------------- |
| `channel` | string | ✅  | 渠道名称 | `ghost`, `rocketchat` |

### Body

| 字段              | 类型      | 必填 | 说明                        |
| --------------- | ------- | -- | ------------------------- |
| `outbox_id`     | integer | ✅  | Outbox 记录 ID（从拉取响应中获取）    |
| `ok`            | boolean | ✅  | 投递结果：`true` 成功，`false` 失败 |
| `error_message` | string  | ❌  | 错误信息（`ok=false` 时建议提供）    |

***

## 响应

### 成功响应（200 OK）

<ResponseExample>
  ```json theme={null}
  {
    "status": "acknowledged",
    "outbox_id": 1,
    "outbox_status": "delivered",
    "attempts": 0
  }
  ```
</ResponseExample>

<AccordionGroup>
  <Accordion title="字段说明">
    * **`status`**: 确认状态（`acknowledged`）
    * **`outbox_id`**: Outbox 记录 ID
    * **`outbox_status`**: Outbox 状态（`delivered` 或 `failed`）
    * **`attempts`**: 尝试次数
  </Accordion>
</AccordionGroup>

### 错误响应

<AccordionGroup>
  <Accordion title="400 Bad Request - 无效的 outbox_id">
    <ResponseExample>
      ```json theme={null}
      {
        "detail": "Outbox not found"
      }
      ```
    </ResponseExample>

    <Warning>
      请确保 `outbox_id` 是从拉取响应中获取的有效 ID。
    </Warning>
  </Accordion>

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

  <Accordion title="500 Internal Server Error - 服务器错误">
    <ResponseExample>
      ```json theme={null}
      {
        "detail": "Internal server error"
      }
      ```
    </ResponseExample>

    **说明**：

    * 生产环境：返回通用错误信息
    * 开发环境（DEBUG 模式）：返回详细错误信息

    <Info>
      时区相关问题已在 2026-02-02 修复，不再出现 `TypeError: can't compare offset-naive and offset-aware datetimes` 错误。
    </Info>
  </Accordion>
</AccordionGroup>

***

## 使用示例

<Tabs>
  <Tab title="成功 ACK">
    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST https://api.dev.akria.net/api/v1/outbox/ghost/ack \
        -H "Content-Type: application/json" \
        -d '{
          "outbox_id": 1,
          "ok": true
        }'
      ```
    </RequestExample>

    <ResponseExample>
      ```json theme={null}
      {
        "status": "acknowledged",
        "outbox_id": 1,
        "outbox_status": "delivered",
        "attempts": 0
      }
      ```
    </ResponseExample>
  </Tab>

  <Tab title="失败 ACK">
    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST https://api.dev.akria.net/api/v1/outbox/ghost/ack \
        -H "Content-Type: application/json" \
        -d '{
          "outbox_id": 1,
          "ok": false,
          "error_message": "Connection timeout to Ghost API"
        }'
      ```
    </RequestExample>

    <ResponseExample>
      ```json theme={null}
      {
        "status": "acknowledged",
        "outbox_id": 1,
        "outbox_status": "failed",
        "attempts": 1
      }
      ```
    </ResponseExample>
  </Tab>
</Tabs>

<CodeGroup>
  ```python Python theme={null}
  import requests

  def ack_outbox(channel, outbox_id, success, error_message=None):
      url = f"https://api.dev.akria.net/api/v1/outbox/{channel}/ack"
      data = {
          "outbox_id": outbox_id,
          "ok": success
      }
      if error_message:
          data["error_message"] = error_message
      
      response = requests.post(url, json=data)
      return response.json()

  # 成功 ACK
  result = ack_outbox("ghost", 1, True)
  print(f"Status: {result['outbox_status']}")

  # 失败 ACK
  result = ack_outbox("ghost", 1, False, "Connection timeout")
  print(f"Status: {result['outbox_status']}, Attempts: {result['attempts']}")
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');

  async function ackOutbox(channel, outboxId, success, errorMessage = null) {
    const url = `https://api.dev.akria.net/api/v1/outbox/${channel}/ack`;
    const data = {
      outbox_id: outboxId,
      ok: success
    };
    
    if (errorMessage) {
      data.error_message = errorMessage;
    }

    try {
      const response = await axios.post(url, data);
      return response.data;
    } catch (error) {
      console.error('Error acknowledging outbox:', error.response.data);
      throw error;
    }
  }

  // 使用
  ackOutbox('ghost', 1, true).then(result => {
    console.log(`Outbox ${result.outbox_id} acknowledged: ${result.outbox_status}`);
  });
  ```
</CodeGroup>

***

## 状态聚合规则

ACK 确认后，系统会自动更新信号的整体状态：

| Ghost 状态    | Rocket.Chat 状态 | 信号状态           |
| ----------- | -------------- | -------------- |
| `delivered` | `delivered`    | `sent`         |
| `delivered` | `failed`       | `partial_sent` |
| `failed`    | `delivered`    | `partial_sent` |
| `failed`    | `failed`       | `failed`       |

<Info>
  只有当两个渠道都成功时，信号状态才会变为 `sent`。如果只有一个渠道成功，状态为 `partial_sent`。
</Info>

***

## 重试机制

<AccordionGroup>
  <Accordion title="自动重试规则">
    <Steps>
      <Step title="第一次失败">
        `attempts` 自动 +1，状态变为 `failed`，可以重新拉取
      </Step>

      <Step title="第二次失败">
        `attempts` 变为 2，状态仍为 `failed`，可以重新拉取
      </Step>

      <Step title="第三次失败">
        `attempts` 变为 3，状态变为 `dead`，不再重试
      </Step>
    </Steps>
  </Accordion>

  <Accordion title="重试流程">
    ```
    失败 (attempts=1) → 可以重新拉取 → 失败 (attempts=2) → 可以重新拉取 
    → 失败 (attempts=3) → 状态变为 dead → 不再重试
    ```
  </Accordion>

  <Accordion title="检查重试次数">
    ```bash theme={null}
    # 查询数据库
    kubectl exec -it postgres-dev-0 -n insight-dev -- \
      psql -U postgres -d core_api_dev -c \
      "SELECT id, channel, status, attempts FROM outbox WHERE id = 1;"
    ```
  </Accordion>
</AccordionGroup>

***

## 注意事项

<CardGroup cols={2}>
  <Card title="立即 ACK" icon="zap">
    处理完成后立即发送 ACK，不要等待
  </Card>

  <Card title="错误信息" icon="alert-circle">
    失败时提供详细的 `error_message`，便于排查
  </Card>

  <Card title="监控重试" icon="eye">
    监控 `attempts` 字段，超过 2 次失败需要人工介入
  </Card>

  <Card title="幂等性" icon="shield-check">
    相同 `outbox_id` 可以多次 ACK，但只有第一次有效
  </Card>
</CardGroup>

***

## 相关文档

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

  <Card title="拉取 Outbox" icon="download" href="/technical/api-reference/core-api-outbox-pull">
    下游服务拉取待投递内容
  </Card>

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


## OpenAPI

````yaml POST /api/v1/outbox/{channel}/ack
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}/ack:
    post:
      tags:
        - outbox
      summary: ACK 确认
      description: 下游服务投递完成后发送 ACK 确认。成功时更新状态为 delivered，失败时记录错误并增加重试次数。
      parameters:
        - name: channel
          in: path
          required: true
          description: 渠道名称
          schema:
            type: string
            enum:
              - ghost
              - rocketchat
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutboxAckRequest'
            example:
              outbox_id: 1
              ok: true
              error_message: null
      responses:
        '200':
          description: ACK 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    description: 确认状态
                    example: acknowledged
                  outbox_id:
                    type: integer
                    description: Outbox 记录 ID
                  outbox_status:
                    type: string
                    enum:
                      - delivered
                      - failed
                      - dead
                    description: Outbox 状态
                  attempts:
                    type: integer
                    description: 尝试次数
              example:
                status: acknowledged
                outbox_id: 1
                outbox_status: delivered
                attempts: 0
        '400':
          description: 验证失败
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Outbox 不存在
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                detail: Outbox 1 not found for channel ghost
        '500':
          description: 服务器内部错误
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                detail: Internal server error
components:
  schemas:
    OutboxAckRequest:
      type: object
      required:
        - outbox_id
        - ok
      properties:
        outbox_id:
          type: integer
          description: Outbox 记录 ID
        ok:
          type: boolean
          description: 是否成功（true=成功，false=失败）
        error_message:
          type: string
          nullable: true
          description: 错误消息（仅在 ok=false 时使用）
    ErrorResponse:
      type: object
      properties:
        detail:
          type: string
          description: 错误详情
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: API 认证密钥

````