openapi: 3.1.0
info:
  title: Catchy API
  description: API documentation for Catchy API
  version: 1.0.0
servers:
  - url: https://hooks.decenterlab.com
    description: Your Catchy instance
paths:
  /v1/channels:
    get:
      summary: ListChannels
      description: List channels by name.
      operationId: ChannelService_ListChannels
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/catchy.v1.Channel'
                title: channels
                description: Sorted by name.
  /v1/channels/{name}:
    get:
      summary: GetChannel
      description: Get a channel.
      operationId: ChannelService_GetChannel
      parameters:
        - name: name
          in: path
          description: The name path parameter.
          required: true
          schema:
            type: string
            title: name
            maxLength: 64
            pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/catchy.v1.Channel'
                title: channel
    delete:
      summary: DeleteChannel
      description: |-
        Permanently delete a channel and all its hooks. A hook
         sent to it later creates it again.
      operationId: ChannelService_DeleteChannel
      parameters:
        - name: name
          in: path
          description: The name path parameter.
          required: true
          schema:
            type: string
            title: name
            maxLength: 64
            pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/catchy.v1.DeleteChannelResponse'
  /v1/channels/{name}/pause:
    post:
      summary: PauseChannel
      description: |-
        Pause a channel: it refuses new hooks until resumed. Hooks it already
         caught are unaffected.
      operationId: ChannelService_PauseChannel
      parameters:
        - name: name
          in: path
          description: The name path parameter.
          required: true
          schema:
            type: string
            title: name
            maxLength: 64
            pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/catchy.v1.Channel'
                title: channel
  /v1/channels/{name}/resume:
    post:
      summary: ResumeChannel
      description: Resume a paused channel so it catches hooks again.
      operationId: ChannelService_ResumeChannel
      parameters:
        - name: name
          in: path
          description: The name path parameter.
          required: true
          schema:
            type: string
            title: name
            maxLength: 64
            pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/catchy.v1.Channel'
                title: channel
  /v1/hooks:
    get:
      summary: ListHooks
      description: |-
        List hooks, newest first. To get the next page, pass the last hook's ID
         as after. A page shorter than limit is the last one.
         To consume a channel, list its pending hooks, handle them, and report
         each one with ProcessHook, FailHook, or DiscardHook.
      operationId: HookService_ListHooks
      parameters:
        - name: channel
          in: query
          description: Only list hooks in this channel. Absent means all channels.
          schema:
            type: string
            title: channel
            maxLength: 64
            pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
        - name: status
          in: query
          description: Only list hooks with this status. Absent means all statuses.
          schema:
            type: string
            title: status
            enum:
              - pending
              - handled
              - failed
              - discarded
        - name: limit
          in: query
          description: Maximum number of hooks to return. Absent means 50.
          schema:
            type: integer
            title: limit
            maximum: 100
            minimum: 1
            format: int32
        - name: after
          in: query
          description: |-
            ID of the last hook from the previous page; only older hooks are
             returned. Absent means the first page.
          schema:
            type: string
            title: after
            pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/catchy.v1.Hook'
                title: hooks
                description: Newest first.
  /v1/hooks/{id}:
    get:
      summary: GetHook
      description: Get a hook.
      operationId: HookService_GetHook
      parameters:
        - name: id
          in: path
          description: The id path parameter.
          required: true
          schema:
            type: string
            title: id
            pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/catchy.v1.Hook'
                title: hook
    delete:
      summary: DeleteHook
      description: Permanently delete a hook.
      operationId: HookService_DeleteHook
      parameters:
        - name: id
          in: path
          description: The id path parameter.
          required: true
          schema:
            type: string
            title: id
            pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/catchy.v1.DeleteHookResponse'
  /v1/hooks/{id}/discard:
    post:
      summary: DiscardHook
      description: |-
        Discard a hook: it's deliberately ignored, and deliveries still queued
         for it are cancelled.
      operationId: HookService_DiscardHook
      parameters:
        - name: id
          in: path
          description: The id path parameter.
          required: true
          schema:
            type: string
            title: id
            pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/catchy.v1.Hook'
                title: hook
  /v1/hooks/{id}/retry:
    post:
      summary: RetryHook
      description: |-
        Retry a hook: set it back to pending and deliver it again to each
         destination whose last try failed. Its failures are kept.
      operationId: HookService_RetryHook
      parameters:
        - name: id
          in: path
          description: The id path parameter.
          required: true
          schema:
            type: string
            title: id
            pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      responses:
        "200":
          description: Success
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/catchy.v1.Hook'
                title: hook
