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

# Report the transactional outbox's health

> Reports how much work the accounts service's outbox is holding, broken down by status and event type, and how old the oldest row in each state is. Covers the whole queue rather than one organization, and carries no organization id, account id, payload, vault object or error text: it says how much work is stuck and what kind, not whose. A `failed` row is terminal and will not be retried, so a non-zero failed count is work the platform has dropped and needs an operator. A `processing` row older than a few seconds means no worker is running to reclaim it. Readable by a client credential holding the credential:read scope and by an organization admin of a configured internal organization. API keys and every other organization are refused.



## OpenAPI

````yaml /api-reference/accounts.openapi.json get /v1/accounts/outbox/health
openapi: 3.1.0
info:
  title: accounts
  description: ''
  license:
    name: ''
  version: 0.1.0
servers:
  - url: https://api.anthid.com
    description: Production
security: []
tags:
  - name: Accounts
    description: Create, inspect, update, and disable trading accounts
  - name: Broker Credentials
    description: Inspect and manage credentials linked to trading accounts
paths:
  /v1/accounts/outbox/health:
    get:
      tags:
        - Accounts
      summary: Report the transactional outbox's health
      description: >-
        Reports how much work the accounts service's outbox is holding, broken
        down by status and event type, and how old the oldest row in each state
        is. Covers the whole queue rather than one organization, and carries no
        organization id, account id, payload, vault object or error text: it
        says how much work is stuck and what kind, not whose. A `failed` row is
        terminal and will not be retried, so a non-zero failed count is work the
        platform has dropped and needs an operator. A `processing` row older
        than a few seconds means no worker is running to reclaim it. Readable by
        a client credential holding the credential:read scope and by an
        organization admin of a configured internal organization. API keys and
        every other organization are refused.
      operationId: getOutboxHealth
      responses:
        '200':
          description: The queue's current health
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OutboxHealthResponse'
              example:
                completed: 4718
                entries:
                  - count: 2
                    event_type: credential.deleted
                    max_attempts: 10
                    oldest_claimed_at: '2026-08-25T15:08:41Z'
                    oldest_created_at: '2026-08-25T15:07:59Z'
                    status: failed
                  - count: 1
                    event_type: account.created
                    max_attempts: 0
                    oldest_claimed_at: null
                    oldest_created_at: '2026-08-26T11:59:02Z'
                    status: pending
                failed: 2
                oldest_claim_at: null
                oldest_failed_at: '2026-08-25T15:07:59Z'
                oldest_pending_at: '2026-08-26T11:59:02Z'
                pending: 1
                processing: 0
        '401':
          description: Caller is an API key, or a user whose token carries no organization
          content:
            text/plain:
              schema:
                type: string
        '403':
          description: >-
            The client credential is missing the credential:read scope, the
            caller's organization is not configured as internal, or the caller
            is not an organization admin
          content:
            text/plain:
              schema:
                type: string
        '429':
          description: Caller exceeded the request rate limit
          content:
            text/plain:
              schema:
                type: string
        '500':
          description: Unexpected server error while reading the queue
          content:
            text/plain:
              schema:
                type: string
      security:
        - bearerAuth: []
components:
  schemas:
    OutboxHealthResponse:
      type: object
      description: >-
        The queue's health, as the endpoint reports it.


        Lives here rather than in `api::accounts_api::response` with its
        siblings,

        because it is built on [`OutboxStatus`] and [`OutboxEventType`], which
        are

        this service's own Postgres enums and belong to the worker that drains
        the

        queue. Moving them into the shared crate to put the response there would

        hand every other service a vocabulary for a table only this one has.


        `entries` is the raw grouping and the rolled-up fields below are derived

        from it, rather than the other way round. A reader wanting "how bad is
        it"

        gets the totals without doing arithmetic; a reader wanting "what kind of

        work is stuck" gets the breakdown without a second request.
      required:
        - entries
        - pending
        - processing
        - failed
        - completed
      properties:
        completed:
          type: integer
          format: int64
        entries:
          type: array
          items:
            $ref: '#/components/schemas/OutboxHealthEntry'
          description: Every status and event type that has at least one row, counted.
        failed:
          type: integer
          format: int64
        oldest_claim_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            The oldest outstanding claim.


            A `processing` row is reclaimed after five seconds, so anything much

            older than that means no worker is running to reclaim it. This is
            how

            "the outbox worker is down" looks from the outside.
        oldest_failed_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the oldest terminally failed row was queued.


            `failed` is terminal: a row reaches it only after the attempt limit,
            and

            the claim query deliberately does not take it back. Nothing will
            retry

            it, so this figure only ever grows until somebody acts on it.
        oldest_pending_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the oldest row still waiting was queued.


            The figure worth alerting on. A healthy queue drains in under a
            second,

            so a pending row minutes old means the worker is behind or gone.
        pending:
          type: integer
          format: int64
        processing:
          type: integer
          format: int64
    OutboxHealthEntry:
      type: object
      description: >-
        One cell of the queue's health: a status and event type, and what the
        rows

        under it look like.


        Deliberately carries no `organization_id`, no `trading_account_id`, no

        `payload`, no `vault_object` and no `last_error`. This is the shape that

        makes a cross-tenant read safe to hand a browser session: it says how
        much

        work is stuck and what kind, and nothing about whose. See

        [`can_read_outbox_health`] for the argument.


        [`can_read_outbox_health`]:
        crate::policy::can_read_outbox_health::can_read_outbox_health
      required:
        - status
        - event_type
        - count
        - oldest_created_at
        - max_attempts
      properties:
        count:
          type: integer
          format: int64
          description: How many rows are in this state.
        event_type:
          $ref: '#/components/schemas/OutboxEventType'
        max_attempts:
          type: integer
          format: int32
          description: |-
            The highest attempt count among them. Approaching the limit on a
            `pending` row is a failure that has not finished happening yet.
        oldest_claimed_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the oldest of them was claimed, on the rows that have been.


            Only meaningful for `processing`, and it is the signal that the
            worker

            has stopped: the claim query takes a `processing` row back after
            five

            seconds, so one claimed minutes ago means nothing is running to
            reclaim

            it.
        oldest_created_at:
          type: string
          format: date-time
          description: >-
            When the oldest of them was queued. The age of the queue's head, and
            the

            figure that turns "three rows pending" into "three rows pending
            since

            yesterday", which are very different reports.
        status:
          $ref: '#/components/schemas/OutboxStatus'
    OutboxEventType:
      type: string
      description: >-
        What a queued row is, spelled the way the subject is.


        The database labels carry the dots, so a row read straight out of the
        queue

        says where it is going. The variants are renamed one by one rather than

        through `rename_all` for that reason.
      enum:
        - account.created
        - account.updated
        - account.deleted
        - credential.created
        - credential.updated
        - credential.deleted
    OutboxStatus:
      type: string
      enum:
        - pending
        - processing
        - completed
        - failed
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````