{
  "openapi": "3.1.0",
  "info": {
    "title": "LiveStreamBlog API",
    "version": "1.0.0-alpha.1",
    "description": "Versioned real-time publishing API. Demonstration responses are explicitly marked as sample data."
  },
  "servers": [
    {
      "url": "https://livestreamblog.com/api/v1"
    },
    {
      "url": "http://localhost:3000/api/v1"
    }
  ],
  "paths": {
    "/auth/mobile/login": {
      "post": {
        "operationId": "createMobileSession",
        "summary": "Authenticate a verified account for organisation-scoped mobile publishing",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "password": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Opaque revocable session token",
            "user metadata": null,
            "expiry": null,
            "and current organisation memberships": null
          },
          "401": {
            "description": "Enumeration-safe invalid",
            "disabled": null,
            "unverified": null,
            "or unknown account response": null
          },
          "429": {
            "description": "Durable credential-attempt rate limit exceeded"
          }
        }
      }
    },
    "/auth/mobile/logout": {
      "post": {
        "operationId": "revokeMobileSession",
        "summary": "Revoke the current mobile device session",
        "security": [
          {
            "mobileSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Session revoked"
          },
          "401": {
            "description": "Missing or malformed mobile session"
          },
          "429": {
            "description": "Logout rate limit exceeded"
          }
        }
      }
    },
    "/auth/mobile/logout-others": {
      "post": {
        "operationId": "revokeOtherMobileSessions",
        "summary": "Revoke every other session owned by the mobile account",
        "security": [
          {
            "mobileSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Other device sessions revoked"
          },
          "401": {
            "description": "Missing",
            "expired": null,
            "or revoked mobile session": null
          },
          "429": {
            "description": "Session-change rate limit exceeded"
          }
        }
      }
    },
    "/auth/mobile/refresh": {
      "post": {
        "operationId": "refreshMobileSession",
        "summary": "Extend a valid mobile session within its bounded 90-day lifetime",
        "security": [
          {
            "mobileSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Refreshed expiry without returning or rotating the opaque token"
          },
          "401": {
            "description": "Missing",
            "expired": null,
            "or revoked mobile session": null
          },
          "429": {
            "description": "Daily refresh rate limit exceeded"
          }
        }
      }
    },
    "/auth/mobile/invitations/accept": {
      "post": {
        "operationId": "acceptMobileContributorInvitation",
        "summary": "Accept a contributor invitation using the invited verified mobile account",
        "security": [
          {
            "mobileSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invitation accepted and current memberships refreshed"
          },
          "400": {
            "description": "Invitation invalid",
            "expired": null,
            "revoked": null,
            "or already used": null
          },
          "401": {
            "description": "Missing",
            "expired": null,
            "or revoked mobile session": null
          },
          "403": {
            "description": "Signed-in account does not match the invited email"
          },
          "429": {
            "description": "Invitation attempt rate limit exceeded"
          }
        }
      }
    },
    "/auth/session": {
      "get": {
        "operationId": "getBrowserSession",
        "summary": "Get the authenticated browser user and organisation memberships",
        "security": [
          {
            "browserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current verified browser session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing",
            "expired": null,
            "revoked": null,
            "disabled": null,
            "or unverified session": null
          }
        }
      }
    },
    "/reader/preferences": {
      "get": {
        "operationId": "listReaderStoryPreferences",
        "summary": "List the mobile account's follows and bookmarks for currently public live stories",
        "security": [
          {
            "mobileSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Current public story preferences",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ReaderStoryPreference"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing",
            "expired": null,
            "or revoked mobile session": null
          }
        }
      },
      "put": {
        "operationId": "replaceReaderStoryPreference",
        "summary": "Atomically replace follow and bookmark state for a public live story",
        "security": [
          {
            "mobileSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReaderStoryPreferenceWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated preference",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReaderStoryPreference"
                }
              }
            }
          },
          "401": {
            "description": "Missing",
            "expired": null,
            "or revoked mobile session": null
          },
          "404": {
            "description": "Live story is private",
            "archived": null,
            "or unavailable": null
          },
          "422": {
            "description": "Invalid preference input"
          },
          "429": {
            "description": "Preference mutation rate limit exceeded"
          }
        }
      }
    },
    "/auth/sessions": {
      "get": {
        "operationId": "listBrowserSessions",
        "summary": "List active browser sessions owned by the authenticated account",
        "security": [
          {
            "browserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token-free device session list with current-device marker"
          },
          "401": {
            "description": "Valid verified browser session required"
          }
        }
      }
    },
    "/auth/sessions/{sessionId}": {
      "delete": {
        "operationId": "revokeBrowserSession",
        "summary": "Idempotently revoke one session owned by the authenticated account",
        "security": [
          {
            "browserSession": []
          }
        ],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session revoked or already revoked; current-session cookie is cleared when applicable"
          },
          "403": {
            "description": "Same-origin check failed"
          },
          "404": {
            "description": "Session does not belong to the account or does not exist"
          },
          "429": {
            "description": "Session-change rate limit exceeded"
          }
        }
      }
    },
    "/auth/sessions/revoke-others": {
      "post": {
        "operationId": "revokeOtherBrowserSessions",
        "summary": "Revoke every other active session while preserving the issuing browser",
        "security": [
          {
            "browserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Other active sessions revoked and audited"
          },
          "403": {
            "description": "Same-origin check failed"
          },
          "429": {
            "description": "Session-change rate limit exceeded"
          }
        }
      }
    },
    "/auth/data-export": {
      "get": {
        "operationId": "downloadAccountData",
        "summary": "Download the authenticated user’s portable account data as JSON",
        "description": "Excludes passwords, token and credential hashes, encrypted secrets, IP hashes, and other subscribers’ contact details.",
        "security": [
          {
            "browserSession": []
          }
        ],
        "responses": {
          "200": {
            "description": "Private non-storable JSON attachment containing subject-attributed account and publishing data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "description": "Valid verified browser session required"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/organisations": {
      "get": {
        "operationId": "listOrganisations",
        "summary": "List the API credential's tenant organisation",
        "security": [
          {
            "bearerApiCredential": [
              "organisation:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-scoped organisation collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `organisation:read`"
          }
        }
      },
      "post": {
        "operationId": "createWorkspace",
        "summary": "Create an isolated workspace owned by the authenticated browser user",
        "security": [
          {
            "browserSession": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 160
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exact idempotent replay returned the original workspace"
          },
          "201": {
            "description": "Workspace",
            "owner membership": null,
            "command ledger": null,
            "and audit record created atomically": null
          },
          "401": {
            "description": "Verified browser session required"
          },
          "403": {
            "description": "Same-origin check failed"
          },
          "409": {
            "description": "Idempotency key was reused with different workspace input"
          },
          "422": {
            "description": "Workspace name failed validation"
          },
          "429": {
            "description": "Daily workspace creation limit exceeded"
          }
        }
      }
    },
    "/organisations/{organisationId}": {
      "patch": {
        "operationId": "updateOrganisation",
        "summary": "Idempotently update versioned workspace identity and branding",
        "security": [
          {
            "bearerApiCredential": [
              "organisation:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current integer workspace version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceSettingsWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Workspace identity updated",
            "audited": null,
            "or safely replayed": null
          },
          "403": {
            "description": "Credential actor is not a current owner or administrator"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale version",
            "idempotency reuse": null,
            "or slug conflict": null
          },
          "428": {
            "description": "If-Match version required"
          }
        }
      }
    },
    "/organisations/{organisationId}/memberships": {
      "get": {
        "operationId": "listMemberships",
        "summary": "List users and roles in the credential's tenant",
        "security": [
          {
            "bearerApiCredential": [
              "membership:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant membership collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "addOrganisationMember",
        "summary": "Add an existing verified account to the organisation",
        "security": [
          {
            "bearerApiCredential": [
              "membership:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMembership"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Membership created and audited"
          },
          "403": {
            "description": "Actor lacks a current administrative role or an administrator attempted to grant owner"
          },
          "404": {
            "description": "Organisation or verified account unavailable"
          },
          "409": {
            "description": "Account is already a member"
          }
        }
      }
    },
    "/organisations/{organisationId}/memberships/{userId}": {
      "patch": {
        "operationId": "updateOrganisationMember",
        "summary": "Change a member role while retaining at least one owner",
        "security": [
          {
            "bearerApiCredential": [
              "membership:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "additionalProperties": false,
                "properties": {
                  "role": {
                    "$ref": "#/components/schemas/MembershipRole"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Membership role updated or an identical role replayed"
          },
          "403": {
            "description": "Administrative role",
            "owner authority": null,
            "or owner-continuity invariant failed": null
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "operationId": "removeOrganisationMember",
        "summary": "Remove a member while retaining at least one owner",
        "security": [
          {
            "bearerApiCredential": [
              "membership:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Membership removed and audited"
          },
          "403": {
            "description": "Administrative role",
            "owner authority": null,
            "or owner-continuity invariant failed": null
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/organisations/{organisationId}/invitations": {
      "get": {
        "operationId": "listMembershipInvitations",
        "summary": "List masked tenant membership invitations and status",
        "security": [
          {
            "bearerApiCredential": [
              "membership:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated invitations with masked destinations"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createMembershipInvitation",
        "summary": "Create or safely reissue a seven-day membership invitation",
        "security": [
          {
            "bearerApiCredential": [
              "membership:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMembership"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Expired or revoked invitation reissued with a new capability"
          },
          "201": {
            "description": "Invitation created and encrypted email queued"
          },
          "403": {
            "description": "Actor lacks current authority or administrator attempted to invite an owner"
          },
          "409": {
            "description": "Destination already belongs to the organisation"
          },
          "429": {
            "description": "Invitation creation rate limit exceeded"
          }
        }
      }
    },
    "/organisations/{organisationId}/invitations/{invitationId}": {
      "delete": {
        "operationId": "revokeMembershipInvitation",
        "summary": "Idempotently revoke a pending membership invitation",
        "security": [
          {
            "bearerApiCredential": [
              "membership:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "organisationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "invitationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation revoked"
          },
          "403": {
            "description": "Actor cannot manage the invited role"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Invitation has already been accepted"
          }
        }
      }
    },
    "/invitations/accept": {
      "post": {
        "operationId": "acceptMembershipInvitation",
        "summary": "Accept an invitation using the matching verified browser account",
        "description": "Requires the secure browser session cookie and a same-origin request. The capability alone cannot grant membership.",
        "security": [
          {
            "browserSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcceptInvitation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Membership granted",
            "or identical acceptance safely replayed": null
          },
          "400": {
            "description": "Invitation invalid",
            "expired": null,
            "or revoked": null
          },
          "401": {
            "description": "Matching verified browser session required"
          },
          "403": {
            "description": "Session email does not match the encrypted invitation destination"
          },
          "429": {
            "description": "Acceptance attempt rate limit exceeded"
          }
        }
      }
    },
    "/teams": {
      "get": {
        "operationId": "listTeams",
        "summary": "List tenant teams using an opaque cursor",
        "security": [
          {
            "bearerApiCredential": [
              "team:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Filtered tenant team collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      },
      "post": {
        "operationId": "createTeam",
        "summary": "Idempotently create a tenant team",
        "security": [
          {
            "bearerApiCredential": [
              "team:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical creation safely replayed"
          },
          "201": {
            "description": "Team created"
          },
          "403": {
            "description": "Credential or actor cannot administer teams"
          },
          "409": {
            "description": "Idempotency or external-ID uniqueness conflict"
          },
          "422": {
            "description": "Team data failed validation"
          }
        }
      }
    },
    "/teams/{teamId}": {
      "get": {
        "operationId": "getTeam",
        "summary": "Retrieve one tenant team",
        "security": [
          {
            "bearerApiCredential": [
              "team:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "teamId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Versioned team resource"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "replaceTeam",
        "summary": "Idempotently replace mutable team details",
        "security": [
          {
            "bearerApiCredential": [
              "team:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "teamId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current integer team version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TeamWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Team updated or identical command safely replayed"
          },
          "403": {
            "description": "Credential or actor cannot administer teams"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale version",
            "idempotency reuse": null,
            "or external-ID conflict": null
          },
          "428": {
            "description": "If-Match version required"
          }
        }
      }
    },
    "/participants": {
      "get": {
        "operationId": "listParticipants",
        "summary": "List tenant participants or players using an opaque cursor",
        "security": [
          {
            "bearerApiCredential": [
              "participant:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "team_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 190
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Filtered tenant participant collection"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      },
      "post": {
        "operationId": "createParticipant",
        "summary": "Idempotently create a participant under a tenant team",
        "security": [
          {
            "bearerApiCredential": [
              "participant:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParticipantWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical creation safely replayed"
          },
          "201": {
            "description": "Participant created"
          },
          "403": {
            "description": "Credential or actor cannot administer participants"
          },
          "404": {
            "description": "Team not found in the tenant"
          },
          "409": {
            "description": "Idempotency or external-ID uniqueness conflict"
          },
          "422": {
            "description": "Participant data failed validation"
          }
        }
      }
    },
    "/participants/{participantId}": {
      "get": {
        "operationId": "getParticipant",
        "summary": "Retrieve one tenant participant",
        "security": [
          {
            "bearerApiCredential": [
              "participant:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "participantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Versioned participant resource"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "replaceParticipant",
        "summary": "Idempotently replace mutable participant details",
        "security": [
          {
            "bearerApiCredential": [
              "participant:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "participantId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current integer participant version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParticipantWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Participant updated or identical command safely replayed"
          },
          "403": {
            "description": "Credential or actor cannot administer participants"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Stale version",
            "idempotency reuse": null,
            "or external-ID conflict": null
          },
          "428": {
            "description": "If-Match version required"
          }
        }
      }
    },
    "/competitions": {
      "get": {
        "operationId": "listCompetitions",
        "summary": "List tenant competitions using an opaque cursor",
        "security": [
          {
            "bearerApiCredential": [
              "competition:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant competition collection"
          }
        }
      },
      "post": {
        "operationId": "createCompetition",
        "summary": "Idempotently create a tenant competition",
        "security": [
          {
            "bearerApiCredential": [
              "competition:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identical creation replayed"
          },
          "201": {
            "description": "Competition created"
          },
          "409": {
            "description": "Idempotency or uniqueness conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/seasons": {
      "get": {
        "operationId": "listSeasons",
        "summary": "List tenant seasons, optionally filtered by competition",
        "security": [
          {
            "bearerApiCredential": [
              "competition:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "competition_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant season collection"
          }
        }
      },
      "post": {
        "operationId": "createSeason",
        "summary": "Idempotently create a season under a tenant competition",
        "security": [
          {
            "bearerApiCredential": [
              "competition:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identical creation replayed"
          },
          "201": {
            "description": "Season created"
          },
          "404": {
            "description": "Competition not found in the tenant"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/venues": {
      "get": {
        "operationId": "listVenues",
        "summary": "List tenant venues using an opaque cursor",
        "security": [
          {
            "bearerApiCredential": [
              "venue:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant venue collection"
          }
        }
      },
      "post": {
        "operationId": "createVenue",
        "summary": "Idempotently create a tenant venue",
        "security": [
          {
            "bearerApiCredential": [
              "venue:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identical creation replayed"
          },
          "201": {
            "description": "Venue created"
          },
          "409": {
            "description": "Idempotency or uniqueness conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/tags": {
      "get": {
        "operationId": "listTags",
        "summary": "List canonical tenant-owned tags",
        "security": [
          {
            "bearerApiCredential": [
              "tag:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated tenant tag collection"
          }
        }
      },
      "post": {
        "operationId": "createTag",
        "summary": "Idempotently create a canonical tenant tag",
        "security": [
          {
            "bearerApiCredential": [
              "tag:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical creation safely replayed"
          },
          "201": {
            "description": "Tag created with version 1"
          },
          "403": {
            "description": "Actor cannot administer tags"
          },
          "409": {
            "description": "Slug or idempotency conflict"
          }
        }
      }
    },
    "/tags/{tagId}": {
      "get": {
        "operationId": "getTag",
        "summary": "Retrieve one tenant-owned tag",
        "security": [
          {
            "bearerApiCredential": [
              "tag:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TagId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current tag and version"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "operationId": "replaceTag",
        "summary": "Idempotently replace mutable tag details",
        "security": [
          {
            "bearerApiCredential": [
              "tag:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TagId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current integer tag version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tag replaced or identical command safely replayed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Slug",
            "version": null,
            "or idempotency conflict": null
          },
          "428": {
            "description": "If-Match version required"
          }
        }
      }
    },
    "/live-blogs": {
      "get": {
        "operationId": "listLiveBlogs",
        "summary": "List tenant standalone live blogs",
        "security": [
          {
            "bearerApiCredential": [
              "live_blog:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "scheduled",
                "live",
                "paused",
                "finished",
                "archived"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated tenant live-blog collection"
          }
        }
      },
      "post": {
        "operationId": "createLiveBlog",
        "summary": "Idempotently create a scheduled standalone live blog",
        "security": [
          {
            "bearerApiCredential": [
              "live_blog:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLiveBlog"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical creation replayed"
          },
          "201": {
            "description": "Live blog and immutable created event committed"
          },
          "409": {
            "description": "Idempotency or tenant uniqueness conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/live-blogs/public": {
      "get": {
        "operationId": "discoverPublicLiveBlogs",
        "summary": "Discover public non-archived live blogs without authentication",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Server-side title and description search"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "paused",
                "finished"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated public cards without draft or contributor administration data"
          },
          "400": {
            "description": "Invalid search",
            "status": null,
            "cursor": null,
            "or limit": null
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/live-blogs/{liveBlogId}": {
      "get": {
        "operationId": "getLiveBlog",
        "summary": "Get a tenant live blog and ordered lifecycle history",
        "security": [
          {
            "bearerApiCredential": [
              "live_blog:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LiveBlogId"
          }
        ],
        "responses": {
          "200": {
            "description": "Live-blog aggregate and immutable lifecycle events"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/live-blogs/{liveBlogId}/events": {
      "post": {
        "operationId": "transitionLiveBlog",
        "summary": "Append a lifecycle transition using optimistic concurrency",
        "security": [
          {
            "bearerApiCredential": [
              "live_blog:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/LiveBlogId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current aggregate version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "additionalProperties": false,
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "start",
                      "pause",
                      "resume",
                      "finish",
                      "archive"
                    ]
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical transition replayed"
          },
          "201": {
            "description": "Lifecycle event appended and status projection updated"
          },
          "409": {
            "description": "Version or idempotency conflict"
          },
          "422": {
            "description": "Invalid lifecycle transition"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/live-blogs/{liveBlogId}/public": {
      "get": {
        "operationId": "getPublicLiveBlog",
        "summary": "Get the cache-friendly public live-blog read model",
        "parameters": [
          {
            "$ref": "#/components/parameters/LiveBlogId"
          }
        ],
        "responses": {
          "200": {
            "description": "Public live-blog summary with aggregate version"
          },
          "404": {
            "description": "Live blog is unavailable",
            "private": null,
            "or archived": null
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/live-blogs/{liveBlogId}/timeline": {
      "get": {
        "operationId": "getPublicLiveBlogTimeline",
        "summary": "Read the public standalone timeline from the durable outbox",
        "parameters": [
          {
            "$ref": "#/components/parameters/LiveBlogId"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "latest",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Return the newest bounded window in chronological order"
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Return the preceding chronological page before this aggregate version"
          },
          {
            "name": "event_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Return one public timeline event for a specific-update deep link; cannot be combined with cursors or latest"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated canonical public events"
          },
          "404": {
            "description": "Live blog is unavailable",
            "private": null,
            "or archived": null
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/live-blogs/{liveBlogId}/stream": {
      "get": {
        "operationId": "streamPublicLiveBlog",
        "summary": "Resume a public standalone timeline using Server-Sent Events",
        "parameters": [
          {
            "$ref": "#/components/parameters/LiveBlogId"
          },
          {
            "name": "Last-Event-ID",
            "in": "header",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Last applied aggregate version"
          }
        ],
        "responses": {
          "200": {
            "description": "SSE stream with stable aggregate-version IDs",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "Live blog is unavailable",
            "private": null,
            "or archived": null
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/matches": {
      "get": {
        "operationId": "listMatches",
        "summary": "List and filter tenant matches using an opaque cursor",
        "security": [
          {
            "bearerApiCredential": [
              "match:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "scheduled",
                "live",
                "paused",
                "finished",
                "abandoned",
                "archived"
              ]
            }
          },
          {
            "name": "competition",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 160
            }
          },
          {
            "name": "team_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Filtered tenant match collection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      },
      "post": {
        "operationId": "createMatch",
        "summary": "Create a tenant match, teams, and initial output channels atomically",
        "security": [
          {
            "bearerApiCredential": [
              "match:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical idempotent creation replayed"
          },
          "201": {
            "description": "Match created once"
          },
          "409": {
            "description": "Idempotency key reused with different match data"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/matches/{matchId}": {
      "get": {
        "operationId": "getMatch",
        "summary": "Get a public match and current scoreboard",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          }
        ],
        "responses": {
          "200": {
            "description": "Match",
            "teams": null,
            "status and current score": null,
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/matches/{matchId}/score": {
      "get": {
        "operationId": "getCurrentScore",
        "summary": "Get a projected current scoreboard",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current score projection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/matches/{matchId}/events": {
      "post": {
        "operationId": "appendMatchEvent",
        "summary": "Append an immutable match event",
        "security": [
          {
            "bearerApiCredential": [
              "match:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current aggregate version",
            "quoted or unquoted": null
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppendMatchEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical idempotent request replayed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "201": {
            "description": "Event appended and score projection committed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Missing",
            "malformed": null,
            "expired": null,
            "revoked": null,
            "or unknown credential": null
          },
          "403": {
            "description": "Credential lacks `match:write` scope"
          },
          "409": {
            "description": "Version or idempotency conflict"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/matches/{matchId}/timeline": {
      "get": {
        "operationId": "getMatchTimeline",
        "summary": "Get an ordered cursor-paginated match timeline",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor returned by the previous response"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Timeline page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimelineResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/matches/{matchId}/stream": {
      "get": {
        "operationId": "streamMatchTimeline",
        "summary": "Resume a public timeline using Server-Sent Events",
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          },
          {
            "name": "Last-Event-ID",
            "in": "header",
            "schema": {
              "type": "integer",
              "minimum": 0
            },
            "description": "Last applied aggregate version"
          }
        ],
        "responses": {
          "200": {
            "description": "SSE stream with stable event IDs",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/stories": {
      "get": {
        "operationId": "listStories",
        "summary": "List tenant-published stories with optional match or live-blog filtering",
        "security": [
          {
            "bearerApiCredential": [
              "story:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "match_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "live_blog_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 190
            },
            "description": "Stable tenant tag slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated sanitised published stories with author attribution",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `story:read`"
          },
          "404": {
            "description": "The filtered match is unavailable in this tenant"
          }
        }
      },
      "post": {
        "operationId": "publishStory",
        "summary": "Publish a sanitised story and canonical timeline event atomically",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current match or live-blog aggregate version",
            "quoted or unquoted": null
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishStory"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical idempotent request replayed"
          },
          "201": {
            "description": "Story and story.published event committed"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Aggregate version or idempotency-content conflict"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "428": {
            "description": "If-Match aggregate version is required"
          },
          "429": {
            "description": "Story publication rate limit exceeded"
          }
        }
      }
    },
    "/analytics": {
      "get": {
        "operationId": "getTenantAnalytics",
        "summary": "Retrieve bounded operational and editorial tenant analytics",
        "security": [
          {
            "bearerApiCredential": [
              "analytics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                7,
                30,
                90
              ],
              "default": 30
            },
            "description": "UTC reporting window in days"
          }
        ],
        "responses": {
          "200": {
            "description": "Canonical coverage",
            "publishing": null,
            "subscriber": null,
            "and delivery-health report; audience availability is explicit": null
          },
          "400": {
            "30": null,
            "description": "Period is not 7",
            "or 90 days": null
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `analytics:read`"
          },
          "429": {
            "description": "Analytics read rate limit exceeded"
          }
        }
      }
    },
    "/audit-records": {
      "get": {
        "operationId": "listAuditRecords",
        "summary": "List immutable tenant audit history with read-time metadata safety",
        "security": [
          {
            "bearerApiCredential": [
              "audit:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Exact action filter"
          },
          {
            "name": "resource_type",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Exact resource-type filter"
          },
          {
            "name": "actor_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive creation boundary"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Exclusive creation boundary"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated records with actor attribution and recursively redacted metadata"
          },
          "400": {
            "description": "Invalid cursor",
            "filter": null,
            "or date range": null
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `audit:read`"
          },
          "429": {
            "description": "Audit read rate limit exceeded"
          }
        }
      }
    },
    "/stories/drafts": {
      "get": {
        "operationId": "listEditorialStories",
        "summary": "List tenant editorial stories, optionally filtered to one live blog",
        "security": [
          {
            "bearerApiCredential": [
              "story:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "live_blog_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current editorial story read models ordered by latest update"
          },
          "400": {
            "description": "Invalid live-blog identifier"
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `story:read`"
          }
        }
      },
      "post": {
        "operationId": "createStoryDraft",
        "summary": "Create a tenant-scoped sanitised story draft",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishStory"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identical idempotent request replayed"
          },
          "201": {
            "description": "Draft and immutable created revision committed"
          },
          "403": {
            "description": "Current actor lacks an editorial role"
          },
          "404": {
            "description": "Match or live-blog target unavailable in this tenant"
          },
          "409": {
            "description": "Idempotency-content conflict"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "description": "Editorial write rate limit exceeded"
          }
        }
      }
    },
    "/stories/{storyId}": {
      "get": {
        "operationId": "getEditorialStory",
        "summary": "Retrieve a tenant story and its complete revision history for preview",
        "security": [
          {
            "bearerApiCredential": [
              "story:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current story plus ordered immutable revisions"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateStoryDraft",
        "summary": "Edit a draft with optimistic concurrency; editing a scheduled story returns it to draft",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "body_html"
                ],
                "additionalProperties": false,
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "body_html": {
                    "type": "string",
                    "maxLength": 100000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft updated or identical command replayed"
          },
          "409": {
            "description": "Story state",
            "version": null,
            "or idempotency conflict": null
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/stories/{storyId}/schedule": {
      "post": {
        "operationId": "scheduleStory",
        "summary": "Persist a future publication time and immutable scheduled revision",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scheduled_at"
                ],
                "additionalProperties": false,
                "properties": {
                  "scheduled_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Future schedule persisted or identical command replayed"
          },
          "409": {
            "description": "Story state",
            "version": null,
            "or idempotency conflict": null
          },
          "422": {
            "description": "Invalid or non-future publication time"
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/stories/{storyId}/tags": {
      "put": {
        "operationId": "replaceStoryTags",
        "summary": "Atomically replace a story's canonical tenant tags",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "tag_ids"
                ],
                "additionalProperties": false,
                "properties": {
                  "tag_ids": {
                    "type": "array",
                    "maxItems": 20,
                    "uniqueItems": true,
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current story with ordered tags; exact commands replay safely"
          },
          "403": {
            "description": "Current actor lacks editorial access to this story"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Story version or idempotency conflict"
          },
          "422": {
            "description": "A supplied tag is invalid or belongs to another tenant"
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/stories/{storyId}/publish": {
      "post": {
        "operationId": "publishStoryDraft",
        "summary": "Publish an existing draft into its canonical match or live-blog timeline transaction",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "aggregate_version"
                ],
                "additionalProperties": false,
                "properties": {
                  "aggregate_version": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing draft published",
            "or the identical command safely replayed": null
          },
          "409": {
            "description": "Story/aggregate version",
            "state": null,
            "or idempotency conflict": null
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/stories/{storyId}/submit": {
      "post": {
        "operationId": "submitStoryForReview",
        "summary": "Submit the current story revision for editorial review",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "responses": {
          "200": {
            "description": "Story moved to pending review with an immutable submitted revision"
          },
          "403": {
            "description": "Current actor lacks an editorial role"
          },
          "409": {
            "description": "Story state",
            "version": null,
            "or idempotency conflict": null
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/stories/{storyId}/review": {
      "post": {
        "operationId": "reviewStory",
        "summary": "Approve a pending story or request contributor changes",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decision"
                ],
                "additionalProperties": false,
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "request_changes"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Immutable moderation decision and audit record committed"
          },
          "403": {
            "description": "Only an owner",
            "administrator": null,
            "or editor may review": null
          },
          "409": {
            "description": "Story state",
            "version": null,
            "or idempotency conflict": null
          },
          "422": {
            "description": "A change request requires a reason"
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/stories/{storyId}/corrections": {
      "post": {
        "operationId": "correctPublishedStory",
        "summary": "Correct a published story while preserving prior content and emitting correction.created",
        "security": [
          {
            "bearerApiCredential": [
              "story:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "storyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current story revision version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "aggregate_version",
                  "body_html",
                  "reason"
                ],
                "additionalProperties": false,
                "properties": {
                  "aggregate_version": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "body_html": {
                    "type": "string",
                    "maxLength": 100000
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Corrected read model",
            "immutable revision": null,
            "timeline event": null,
            "audit": null,
            "and outbox committed atomically": null
          },
          "409": {
            "description": "Story/aggregate version",
            "state": null,
            "or idempotency conflict": null
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "428": {
            "description": "If-Match story version is required"
          }
        }
      }
    },
    "/subscriptions": {
      "get": {
        "operationId": "listSubscribers",
        "summary": "List masked tenant subscribers and their topic rules",
        "security": [
          {
            "bearerApiCredential": [
              "subscription:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated subscriber metadata without contact addresses",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `subscription:read`"
          }
        }
      },
      "post": {
        "operationId": "createSubscription",
        "summary": "Record consent and queue double-opt-in verification",
        "security": [
          {
            "bearerApiCredential": [
              "subscription:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSubscription"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exact idempotent request replayed without another email"
          },
          "202": {
            "description": "Pending subscriber stored and verification email queued"
          },
          "409": {
            "description": "Destination already exists or idempotency-content conflict"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "description": "Subscription creation rate limit exceeded"
          }
        }
      }
    },
    "/subscriptions/{subscriberId}": {
      "put": {
        "operationId": "replaceSubscriberNotificationRules",
        "summary": "Atomically replace an active subscriber's topic and event rules",
        "security": [
          {
            "bearerApiCredential": [
              "subscription:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubscriberId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Current subscriber rule version"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "topics"
                ],
                "additionalProperties": false,
                "properties": {
                  "topics": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 20,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/SubscriptionTopic"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replaced rule set",
            "current version": null,
            "and replay status": null
          },
          "403": {
            "description": "Actor is not a current owner or administrator"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Version or idempotency conflict"
          },
          "422": {
            "description": "Invalid topic",
            "inactive subscriber": null,
            "or invalid rule set": null
          },
          "428": {
            "description": "If-Match subscriber version is required"
          },
          "429": {
            "description": "Notification rule change rate limit exceeded"
          }
        }
      },
      "delete": {
        "operationId": "unsubscribeSubscriber",
        "summary": "Idempotently unsubscribe and deactivate all topic rules",
        "security": [
          {
            "bearerApiCredential": [
              "subscription:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/SubscriberId"
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriber suppression state and timestamp"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/subscriptions/verify": {
      "post": {
        "operationId": "verifySubscription",
        "summary": "Activate a pending subscription with a single-use capability token",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 43,
                    "maxLength": 43
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber and topic rules activated"
          },
          "400": {
            "description": "Token invalid",
            "expired": null,
            "or already used": null
          },
          "429": {
            "description": "Verification attempt rate limit exceeded"
          }
        }
      }
    },
    "/subscriptions/unsubscribe": {
      "post": {
        "operationId": "unsubscribeWithCapability",
        "summary": "Idempotently stop a subscription using its private per-message capability",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "additionalProperties": false,
                "properties": {
                  "token": {
                    "type": "string",
                    "minLength": 43,
                    "maxLength": 43
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber suppressed and all topic rules disabled"
          },
          "400": {
            "description": "Token invalid"
          },
          "429": {
            "description": "Unsubscribe attempt rate limit exceeded"
          }
        }
      }
    },
    "/notifications": {
      "get": {
        "operationId": "listNotificationDeliveries",
        "summary": "List tenant notification delivery status without revealing destinations",
        "security": [
          {
            "bearerApiCredential": [
              "subscription:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated masked delivery metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `subscription:read`"
          }
        }
      }
    },
    "/matches/{matchId}/outputs": {
      "get": {
        "operationId": "listMatchOutputs",
        "summary": "List the tenant match's managed output channels and public URLs",
        "security": [
          {
            "bearerApiCredential": [
              "output:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          }
        ],
        "responses": {
          "200": {
            "description": "All managed output channels for the match",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `output:read`"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/matches/{matchId}/outputs/{outputId}": {
      "patch": {
        "operationId": "updateMatchOutput",
        "summary": "Activate or deactivate a tenant match output",
        "security": [
          {
            "bearerApiCredential": [
              "output:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          },
          {
            "$ref": "#/components/parameters/OutputId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOutput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated output metadata and public URL"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "description": "Output mutation rate limit exceeded"
          }
        }
      }
    },
    "/matches/{matchId}/outputs/{outputId}/rotate-key": {
      "post": {
        "operationId": "rotateMatchOutputKey",
        "summary": "Permanently replace a public output key and URL",
        "security": [
          {
            "bearerApiCredential": [
              "output:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MatchId"
          },
          {
            "$ref": "#/components/parameters/OutputId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            },
            "description": "Stable command ID; exact retries return the same replacement key"
          }
        ],
        "responses": {
          "200": {
            "description": "New public key and URL; the prior key is immediately invalid"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "description": "Output rotation rate limit exceeded"
          }
        }
      }
    },
    "/api-keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List tenant API-key metadata without token hashes or full tokens",
        "security": [
          {
            "bearerApiCredential": [
              "api_key:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated API-key metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `api_key:read`"
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "summary": "Create a scoped tenant credential and reveal its token once",
        "security": [
          {
            "bearerApiCredential": [
              "api_key:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKey"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Credential created; `token` is returned only in this response"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "description": "Credential creation rate limit exceeded"
          }
        }
      }
    },
    "/api-keys/{keyId}": {
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an API key without deleting its metadata or audit history",
        "security": [
          {
            "bearerApiCredential": [
              "api_key:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyId"
          }
        ],
        "responses": {
          "200": {
            "description": "Revocation timestamp; repeated revocation is idempotent"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhookEndpoints",
        "summary": "List tenant webhook endpoints without secret material",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant webhook endpoints",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential"
          },
          "403": {
            "description": "Credential lacks `webhook:read`"
          }
        }
      },
      "post": {
        "operationId": "createWebhookEndpoint",
        "summary": "Create a tenant webhook endpoint and reveal its signing secret once",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhook"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint created; `secret` is returned only in this response"
          },
          "422": {
            "description": "Invalid or non-public webhook URL or invalid subscription"
          }
        }
      }
    },
    "/webhooks/{webhookId}": {
      "patch": {
        "operationId": "updateWebhookEndpoint",
        "summary": "Update endpoint name, URL, event subscriptions, or active state",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhook"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated endpoint without secret material"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      },
      "delete": {
        "operationId": "deactivateWebhookEndpoint",
        "summary": "Deactivate an endpoint without deleting its delivery history",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "204": {
            "description": "Endpoint deactivated"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/webhooks/{webhookId}/rotate-secret": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "Replace and reveal a webhook signing secret once",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          }
        ],
        "responses": {
          "200": {
            "description": "Secret rotated; `secret` is returned only in this response"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/webhooks/{webhookId}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List tenant-scoped delivery attempts and terminal states",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated delivery log",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollectionResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/webhooks/{webhookId}/deliveries/{deliveryId}/replay": {
      "post": {
        "operationId": "replayDeadWebhookDelivery",
        "summary": "Requeue one dead-lettered delivery with a durable idempotent command",
        "security": [
          {
            "bearerApiCredential": [
              "webhook:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookId"
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identical replay command safely replayed"
          },
          "202": {
            "description": "Dead letter reset to pending for immediate worker delivery"
          },
          "404": {
            "description": "Endpoint or delivery unavailable in this tenant"
          },
          "409": {
            "description": "Idempotency key reused for another command"
          },
          "422": {
            "description": "Delivery is not dead or endpoint is inactive"
          },
          "429": {
            "description": "Manual replay rate limit exceeded"
          }
        }
      }
    },
    "/media": {
      "get": {
        "operationId": "listMediaAssets",
        "summary": "List the credential tenant's non-deleted media assets",
        "security": [
          {
            "bearerApiCredential": [
              "media:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated media metadata and stable public URLs"
          },
          "401": {
            "description": "Missing or invalid API credential"
          },
          "403": {
            "description": "Credential lacks `media:read`"
          }
        }
      },
      "post": {
        "operationId": "uploadMediaAsset",
        "summary": "Validate and upload an idempotent tenant image",
        "security": [
          {
            "bearerApiCredential": [
              "media:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "alt_text"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Signature-verified JPEG",
                    "PNG": null,
                    "GIF": null,
                    "WebP": null,
                    "or AVIF up to 10 MiB": null
                  },
                  "alt_text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Exact idempotent replay"
          },
          "201": {
            "description": "Image stored",
            "audited": null,
            "and ready at its immutable public URL": null
          },
          "409": {
            "description": "Idempotency key was reused for different content or metadata"
          },
          "413": {
            "description": "Upload exceeds 10 MiB"
          },
          "422": {
            "description": "Missing accessibility text or unsupported content signature"
          }
        }
      }
    },
    "/media/{mediaId}": {
      "get": {
        "operationId": "getMediaAsset",
        "summary": "Retrieve tenant-scoped media metadata",
        "security": [
          {
            "bearerApiCredential": [
              "media:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MediaId"
          }
        ],
        "responses": {
          "200": {
            "description": "Media metadata and immutable public URL"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/gaablogs/matches": {
      "get": {
        "operationId": "listGaaBlogsMatches",
        "summary": "Synchronize tenant matches and current score projections into GAABlogs",
        "security": [
          {
            "bearerApiCredential": [
              "gaablogs:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/UpdatedAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-scoped canonical matches",
            "scoreboards": null,
            "source attribution": null,
            "and public live links": null
          },
          "304": {
            "description": "Representation unchanged for the supplied If-None-Match value"
          },
          "401": {
            "description": "Missing or invalid API credential"
          },
          "403": {
            "description": "Credential lacks `gaablogs:read`"
          }
        }
      }
    },
    "/gaablogs/stories": {
      "get": {
        "operationId": "listGaaBlogsStories",
        "summary": "Synchronize published match and standalone live-blog stories into GAABlogs",
        "security": [
          {
            "bearerApiCredential": [
              "gaablogs:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/UpdatedAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-scoped published stories with canonical targets and source attribution"
          },
          "304": {
            "description": "Representation unchanged for the supplied If-None-Match value"
          },
          "401": {
            "description": "Missing or invalid API credential"
          },
          "403": {
            "description": "Credential lacks `gaablogs:read`"
          }
        }
      }
    },
    "/gaablogs/mappings": {
      "get": {
        "operationId": "listGaaBlogsMappings",
        "summary": "Synchronize stable external identifiers for teams, competitions, and venues",
        "security": [
          {
            "bearerApiCredential": [
              "gaablogs:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "team",
                "competition",
                "venue"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/UpdatedAfter"
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant-scoped external-to-canonical identifier mappings"
          },
          "304": {
            "description": "Representation unchanged for the supplied If-None-Match value"
          },
          "400": {
            "description": "Invalid mapping kind or incremental synchronization parameter"
          },
          "401": {
            "description": "Missing or invalid API credential"
          },
          "403": {
            "description": "Credential lacks `gaablogs:read`"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "browserSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "lbs_session",
        "description": "HttpOnly secure browser session; never expose its value to client JavaScript"
      },
      "bearerApiCredential": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "LiveStreamBlog scoped API credential"
      },
      "mobileSession": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Revocable `Session <opaque-token>` account session; organisation-scoped publishing appends `.<organisation-id>` and rechecks live membership and role"
      }
    },
    "parameters": {
      "MatchId": {
        "name": "matchId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Stable LiveStreamBlog match identifier"
      },
      "LiveBlogId": {
        "name": "liveBlogId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Tenant-scoped live-blog identifier"
      },
      "MediaId": {
        "name": "mediaId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Tenant-scoped media identifier"
      },
      "TagId": {
        "name": "tagId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Tenant-scoped tag identifier"
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Opaque cursor returned in `next_cursor`"
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "UpdatedAfter": {
        "name": "updated_after",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "Return records updated strictly after this timestamp; cursors are preferred while paging a snapshot"
      },
      "ApiKeyId": {
        "name": "keyId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "OutputId": {
        "name": "outputId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "SubscriberId": {
        "name": "subscriberId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "WebhookId": {
        "name": "webhookId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Fixed-window request allowance exhausted",
        "headers": {
          "RateLimit-Limit": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests allowed in the current window"
          },
          "RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests remaining in the current window"
          },
          "RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Unix timestamp when the current window resets"
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until another request should be attempted"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "ReaderStoryPreference": {
        "type": "object",
        "required": [
          "live_blog_id",
          "following",
          "bookmarked",
          "updated_at"
        ],
        "properties": {
          "live_blog_id": {
            "type": "string",
            "format": "uuid"
          },
          "following": {
            "type": "boolean"
          },
          "bookmarked": {
            "type": "boolean"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReaderStoryPreferenceWrite": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "live_blog_id",
          "following",
          "bookmarked"
        ],
        "properties": {
          "live_blog_id": {
            "type": "string",
            "format": "uuid"
          },
          "following": {
            "type": "boolean"
          },
          "bookmarked": {
            "type": "boolean"
          }
        }
      },
      "CollectionResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "items",
              "next_cursor"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "next_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "CreateMatch": {
        "type": "object",
        "required": [
          "sport",
          "competition",
          "starts_at",
          "home",
          "away"
        ],
        "properties": {
          "sport": {
            "type": "string",
            "enum": [
              "gaelic_football",
              "hurling",
              "camogie",
              "ladies_football"
            ]
          },
          "competition": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "competition_id": {
            "type": "string",
            "format": "uuid"
          },
          "season_id": {
            "type": "string",
            "format": "uuid"
          },
          "venue": {
            "type": "string",
            "maxLength": 200
          },
          "venue_id": {
            "type": "string",
            "format": "uuid"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "home": {
            "$ref": "#/components/schemas/CreateTeam"
          },
          "away": {
            "$ref": "#/components/schemas/CreateTeam"
          },
          "officials": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/MatchOfficialInput"
            }
          },
          "branding": {
            "$ref": "#/components/schemas/MatchBrandingInput"
          }
        }
      },
      "CreateLiveBlog": {
        "type": "object",
        "required": [
          "title",
          "slug"
        ],
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string",
            "minLength": 3,
            "maxLength": 240
          },
          "slug": {
            "type": "string",
            "minLength": 3,
            "maxLength": 190,
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "description": {
            "type": "string",
            "maxLength": 2000
          },
          "external_id": {
            "type": "string",
            "maxLength": 190
          },
          "scheduled_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateTeam": {
        "type": "object",
        "required": [
          "name",
          "code"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Existing team in the credential tenant; name and code remain required as command context"
          },
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 12
          },
          "lineup": {
            "type": "array",
            "maxItems": 60,
            "items": {
              "$ref": "#/components/schemas/LineupEntryInput"
            }
          }
        }
      },
      "LineupEntryInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190
          },
          "shirt_number": {
            "type": "integer",
            "minimum": 1,
            "maximum": 99
          },
          "position": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "status": {
            "type": "string",
            "enum": [
              "starter",
              "substitute"
            ],
            "default": "starter"
          }
        }
      },
      "MatchOfficialInput": {
        "type": "object",
        "required": [
          "name",
          "role"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 190
          },
          "role": {
            "type": "string",
            "enum": [
              "referee",
              "linesperson",
              "umpire",
              "fourth_official",
              "timekeeper",
              "other"
            ]
          }
        }
      },
      "MatchBrandingInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "primary_color": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "accent_color": {
            "type": "string",
            "pattern": "^#[0-9a-fA-F]{6}$"
          },
          "sponsor_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "logo_media_id": {
            "type": "string",
            "format": "uuid",
            "description": "Ready image owned by the same tenant"
          }
        }
      },
      "TeamWrite": {
        "type": "object",
        "required": [
          "name",
          "code"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "code": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 190
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Provider metadata limited to 8 KiB after JSON encoding"
          }
        }
      },
      "WorkspaceSettingsWrite": {
        "type": "object",
        "required": [
          "name",
          "slug"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
            "minLength": 3,
            "maxLength": 63
          },
          "branding": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "primary_color": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$"
              },
              "accent_color": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$"
              },
              "logo_media_id": {
                "type": "string",
                "format": "uuid",
                "description": "Ready tenant-owned image"
              }
            }
          }
        }
      },
      "ParticipantWrite": {
        "type": "object",
        "required": [
          "team_id",
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "team_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 190
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Provider metadata limited to 8 KiB after JSON encoding"
          }
        }
      },
      "TagWrite": {
        "type": "object",
        "required": [
          "name",
          "slug"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
            "maxLength": 190
          },
          "description": {
            "type": "string",
            "maxLength": 500
          }
        }
      },
      "AppendMatchEvent": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "match_started",
              "score",
              "score_corrected",
              "commentary",
              "card",
              "substitution",
              "full_time"
            ]
          },
          "team": {
            "type": "string",
            "enum": [
              "home",
              "away"
            ]
          },
          "score_kind": {
            "type": "string",
            "enum": [
              "goal",
              "point",
              "two_pointer"
            ],
            "description": "two_pointer is valid only for Gaelic football and ladies’ football matches"
          },
          "reverses_event_id": {
            "type": "string",
            "format": "uuid"
          },
          "commentary": {
            "type": "string",
            "minLength": 1,
            "maxLength": 5000
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiKeyScope": {
        "type": "string",
        "enum": [
          "organisation:read",
          "organisation:write",
          "membership:read",
          "membership:write",
          "team:read",
          "team:write",
          "participant:read",
          "participant:write",
          "competition:read",
          "competition:write",
          "venue:read",
          "venue:write",
          "tag:read",
          "tag:write",
          "live_blog:read",
          "live_blog:write",
          "gaablogs:read",
          "match:read",
          "match:write",
          "story:read",
          "story:write",
          "media:read",
          "media:write",
          "subscription:read",
          "subscription:write",
          "output:read",
          "output:write",
          "webhook:read",
          "webhook:write",
          "api_key:read",
          "api_key:write"
        ]
      },
      "MembershipRole": {
        "type": "string",
        "enum": [
          "owner",
          "admin",
          "editor",
          "reporter",
          "scorekeeper",
          "viewer",
          "api_client"
        ]
      },
      "CreateMembership": {
        "type": "object",
        "required": [
          "email",
          "role"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 320,
            "description": "Must identify an existing verified active account"
          },
          "role": {
            "$ref": "#/components/schemas/MembershipRole"
          }
        }
      },
      "AcceptInvitation": {
        "type": "object",
        "required": [
          "token"
        ],
        "additionalProperties": false,
        "properties": {
          "token": {
            "type": "string",
            "minLength": 43,
            "maxLength": 43,
            "description": "Private 256-bit invitation capability from the email link"
          }
        }
      },
      "CreateApiKey": {
        "type": "object",
        "required": [
          "name",
          "scopes"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "default": "live"
          },
          "scopes": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/ApiKeyScope"
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Optional future expiry no more than 366 days away"
          }
        }
      },
      "UpdateOutput": {
        "type": "object",
        "required": [
          "active"
        ],
        "additionalProperties": false,
        "properties": {
          "active": {
            "type": "boolean"
          }
        }
      },
      "PublishStory": {
        "type": "object",
        "required": [
          "body_html"
        ],
        "additionalProperties": false,
        "oneOf": [
          {
            "required": [
              "match_id"
            ],
            "not": {
              "required": [
                "live_blog_id"
              ]
            }
          },
          {
            "required": [
              "live_blog_id"
            ],
            "not": {
              "required": [
                "match_id"
              ]
            }
          }
        ],
        "properties": {
          "match_id": {
            "type": "string",
            "format": "uuid"
          },
          "live_blog_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "body_html": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100000,
            "description": "Rich text sanitised server-side before persistence"
          }
        }
      },
      "SubscriptionEventType": {
        "type": "string",
        "enum": [
          "match.started",
          "score.changed",
          "story.published",
          "match.finished",
          "correction.created"
        ]
      },
      "SubscriptionTopic": {
        "type": "object",
        "required": [
          "type",
          "key",
          "event_types"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "all",
              "match",
              "team",
              "competition",
              "tag"
            ]
          },
          "key": {
            "type": "string",
            "maxLength": 190,
            "description": "* for all; tenant UUID for match/team; stable competition name otherwise; tag slugs must already exist in the tenant catalogue"
          },
          "event_types": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/SubscriptionEventType"
            }
          }
        }
      },
      "CreateSubscription": {
        "type": "object",
        "required": [
          "email",
          "consent",
          "consent_at",
          "consent_source",
          "topics"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 320
          },
          "consent": {
            "type": "boolean",
            "const": true
          },
          "consent_at": {
            "type": "string",
            "format": "date-time",
            "description": "Within the preceding 30 days with five minutes of positive clock skew allowed"
          },
          "consent_source": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "topics": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/SubscriptionTopic"
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "match.started",
          "score.changed",
          "story.published",
          "match.finished",
          "correction.created",
          "match.event.created"
        ]
      },
      "CreateWebhook": {
        "type": "object",
        "required": [
          "name",
          "url",
          "event_types"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Public standard-HTTPS URL; private/local destinations are rejected"
          },
          "event_types": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          }
        }
      },
      "UpdateWebhook": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 160
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048
          },
          "event_types": {
            "type": "array",
            "minItems": 1,
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "TeamScore": {
        "type": "object",
        "required": [
          "goals",
          "points",
          "twoPointers",
          "total"
        ],
        "properties": {
          "goals": {
            "type": "integer",
            "minimum": 0
          },
          "points": {
            "type": "integer",
            "minimum": 0
          },
          "twoPointers": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "MatchResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "TimelineResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "items",
              "next_cursor"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              },
              "next_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      }
    }
  }
}