components:
  schemas:
    catchy.v1.Channel:
      type: object
      properties:
        name:
          type: string
          title: name
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
          description: |-
            Channel name, as passed in ?channel=. Hooks sent without one go to
             "default".
        paused:
          type: boolean
          title: paused
          description: |-
            Whether the channel refuses new hooks. Senders get 503 Service
             Unavailable, which webhook providers retry later.
        stats:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.ChannelStats'
          title: stats
        guards:
          type: array
          items:
            type: string
            maxLength: 64
            pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
          title: guards
          uniqueItems: true
          description: |-
            Names of the guards every hook must pass to be caught, set in the
             dashboard. A channel created by its first hook gets the default guards.
      title: Channel
      required:
        - name
        - paused
        - stats
        - guards
      additionalProperties: false
      description: |-
        A channel groups hooks. Channels are created the first time a hook is sent
         to them.
    catchy.v1.ChannelStats:
      type: object
      properties:
        pending:
          type: string
          title: pending
          format: int64
        handled:
          type: string
          title: handled
          format: int64
        failed:
          type: string
          title: failed
          format: int64
        discarded:
          type: string
          title: discarded
          format: int64
      title: ChannelStats
      required:
        - pending
        - handled
        - failed
        - discarded
      additionalProperties: false
      description: Number of a channel's hooks in each status.
    catchy.v1.DeleteChannelRequest:
      type: object
      properties:
        name:
          type: string
          title: name
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      title: DeleteChannelRequest
      required:
        - name
      additionalProperties: false
    catchy.v1.DeleteChannelResponse:
      type: object
      title: DeleteChannelResponse
      additionalProperties: false
    catchy.v1.DeleteHookRequest:
      type: object
      properties:
        id:
          type: string
          title: id
          pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      title: DeleteHookRequest
      required:
        - id
      additionalProperties: false
    catchy.v1.DeleteHookResponse:
      type: object
      title: DeleteHookResponse
      additionalProperties: false
    catchy.v1.DiscardHookRequest:
      type: object
      properties:
        id:
          type: string
          title: id
          pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      title: DiscardHookRequest
      required:
        - id
      additionalProperties: false
    catchy.v1.DiscardHookResponse:
      type: object
      properties:
        hook:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.Hook'
          title: hook
      title: DiscardHookResponse
      required:
        - hook
      additionalProperties: false
    catchy.v1.Event:
      type: object
      properties:
        kind:
          type: string
          title: kind
          enum:
            - handled
            - discarded
            - retried
            - failed
          description: '"handled", "discarded", "retried", or "failed".'
        actor:
          type: string
          title: actor
          description: |-
            Who: a user's email, "api:" and an API key's label, the name of the
             handler that gave up, or empty for Catchy itself (its handlers all
             succeeded).
        message:
          type: string
          title: message
          description: Why, for "failed".
        created_at:
          allOf:
            - $ref: '#/components/schemas/google.protobuf.Timestamp'
          title: created_at
      title: Event
      required:
        - kind
        - actor
        - message
        - created_at
      additionalProperties: false
      description: Something that happened to a hook.
    catchy.v1.GetChannelRequest:
      type: object
      properties:
        name:
          type: string
          title: name
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      title: GetChannelRequest
      required:
        - name
      additionalProperties: false
    catchy.v1.GetChannelResponse:
      type: object
      properties:
        channel:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.Channel'
          title: channel
      title: GetChannelResponse
      required:
        - channel
      additionalProperties: false
    catchy.v1.GetHookRequest:
      type: object
      properties:
        id:
          type: string
          title: id
          pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      title: GetHookRequest
      required:
        - id
      additionalProperties: false
    catchy.v1.GetHookResponse:
      type: object
      properties:
        hook:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.Hook'
          title: hook
      title: GetHookResponse
      required:
        - hook
      additionalProperties: false
    catchy.v1.Hook:
      type: object
      properties:
        id:
          type: string
          title: id
          pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
          description: 'Unique hook ID, a lowercase ULID: IDs sort by the time they were caught.'
        channel:
          type: string
          title: channel
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
          description: Name of the channel that caught it.
        status:
          type: string
          title: status
          enum:
            - pending
            - handled
            - failed
            - discarded
          description: |-
            "pending" while its handlers run or retry (and, with no handlers, until
             it's marked handled in the dashboard); "handled" when every handler
             succeeded; "failed" when one gave up; "discarded" when deliberately
             ignored.
        method:
          type: string
          title: method
          description: HTTP method of the request.
        query:
          type: string
          title: query
          description: Raw query string of the request, without the leading "?".
        headers:
          type: object
          title: headers
          additionalProperties:
            type: string
            title: value
          description: |-
            Request headers. Repeated headers are joined with ", ". Cookies are not
             stored.
        content_type:
          type: string
          title: content_type
          description: Media type of the body, from the Content-Type header. Empty when not sent.
        body:
          type: string
          title: body
          format: byte
          description: The body exactly as received, e.g. for verifying a webhook signature.
        payload:
          oneOf:
            - $ref: '#/components/schemas/google.protobuf.Struct'
            - type: "null"
          title: payload
          description: |-
            The body decoded, when it is a JSON object or a form. Form fields are
             strings, or arrays of strings when a field was sent more than once.
        ip:
          type: string
          title: ip
          description: IP address of the sender.
        created_at:
          allOf:
            - $ref: '#/components/schemas/google.protobuf.Timestamp'
          title: created_at
          description: When the hook was caught.
        finalized_at:
          oneOf:
            - $ref: '#/components/schemas/google.protobuf.Timestamp'
            - type: "null"
          title: finalized_at
          description: When the hook was handled or discarded. Absent while pending or failed.
        events:
          type: array
          items:
            $ref: '#/components/schemas/catchy.v1.Event'
          title: events
          description: |-
            What happened to the hook, oldest first, and who did it: a handler gave
             up, someone retried, discarded, or handled it, or its handlers all
             succeeded. Kept across retries, so its history stays visible.
      title: Hook
      required:
        - id
        - channel
        - status
        - method
        - query
        - headers
        - content_type
        - body
        - ip
        - created_at
        - events
      additionalProperties: false
      description: |-
        A hook is one request caught at POST /?channel={channel}: a webhook, a
         contact form submission, or anything else. Catchy stores it as received,
         then runs its channel's handlers on it.
    catchy.v1.ListChannelsRequest:
      type: object
      title: ListChannelsRequest
      additionalProperties: false
    catchy.v1.ListChannelsResponse:
      type: object
      properties:
        channels:
          type: array
          items:
            $ref: '#/components/schemas/catchy.v1.Channel'
          title: channels
          description: Sorted by name.
      title: ListChannelsResponse
      additionalProperties: false
    catchy.v1.ListHooksRequest:
      type: object
      properties:
        channel:
          type:
            - string
            - "null"
          title: channel
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
          description: Only list hooks in this channel. Absent means all channels.
        status:
          type:
            - string
            - "null"
          title: status
          enum:
            - pending
            - handled
            - failed
            - discarded
          description: Only list hooks with this status. Absent means all statuses.
        limit:
          type:
            - integer
            - "null"
          title: limit
          maximum: 100
          minimum: 1
          format: int32
          description: Maximum number of hooks to return. Absent means 50.
        after:
          type:
            - string
            - "null"
          title: after
          pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
          description: |-
            ID of the last hook from the previous page; only older hooks are
             returned. Absent means the first page.
      title: ListHooksRequest
      additionalProperties: false
    catchy.v1.ListHooksResponse:
      type: object
      properties:
        hooks:
          type: array
          items:
            $ref: '#/components/schemas/catchy.v1.Hook'
          title: hooks
          description: Newest first.
      title: ListHooksResponse
      additionalProperties: false
    catchy.v1.PauseChannelRequest:
      type: object
      properties:
        name:
          type: string
          title: name
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      title: PauseChannelRequest
      required:
        - name
      additionalProperties: false
    catchy.v1.PauseChannelResponse:
      type: object
      properties:
        channel:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.Channel'
          title: channel
      title: PauseChannelResponse
      required:
        - channel
      additionalProperties: false
    catchy.v1.ResumeChannelRequest:
      type: object
      properties:
        name:
          type: string
          title: name
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[_-][a-z0-9]+)*$
      title: ResumeChannelRequest
      required:
        - name
      additionalProperties: false
    catchy.v1.ResumeChannelResponse:
      type: object
      properties:
        channel:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.Channel'
          title: channel
      title: ResumeChannelResponse
      required:
        - channel
      additionalProperties: false
    catchy.v1.RetryHookRequest:
      type: object
      properties:
        id:
          type: string
          title: id
          pattern: ^[0-7][0-9a-hjkmnp-tv-z]{25}$
      title: RetryHookRequest
      required:
        - id
      additionalProperties: false
    catchy.v1.RetryHookResponse:
      type: object
      properties:
        hook:
          allOf:
            - $ref: '#/components/schemas/catchy.v1.Hook'
          title: hook
      title: RetryHookResponse
      required:
        - hook
      additionalProperties: false
    google.protobuf.ListValue:
      type: array
      items:
        $ref: '#/components/schemas/google.protobuf.Value'
      description: A JSON array whose elements may be any JSON value.
    google.protobuf.NullValue:
      type: string
      title: NullValue
      enum:
        - NULL_VALUE
      description: |-
        Represents a JSON `null`.

         `NullValue` is a sentinel, using an enum with only one value to represent
         the null value for the `Value` type union.

         A field of type `NullValue` with any value other than `0` is considered
         invalid. Most ProtoJSON serializers will emit a Value with a `null_value` set
         as a JSON `null` regardless of the integer value, and so will round trip to
         a `0` value.
    google.protobuf.Struct:
      type: object
      additionalProperties:
        $ref: '#/components/schemas/google.protobuf.Value'
      description: A JSON object whose property values may be any JSON value.
    google.protobuf.Timestamp:
      type: string
      examples:
        - "2023-01-15T01:30:15.01Z"
        - "2024-12-25T12:00:00Z"
      format: date-time
      description: A point in time in RFC 3339 format, with up to nanosecond precision. Output uses UTC (`Z`); input may use an offset from UTC.
    google.protobuf.Value:
      oneOf:
        - type: "null"
        - type: number
        - type: string
        - type: boolean
        - type: array
        - type: object
          additionalProperties: true
      description: 'Any JSON value: `null`, number, string, boolean, array, or object.'
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque
security:
  - BearerAuth: []
