{
  "openapi": "3.1.0",
  "info": {
    "title": "Events consumer feed",
    "version": "v1"
  },
  "paths": {
    "/v1/feed/grants": {
      "get": {
        "operationId": "feedGrants",
        "description": "Requires events:consume and an explicit tenant/key/consumer/producer grant. Filters cannot broaden a grant. First read initializes from now; save each cursor only after successful downstream delivery. Poll no more than once per 60 seconds.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "Bearer": []
          }
        ],
        "parameters": [
          {
            "in": "header",
            "name": "x-events-tenant-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Authenticated bounded feed result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedGrants"
                }
              }
            }
          },
          "400": {
            "description": "Malformed filter, cursor or query"
          },
          "401": {
            "description": "Invalid or revoked key"
          },
          "403": {
            "description": "Scope or consumer grant refused"
          },
          "409": {
            "description": "Cursor binding changed or history unavailable"
          },
          "503": {
            "description": "Required schema or service unavailable"
          }
        }
      }
    },
    "/v1/feed/capability": {
      "get": {
        "operationId": "feedCapability",
        "description": "Requires events:consume and an explicit tenant/key/consumer/producer grant. Filters cannot broaden a grant. First read initializes from now; save each cursor only after successful downstream delivery. Poll no more than once per 60 seconds.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "Bearer": []
          }
        ],
        "parameters": [
          {
            "in": "header",
            "name": "x-events-tenant-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-sink-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-consumer-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-producer-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-corpus-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-source-authority-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "type",
            "required": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 256
              },
              "minItems": 1,
              "maxItems": 50,
              "uniqueItems": true
            }
          },
          {
            "in": "query",
            "name": "subject",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Authenticated bounded feed result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedCapability"
                }
              }
            }
          },
          "400": {
            "description": "Malformed filter, cursor or query"
          },
          "401": {
            "description": "Invalid or revoked key"
          },
          "403": {
            "description": "Scope or consumer grant refused"
          },
          "409": {
            "description": "Cursor binding changed or history unavailable"
          },
          "503": {
            "description": "Required schema or service unavailable"
          }
        }
      }
    },
    "/v1/feed/events": {
      "get": {
        "operationId": "readFeed",
        "description": "Requires events:consume and an explicit tenant/key/consumer/producer grant. Filters cannot broaden a grant. First read initializes from now; save each cursor only after successful downstream delivery. Poll no more than once per 60 seconds.",
        "security": [
          {
            "ApiKey": []
          },
          {
            "Bearer": []
          }
        ],
        "parameters": [
          {
            "in": "header",
            "name": "x-events-tenant-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-sink-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-consumer-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-producer-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-corpus-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "x-events-source-authority-id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "type",
            "required": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "maxLength": 256
              },
              "minItems": 1,
              "maxItems": 50,
              "uniqueItems": true
            }
          },
          {
            "in": "query",
            "name": "subject",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1024
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 20,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Authenticated bounded feed result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedPage"
                }
              }
            }
          },
          "400": {
            "description": "Malformed filter, cursor or query"
          },
          "401": {
            "description": "Invalid or revoked key"
          },
          "403": {
            "description": "Scope or consumer grant refused"
          },
          "409": {
            "description": "Cursor binding changed or history unavailable"
          },
          "503": {
            "description": "Required schema or service unavailable"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "FeedFilter": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "types": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 256
            },
            "minItems": 1,
            "maxItems": 50,
            "uniqueItems": true
          },
          "subject": {
            "type": "string",
            "maxLength": 1024
          }
        },
        "required": [
          "types"
        ]
      },
      "FeedEvent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "event_id": {
            "type": "string"
          },
          "envelope_sha256": {
            "type": "string"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "envelope": {
            "type": "object",
            "description": "The canonical validated immutable EventEnvelope, including source, type, subject, time, data and metadata."
          }
        },
        "required": [
          "event_id",
          "envelope_sha256",
          "accepted_at",
          "envelope"
        ]
      },
      "FeedPage": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "protocol": {
            "type": "string",
            "enum": [
              "hasna.events.feed.v1"
            ]
          },
          "sink_id": {
            "type": "string"
          },
          "consumer_id": {
            "type": "string"
          },
          "producer_id": {
            "type": "string"
          },
          "corpus_id": {
            "type": "string"
          },
          "source_authority_id": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string"
          },
          "filter": {
            "$ref": "#/components/schemas/FeedFilter"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeedEvent"
            },
            "maxItems": 20
          },
          "cursor": {
            "type": "string"
          },
          "has_more": {
            "type": "boolean"
          },
          "initialized": {
            "type": "boolean"
          }
        },
        "required": [
          "protocol",
          "sink_id",
          "consumer_id",
          "producer_id",
          "corpus_id",
          "source_authority_id",
          "tenant_id",
          "filter",
          "events",
          "cursor",
          "has_more",
          "initialized"
        ]
      },
      "FeedCapability": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "protocol": {
            "type": "string",
            "enum": [
              "hasna.events.feed.v1"
            ]
          },
          "sink_id": {
            "type": "string"
          },
          "consumer_id": {
            "type": "string"
          },
          "producer_id": {
            "type": "string"
          },
          "corpus_id": {
            "type": "string"
          },
          "source_authority_id": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string"
          },
          "filter": {
            "$ref": "#/components/schemas/FeedFilter"
          },
          "start": {
            "type": "string",
            "enum": [
              "now"
            ]
          },
          "minimum_poll_seconds": {
            "type": "integer",
            "enum": [
              60
            ]
          }
        },
        "required": [
          "protocol",
          "sink_id",
          "consumer_id",
          "producer_id",
          "corpus_id",
          "source_authority_id",
          "tenant_id",
          "filter",
          "start",
          "minimum_poll_seconds"
        ]
      },
      "FeedGrant": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "sink_id": {
            "type": "string"
          },
          "consumer_id": {
            "type": "string"
          },
          "producer_id": {
            "type": "string"
          },
          "corpus_id": {
            "type": "string"
          },
          "source_authority_id": {
            "type": "string"
          },
          "app": {
            "type": "string"
          },
          "types": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 256
            },
            "minItems": 1,
            "maxItems": 50,
            "uniqueItems": true
          },
          "subject": {
            "type": "string",
            "maxLength": 1024
          }
        },
        "required": [
          "sink_id",
          "consumer_id",
          "producer_id",
          "corpus_id",
          "source_authority_id",
          "app",
          "types"
        ]
      },
      "FeedGrants": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "protocol": {
            "type": "string",
            "enum": [
              "hasna.events.feed.v1"
            ]
          },
          "tenant_id": {
            "type": "string"
          },
          "grants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FeedGrant"
            },
            "maxItems": 50
          },
          "next": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "protocol",
          "tenant_id",
          "grants",
          "next"
        ]
      }
    }
  }
}
