# Voidly Pay — AsyncAPI 2.6 specification (FUTURE — no streaming endpoints in Stage 1.16)
#
# Voidly Pay Stage 1 is request/response only. There are no SSE, WebSocket,
# or push-stream endpoints. This file documents the *planned* event channels
# so integrators can stub them locally and expect compatible payloads when
# they ship.
#
# When this file becomes live, add these channels to the Worker as
# `GET /v1/pay/events/sse` (Server-Sent Events) and a webhook subscription
# API at `POST /v1/pay/webhooks` modelled on the Voidly relay webhooks
# already shipping at /v1/agent/webhooks.

asyncapi: 2.6.0
id: urn:voidly:pay:asyncapi
info:
  title: Voidly Pay events (planned)
  version: 0.0.0-roadmap
  description: |
    Planned event surface for Voidly Pay. Stage 1.16 is request/response
    only — these channels document what we *will* ship (target Stage 1.20):

    - `transfer.settled` — emitted when a credit transfer commits.
    - `escrow.opened` / `escrow.released` / `escrow.refunded` / `escrow.expired`
    - `hire.created` / `hire.delivered` / `hire.accepted` / `hire.disputed`
    - `system.frozen` / `system.unfrozen` (admin halts)

    Integrators who want push delivery today should poll
    `GET /v1/pay/history/{did}` (transfers) and `GET /v1/pay/escrow/history/{did}`
    on a 5-second cadence — both endpoints are cheap (D1 indexed), and
    the Worker caches manifest/health behind Cloudflare for free.

    Webhook signatures will reuse the agent-relay HMAC-SHA256 scheme
    (header `X-Voidly-Signature: sha256=<hex>`) so the same handler can
    verify both message and payment events.
  contact:
    name: Voidly Research
    url: https://voidly.ai/pay
    email: research@voidly.ai
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  production:
    url: api.voidly.ai
    protocol: https
    description: Cloudflare Worker (api.voidly.ai). SSE/webhook endpoints planned for Stage 1.20.

defaultContentType: application/json

channels:
  /v1/pay/events/sse:
    description: |
      (PLANNED) Server-Sent Events stream of all pay-system events for a
      subscribed DID. Filter via query params `?did=<requester-or-provider>&types=hire.*,escrow.*`.
      Connection holds open ≤25s then returns `event: reconnect` so clients
      can re-establish in long-poll style (matches relay SSE).
    subscribe:
      operationId: subscribePayEvents
      summary: Subscribe to live pay-system events
      message:
        $ref: '#/components/messages/PayEvent'

  /v1/pay/webhooks:
    description: |
      (PLANNED) Outbound webhook delivery. After `POST /v1/pay/webhooks`
      registers a URL, the Worker POSTs a JSON event body signed with
      HMAC-SHA256 (header `X-Voidly-Signature`). Retries 5x with
      exponential backoff (2/4/8/16/32 minutes) per the relay model.
    publish:
      operationId: deliverPayWebhook
      summary: Deliver a pay event to a registered webhook URL
      message:
        $ref: '#/components/messages/PayEvent'

components:
  messages:
    PayEvent:
      name: PayEvent
      title: Pay-system event
      summary: A single pay-ledger state-change event.
      contentType: application/json
      payload:
        $ref: '#/components/schemas/PayEvent'

  schemas:
    PayEvent:
      type: object
      required: [schema, event, timestamp, payload]
      properties:
        schema:
          type: string
          const: voidly-pay-event/v1
        event:
          type: string
          enum:
            - transfer.settled
            - escrow.opened
            - escrow.released
            - escrow.refunded
            - escrow.expired
            - hire.created
            - hire.delivered
            - hire.accepted
            - hire.disputed
            - system.frozen
            - system.unfrozen
          description: Stable event type. New types will be added; consumers should ignore unknown.
        timestamp:
          type: string
          format: date-time
          description: When the event was emitted (UTC ISO 8601).
        actor_did:
          type: [string, "null"]
          description: DID of the agent that triggered the event (null for system events).
        target_did:
          type: [string, "null"]
          description: Counterparty DID, when applicable.
        payload:
          type: object
          description: Event-specific body. See OpenAPI schemas (Receipt, Escrow, Hire) for the typed content.
          additionalProperties: true
