{
  "openapi": "3.1.0",
  "info": {
    "title": "SKMTC Hub API change webhooks",
    "version": "1",
    "description": "When a push or a refetch makes a new revision an API's latest revision, and\nits diff with the previous latest revision finds a change, Hub sends one\nsigned CloudEvent to each active webhook on that API. The owner can also\nsend a test event."
  },
  "tags": [],
  "components": {
    "schemas": {
      "Api": {
        "type": "object",
        "required": [
          "id",
          "owner",
          "slug",
          "name",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "owner": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The API's page on Hub."
          }
        },
        "description": "The API a webhook is on."
      },
      "Revision": {
        "type": "object",
        "required": [
          "id",
          "contentHash",
          "version",
          "publishedAt",
          "retrievedAt",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "contentHash": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The SHA-256 of the revision's document; `null` for a revision with no\nstored document."
          },
          "version": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "The publisher's version label, when the revision has one. Not an order."
          },
          "publishedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "The publisher's timestamp, when it gave one."
          },
          "retrievedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When Hub retrieved the revision."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The revision's page on Hub; the API's revisions page for a revision with\nno stored document."
          }
        },
        "description": "One revision of the API."
      },
      "ChangeDelta": {
        "type": "object",
        "required": [
          "added",
          "deleted",
          "modified"
        ],
        "properties": {
          "added": {
            "type": "integer",
            "format": "int32"
          },
          "deleted": {
            "type": "integer",
            "format": "int32"
          },
          "modified": {
            "type": "integer",
            "format": "int32"
          }
        },
        "description": "An added / deleted / modified count for one diff section."
      },
      "ChangeSummary": {
        "type": "object",
        "required": [
          "diff",
          "details"
        ],
        "properties": {
          "diff": {
            "type": "boolean",
            "description": "Whether the two documents differ."
          },
          "details": {
            "type": "object",
            "unevaluatedProperties": {
              "$ref": "#/components/schemas/ChangeDelta"
            },
            "description": "Added, deleted and modified counts for each section of the document."
          }
        },
        "description": "The diff engine's structural summary of the two revisions."
      },
      "ChangeSeverity": {
        "type": "object",
        "required": [
          "breaking",
          "warning",
          "info"
        ],
        "properties": {
          "breaking": {
            "type": "integer",
            "format": "int32"
          },
          "warning": {
            "type": "integer",
            "format": "int32"
          },
          "info": {
            "type": "integer",
            "format": "int32"
          }
        },
        "description": "The number of changes at each severity. The webhook does not send `total`."
      },
      "RevisionEventData": {
        "type": "object",
        "required": [
          "api",
          "revision",
          "previousRevision",
          "summary",
          "counts",
          "changesUrl"
        ],
        "properties": {
          "api": {
            "$ref": "#/components/schemas/Api"
          },
          "revision": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Revision"
              }
            ],
            "description": "The revision that became the API's latest revision."
          },
          "previousRevision": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Revision"
              }
            ],
            "description": "The API's latest revision before `revision`. It can belong to another\nversion of the API."
          },
          "summary": {
            "$ref": "#/components/schemas/ChangeSummary"
          },
          "counts": {
            "$ref": "#/components/schemas/ChangeSeverity"
          },
          "changesUrl": {
            "type": "string",
            "format": "uri",
            "description": "The page that lists the changes of `revision`. Private APIs need a\nsigned-in user who can read the API."
          }
        },
        "description": "The data of a revision event."
      },
      "RevisionEvent": {
        "type": "object",
        "required": [
          "specversion",
          "id",
          "source",
          "subject",
          "time",
          "datacontenttype",
          "type",
          "data"
        ],
        "properties": {
          "specversion": {
            "type": "string",
            "enum": [
              "1.0"
            ]
          },
          "id": {
            "type": "string",
            "description": "The same on every attempt and every manual retry of a delivery. Use it\nto find duplicates."
          },
          "source": {
            "type": "string",
            "format": "uri",
            "description": "The Hub site that sent the event, e.g. `https://skmtc.dev`."
          },
          "subject": {
            "type": "string",
            "description": "The API, as `{owner}/{slug}`."
          },
          "time": {
            "type": "string",
            "format": "date-time",
            "description": "When Hub captured the event. This is not the publisher's time."
          },
          "datacontenttype": {
            "type": "string",
            "enum": [
              "application/json"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "dev.skmtc.api.revision.v1"
            ]
          },
          "data": {
            "$ref": "#/components/schemas/RevisionEventData"
          }
        },
        "description": "A new revision became the API's latest revision, with a change."
      },
      "TestEventData": {
        "type": "object",
        "required": [
          "test",
          "api",
          "message"
        ],
        "properties": {
          "test": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "api": {
            "$ref": "#/components/schemas/Api"
          },
          "message": {
            "type": "string"
          }
        },
        "description": "The data of a test event. It describes no revision."
      },
      "TestEvent": {
        "type": "object",
        "required": [
          "specversion",
          "id",
          "source",
          "subject",
          "time",
          "datacontenttype",
          "type",
          "data"
        ],
        "properties": {
          "specversion": {
            "type": "string",
            "enum": [
              "1.0"
            ]
          },
          "id": {
            "type": "string",
            "description": "The same on every attempt and every manual retry of a delivery. Use it\nto find duplicates."
          },
          "source": {
            "type": "string",
            "format": "uri",
            "description": "The Hub site that sent the event, e.g. `https://skmtc.dev`."
          },
          "subject": {
            "type": "string",
            "description": "The API, as `{owner}/{slug}`."
          },
          "time": {
            "type": "string",
            "format": "date-time",
            "description": "When Hub captured the event. This is not the publisher's time."
          },
          "datacontenttype": {
            "type": "string",
            "enum": [
              "application/json"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "dev.skmtc.api.test.v1"
            ]
          },
          "data": {
            "$ref": "#/components/schemas/TestEventData"
          }
        },
        "description": "A test event, sent when the owner asks for one."
      },
      "ApiChangeEvent": {
        "type": "object",
        "oneOf": [
          {
            "$ref": "#/components/schemas/RevisionEvent"
          },
          {
            "$ref": "#/components/schemas/TestEvent"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "dev.skmtc.api.revision.v1": "#/components/schemas/RevisionEvent",
            "dev.skmtc.api.test.v1": "#/components/schemas/TestEvent"
          }
        },
        "description": "One structured-mode CloudEvent 1.0. `type` tells the two events apart."
      }
    }
  },
  "webhooks": {
    "api-change": {
      "post": {
        "operationId": "apiChange",
        "summary": "API change event",
        "description": "An API change, or a test. Delivery is at least once and in no fixed order.",
        "parameters": [
          {
            "name": "user-agent",
            "in": "header",
            "required": true,
            "description": "Hub's webhook client; the `1` matches the `v1` of the event types.",
            "schema": {
              "type": "string",
              "enum": [
                "SKMTC-Hub-Webhooks/1"
              ]
            }
          },
          {
            "name": "skmtc-signature",
            "in": "header",
            "required": true,
            "description": "`t=<unix seconds>,v1=<hex HMAC-SHA256>`. The HMAC key is the webhook's\nwhole secret as UTF-8, `whsec_` prefix included; the signed content is\n`<t>.<raw body>`. Compute it from the raw body before parsing it, compare\nin constant time, and refuse a `t` more than five minutes from your\nclock. Each attempt signs again with a new `t` and the current secret.",
            "schema": {
              "type": "string",
              "pattern": "^t=[0-9]+,v1=[0-9a-f]{64}$"
            }
          }
        ],
        "responses": {
          "307": {
            "description": "Hub sends the same request to `location`, for not more than 3 redirects.",
            "headers": {
              "location": {
                "required": true,
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "308": {
            "description": "Hub sends the same request to `location`, for not more than 3 redirects.",
            "headers": {
              "location": {
                "required": true,
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "408": {
            "description": "Hub tries again later with the same event `id`, for not more than 24\nhours from the first attempt. Hub also tries again after a timeout of 10\nseconds or a network error."
          },
          "429": {
            "description": "Hub tries again later with the same event `id`, for not more than 24\nhours from the first attempt. Hub also tries again after a timeout of 10\nseconds or a network error."
          },
          "2XX": {
            "description": "Any 2xx: the delivery succeeded. Hub does not read the body."
          },
          "5XX": {
            "description": "Hub tries again later, as for 408 and 429."
          },
          "3XX": {
            "description": "Any other 3xx or 4xx: the delivery fails and Hub does not try again. The\nowner can retry it from Hub."
          },
          "4XX": {
            "description": "Any other 3xx or 4xx: the delivery fails and Hub does not try again. The\nowner can retry it from Hub."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/cloudevents+json; charset=utf-8": {
              "schema": {
                "$ref": "#/components/schemas/ApiChangeEvent"
              }
            }
          }
        }
      }
    }
  }
}
