asyncapi: 3.0.0
info:
  title: Sport feed AMQP delivery
  version: '0.1'
  description: |
    AMQP 0-9-1 delivery over TLS with the supplied private certificate authority.
    The feed administrator creates durable AMQP exchanges and your private queues.
    Consume prices with automatic acknowledgements. Consume results on their own
    channel with manual acknowledgements and prefetch from 1 to 1000; acknowledge
    only after durable storage. Never declare, delete or purge queues. Each queue has one active consumer. Use a separate
    assigned queue set for each independent consumer.

    Bind your prices queue using #, <sport>.#, *.<competition_id>.* or
    *.*.<event_id>. The administrator binds it to feed.heartbeat and binds your
    results queue to every result. Bind all selected price and event keys, start
    buffering, and read a fresh non-final heartbeat before POST /v1/prices/join.
    Request gzip and use your AMQP-enabled API key. Install the snapshot, then
    apply buffered rows and events only when their version is greater. Results
    are always applied regardless of price filters, and can arrive twice.

    Rejoin after a changed heartbeat session, skipped sequence, final heartbeat,
    heartbeat sent_at older than 3 s, 3 s without
    a heartbeat, connection closure, or consumer cancellation. Suspend prices
    immediately while rejoining. Versions are JSON integers below 2^53: preserve them exactly. Unknown fields and message types must be ignored.
defaultContentType: application/json
servers:
  broker:
    host: '{host}:5671'
    protocol: amqps
    protocolVersion: 0.9.1
    description: Use the address and private authority supplied with your account.
    variables:
      host:
        default: broker.example.com
    security:
      - $ref: '#/components/securitySchemes/account'
channels:
  prices:
    address: '{sport}.{competition_id}.{event_id}'
    description: Every changed market row; moves are sent under both old and new keys.
    bindings:
      amqp:
        is: routingKey
        exchange: {name: feed.prices, type: topic, durable: true, autoDelete: false, vhost: feed}
        bindingVersion: 0.3.0
    messages:
      price: {$ref: '#/components/messages/price'}
  events:
    address: '{sport}.{competition_id}.{event_id}'
    bindings:
      amqp:
        is: routingKey
        exchange: {name: feed.events, type: topic, durable: true, autoDelete: false, vhost: feed}
        bindingVersion: 0.3.0
    messages:
      event: {$ref: '#/components/messages/event'}
  results:
    address: '{sport}.{competition_id}.{event_id}'
    description: Every result and correction, including replay after a restart.
    bindings:
      amqp:
        is: routingKey
        exchange: {name: feed.results, type: topic, durable: true, autoDelete: false, vhost: feed}
        bindingVersion: 0.3.0
    messages:
      result: {$ref: '#/components/messages/result'}
  heartbeat:
    address: ''
    bindings:
      amqp:
        is: routingKey
        exchange: {name: feed.heartbeat, type: fanout, durable: true, autoDelete: false, vhost: feed}
        bindingVersion: 0.3.0
    messages:
      heartbeat: {$ref: '#/components/messages/heartbeat'}
operations:
  publishPrices:
    action: send
    channel: {$ref: '#/channels/prices'}
    bindings:
      amqp: {deliveryMode: 1, timestamp: true, bindingVersion: 0.3.0}
  publishEvents:
    action: send
    channel: {$ref: '#/channels/events'}
    bindings:
      amqp: {deliveryMode: 1, timestamp: true, bindingVersion: 0.3.0}
  publishResults:
    action: send
    channel: {$ref: '#/channels/results'}
    bindings:
      amqp: {deliveryMode: 2, timestamp: true, bindingVersion: 0.3.0}
  publishHeartbeats:
    action: send
    channel: {$ref: '#/channels/heartbeat'}
    bindings:
      amqp: {deliveryMode: 1, timestamp: true, bindingVersion: 0.3.0}
components:
  securitySchemes:
    account: {type: userPassword}
  messages:
    price:
      contentType: application/json
      description: UTF-8 JSON, at most 65536 bytes; type property price, no headers or other properties.
      bindings:
        amqp: {messageType: price, bindingVersion: 0.3.0}
      payload: {$ref: '#/components/schemas/Price'}
    event:
      contentType: application/json
      bindings:
        amqp: {messageType: event, bindingVersion: 0.3.0}
      payload: {$ref: '#/components/schemas/Event'}
    result:
      contentType: application/json
      bindings:
        amqp: {messageType: result, bindingVersion: 0.3.0}
      payload: {$ref: '#/components/schemas/Result'}
    heartbeat:
      contentType: application/json
      bindings:
        amqp: {messageType: heartbeat, bindingVersion: 0.3.0}
      payload: {$ref: '#/components/schemas/Heartbeat'}
  schemas:
    Price:
      allOf:
        - $ref: 'openapi.yaml#/components/schemas/PriceRow'
        - type: object
          required: [type]
          properties:
            type: {const: price}
    Result:
      allOf:
        - $ref: 'openapi.yaml#/components/schemas/PriceRow'
        - type: object
          required: [type]
          properties:
            type: {const: result}
    Event:
      allOf:
        - $ref: 'openapi.yaml#/components/schemas/Event'
        - type: object
          required: [type]
          properties:
            type: {const: event}
    Heartbeat:
      type: object
      required: [type, session, seq, sent_at, as_of, final]
      properties:
        type: {const: heartbeat}
        session: {type: string, pattern: '^s_', description: Changes after every reconnection or resynchronization.}
        seq: {type: integer, minimum: 1, description: Unsigned 64-bit counter starting at 1 within the session.}
        sent_at: {type: string, format: date-time}
        as_of: {type: string, format: date-time, description: Publication time through which all price rows and events were sent.}
        final: {type: boolean}
