asyncapi: 3.0.0
info:
  title: grapl public status events
  description: Server-sent events over HTTP. The application is the grapl API
    server; it sends messages to clients. No public server origin is announced.
  version: 0.1.0
defaultContentType: application/json
channels:
  transactionStatus:
    address: /v1/swap/status/{signature}/events
    messages:
      snapshot:
        name: snapshot
        title: snapshot
        summary: Current observation when there is no durable event and no resume
          cursor. It does not advance the cursor.
        contentType: application/json
        payload:
          type: object
          properties:
            signature: &a1
              type: string
              pattern: ^[1-9A-HJ-NP-Za-km-z]{64,88}$
            status:
              type: string
              enum:
                - not_observed
                - finalized
                - failed
            commitment: &a2
              type: string
              enum:
                - finalized
            slot: &a3
              type:
                - string
                - "null"
              maxLength: 39
              pattern: ^(0|[1-9][0-9]*)$
            observedAt: &a4
              type:
                - string
                - "null"
              format: date-time
          required: &a6
            - signature
            - status
            - commitment
            - slot
            - observedAt
        examples:
          - name: snapshot-1
            payload:
              signature: "1111111111111111111111111111111111111111111111111111111111111111"
              status: not_observed
              commitment: finalized
              slot: null
              observedAt: null
        x-sse:
          event: snapshot
          retry: 3000
      transaction.status:
        name: transaction.status
        title: transaction.status
        summary: Durable finalized or failed observation. The SSE id equals payload.id;
          retain it as a decimal string for resumption.
        contentType: application/json
        payload:
          type: object
          properties:
            signature: *a1
            status: &a5
              type: string
              enum:
                - finalized
                - failed
            commitment: *a2
            slot: *a3
            observedAt: *a4
            id:
              type: string
              pattern: ^[1-9][0-9]{0,18}$
          required:
            - signature
            - status
            - commitment
            - slot
            - observedAt
            - id
        examples:
          - name: transaction.status-1
            payload:
              signature: "1111111111111111111111111111111111111111111111111111111111111111"
              status: finalized
              commitment: finalized
              slot: "9007199254740993"
              observedAt: 2026-10-12T12:00:00.000Z
              id: "9007199254740994"
          - name: transaction.status-2
            payload:
              signature: "1111111111111111111111111111111111111111111111111111111111111111"
              status: failed
              commitment: finalized
              slot: "9007199254740993"
              observedAt: 2026-10-12T12:00:00.000Z
              id: "9007199254740994"
        x-sse:
          event: transaction.status
          retry: 3000
          id: $message.payload#/id
      complete:
        name: complete
        title: complete
        summary: Terminal observation; close the client connection. A resumed terminal
          cursor can receive complete without replaying transaction.status.
        contentType: application/json
        payload:
          type: object
          properties:
            signature: *a1
            status: *a5
            commitment: *a2
            slot: *a3
            observedAt: *a4
          required: *a6
        examples:
          - name: complete-1
            payload:
              signature: "1111111111111111111111111111111111111111111111111111111111111111"
              status: finalized
              commitment: finalized
              slot: "9007199254740993"
              observedAt: 2026-10-12T12:00:00.000Z
          - name: complete-2
            payload:
              signature: "1111111111111111111111111111111111111111111111111111111111111111"
              status: failed
              commitment: finalized
              slot: "9007199254740993"
              observedAt: 2026-10-12T12:00:00.000Z
        x-sse:
          event: complete
          retry: 3000
      heartbeat:
        name: heartbeat
        title: heartbeat
        summary: Connection liveness only. This timestamp is not a transaction
          observation and does not advance the cursor.
        contentType: application/json
        payload:
          type: object
          properties:
            observedAt:
              type: string
              format: date-time
          required:
            - observedAt
          additionalProperties: false
        examples:
          - name: heartbeat-1
            payload:
              observedAt: 1970-01-01T00:00:00.020Z
          - name: heartbeat-2
            payload:
              observedAt: 1970-01-01T00:00:00.040Z
        x-sse:
          event: heartbeat
          retry: 3000
      reconnect:
        name: reconnect
        title: reconnect
        summary: The connection lifetime elapsed. Reconnect after retryAfterMs with the
          last durable event ID, if present.
        contentType: application/json
        payload:
          type: object
          properties:
            reason:
              type: string
              const: connection_lifetime
            retryAfterMs:
              type: integer
              const: 3000
          required:
            - reason
            - retryAfterMs
          additionalProperties: false
        examples:
          - name: reconnect-1
            payload:
              reason: connection_lifetime
              retryAfterMs: 3000
        x-sse:
          event: reconnect
          retry: 3000
      stream-error:
        name: stream-error
        title: stream-error
        summary: Safe error after response headers. No private exception text is
          published. Stop automatic retry when retryable is false.
        contentType: application/json
        payload:
          type: object
          properties:
            code:
              type: string
            retryable:
              type: boolean
          required:
            - code
            - retryable
          additionalProperties: false
        examples:
          - name: stream-error-1
            payload:
              code: STREAM_UNAVAILABLE
              retryable: true
          - name: stream-error-2
            payload:
              code: UNAUTHORIZED
              retryable: false
          - name: stream-error-3
            payload:
              code: FORBIDDEN
              retryable: false
          - name: stream-error-4
            payload:
              code: CURSOR_NOT_AVAILABLE
              retryable: false
        x-sse:
          event: stream-error
          retry: 3000
    parameters:
      signature:
        description: "Solana transaction signature: base58, 64–88 characters. See the
          OpenAPI handshake schema."
operations:
  streamTransactionStatus:
    action: send
    title: Stream transaction status
    channel:
      $ref: "#/channels/transactionStatus"
    summary: Send six public SSE message types for one signature.
    description: The server sends over an authenticated GET response. Last-Event-ID
      resumes only the matching signature and configured observer. Invalid
      cursors return HTTP 409 before the stream starts; after headers, failures
      use stream-error. Delivery can repeat after reconnect; deduplicate
      transaction.status by its string id. This is not a pool subscription or
      exactly-once delivery.
    security:
      - $ref: "#/components/securitySchemes/IntegrationKey"
    bindings:
      http:
        method: GET
        bindingVersion: 0.3.0
    messages:
      - $ref: "#/channels/transactionStatus/messages/snapshot"
      - $ref: "#/channels/transactionStatus/messages/transaction.status"
      - $ref: "#/channels/transactionStatus/messages/complete"
      - $ref: "#/channels/transactionStatus/messages/heartbeat"
      - $ref: "#/channels/transactionStatus/messages/reconnect"
      - $ref: "#/channels/transactionStatus/messages/stream-error"
components:
  securitySchemes:
    IntegrationKey:
      type: httpApiKey
      name: x-api-key
      in: header
x-sse:
  contentType: text/event-stream
  resumeHeader: Last-Event-ID
  retry: 3000
  description: Only transaction.status carries an SSE id. Authenticate the GET
    handshake with x-api-key; frame data is JSON. These extension fields
    describe SSE framing, not a standard AsyncAPI SSE binding.
  defaults:
    pollMs: 1000
    heartbeatMs: 15000
    maxDurationMs: 60000
x-provenance:
  publisherSha256: 9429a86dd81f060cfb95f60da43385f5745b08f083673a209aa6704c44107258
  scope: Checked local publisher fixtures; no public deployment or real wallet
    transaction.
