{
  "openapi": "3.1.0",
  "info": {
    "title": "CatchHook API",
    "version": "1.0.0",
    "description": "REST API for CatchHook — a webhook debugging platform. Manage endpoints, inspect webhook requests, configure forwarding, and connect localhost tunnels.",
    "contact": {
      "name": "CatchHook",
      "url": "https://catchhook.app"
    }
  },
  "servers": [
    {
      "url": "https://catchhook.app/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/auth/verify": {
      "get": {
        "summary": "Verify authentication",
        "description": "Returns the current user and account information. Useful for verifying your token is valid.",
        "operationId": "verifyAuth",
        "tags": ["Authentication"],
        "responses": {
          "200": {
            "description": "Token is valid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": "object",
                      "properties": {
                        "id": { "type": "integer" },
                        "email": { "type": "string", "format": "email" }
                      }
                    },
                    "account": {
                      "type": "object",
                      "properties": {
                        "id": { "type": "integer" },
                        "name": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/endpoints": {
      "get": {
        "summary": "List endpoints",
        "description": "Returns all endpoints accessible to the authenticated user.",
        "operationId": "listEndpoints",
        "tags": ["Endpoints"],
        "responses": {
          "200": {
            "description": "List of endpoints",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Endpoint" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "summary": "Create an endpoint",
        "description": "Creates a new webhook endpoint. Endpoints created with a provider get automatic event type detection and provider-specific UI.",
        "operationId": "createEndpoint",
        "tags": ["Endpoints"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["endpoint"],
                "properties": {
                  "endpoint": {
                    "type": "object",
                    "required": ["name"],
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Human-readable label"
                      },
                      "provider": {
                        "type": "string",
                        "enum": ["github", "stripe", "shopify", "twilio"],
                        "description": "Optional provider preset for enhanced event detection and display"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/Endpoint" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/endpoints/{id}": {
      "get": {
        "summary": "Get an endpoint",
        "description": "Returns details for a single endpoint.",
        "operationId": "getEndpoint",
        "tags": ["Endpoints"],
        "parameters": [
          { "$ref": "#/components/parameters/EndpointId" }
        ],
        "responses": {
          "200": {
            "description": "Endpoint details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/Endpoint" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "summary": "Delete an endpoint",
        "description": "Permanently deletes an endpoint and all its requests. Requires write scope.",
        "operationId": "deleteEndpoint",
        "tags": ["Endpoints"],
        "parameters": [
          { "$ref": "#/components/parameters/EndpointId" }
        ],
        "responses": {
          "200": { "description": "Endpoint deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/endpoints/{endpoint_id}/requests": {
      "get": {
        "summary": "List requests for an endpoint",
        "description": "Returns webhook requests received by the specified endpoint.",
        "operationId": "listRequests",
        "tags": ["Webhook Requests"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Endpoint ID (e.g., ep_abc123)"
          }
        ],
        "responses": {
          "200": {
            "description": "List of webhook requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/WebhookRequest" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/endpoints/{endpoint_id}/requests/{id}": {
      "get": {
        "summary": "Get a single request",
        "description": "Returns full details for a single webhook request, including the complete body.",
        "operationId": "getRequest",
        "tags": ["Webhook Requests"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Request ID (e.g., wr_def456)"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook request details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/WebhookRequest" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "summary": "Delete a request",
        "description": "Permanently deletes a single webhook request. Requires write scope.",
        "operationId": "deleteRequest",
        "tags": ["Webhook Requests"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "Request deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/endpoints/{endpoint_id}/requests/destroy_all": {
      "delete": {
        "summary": "Delete requests in bulk",
        "description": "Deletes all requests for the endpoint. Supports optional time range filters. Requires write scope.",
        "operationId": "deleteAllRequests",
        "tags": ["Webhook Requests"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "before",
            "in": "query",
            "schema": { "type": "string", "format": "date-time" },
            "description": "Delete requests received before this ISO 8601 timestamp"
          },
          {
            "name": "after",
            "in": "query",
            "schema": { "type": "string", "format": "date-time" },
            "description": "Delete requests received after this ISO 8601 timestamp"
          }
        ],
        "responses": {
          "200": { "description": "Requests deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/endpoints/{endpoint_id}/requests/{id}/replay": {
      "post": {
        "summary": "Replay a request",
        "description": "Re-sends the webhook to a target URL. Requires write scope.",
        "operationId": "replayRequest",
        "tags": ["Webhook Requests"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["target_url"],
                "properties": {
                  "target_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL to send the replayed webhook to"
                  },
                  "identity_mode": {
                    "type": "string",
                    "enum": ["original", "regenerate"],
                    "default": "original"
                  },
                  "signature_mode": {
                    "type": "string",
                    "enum": ["strip", "resign"],
                    "default": "strip"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Request replayed successfully" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/endpoints/{endpoint_id}/signature_configs": {
      "post": {
        "summary": "Create a signature config",
        "description": "Creates a signature verification config for an endpoint. Requires write scope.",
        "operationId": "createSignatureConfig",
        "tags": ["Signature Configs"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "force",
            "in": "query",
            "schema": { "type": "boolean" },
            "description": "Overwrite existing config for the same provider"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["signature_config"],
                "properties": {
                  "signature_config": {
                    "type": "object",
                    "required": ["provider", "secret"],
                    "properties": {
                      "provider": {
                        "type": "string",
                        "enum": ["github", "stripe", "shopify", "twilio", "generic_hmac"]
                      },
                      "secret": {
                        "type": "string",
                        "description": "The webhook signing secret"
                      },
                      "enabled": {
                        "type": "boolean",
                        "default": true
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Signature config created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/SignatureConfig" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": {
            "description": "Config for this provider already exists. Use ?force=true to overwrite.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/endpoints/{endpoint_id}/forwarding_targets": {
      "get": {
        "summary": "List forwarding targets",
        "description": "Returns all forwarding targets for the specified endpoint.",
        "operationId": "listForwardingTargets",
        "tags": ["Forwarding Targets"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "List of forwarding targets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/ForwardingTarget" }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "summary": "Create a forwarding target",
        "description": "Creates a new forwarding target for an endpoint. Requires write scope.",
        "operationId": "createForwardingTarget",
        "tags": ["Forwarding Targets"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["forwarding_target"],
                "properties": {
                  "forwarding_target": {
                    "type": "object",
                    "required": ["name", "url"],
                    "properties": {
                      "name": {
                        "type": "string",
                        "description": "Human-readable label"
                      },
                      "url": {
                        "type": "string",
                        "format": "uri",
                        "description": "Target URL to forward webhooks to"
                      },
                      "enabled": {
                        "type": "boolean",
                        "default": true
                      },
                      "timeout_seconds": {
                        "type": "integer",
                        "default": 30
                      },
                      "retry_count": {
                        "type": "integer",
                        "default": 3
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Forwarding target created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/ForwardingTarget" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/endpoints/{endpoint_id}/forwarding_targets/{id}": {
      "get": {
        "summary": "Get a forwarding target",
        "operationId": "getForwardingTarget",
        "tags": ["Forwarding Targets"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Forwarding target details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/ForwardingTarget" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "summary": "Update a forwarding target",
        "description": "Updates a forwarding target. Requires write scope.",
        "operationId": "updateForwardingTarget",
        "tags": ["Forwarding Targets"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "forwarding_target": {
                    "type": "object",
                    "properties": {
                      "name": { "type": "string" },
                      "url": { "type": "string", "format": "uri" },
                      "enabled": { "type": "boolean" },
                      "timeout_seconds": { "type": "integer" },
                      "retry_count": { "type": "integer" }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Forwarding target updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "$ref": "#/components/schemas/ForwardingTarget" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "summary": "Delete a forwarding target",
        "description": "Permanently deletes a forwarding target. Requires write scope.",
        "operationId": "deleteForwardingTarget",
        "tags": ["Forwarding Targets"],
        "parameters": [
          {
            "name": "endpoint_id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "Forwarding target deleted" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/events": {
      "get": {
        "summary": "List recent events",
        "description": "Returns recent activity events for the account. Requires read scope.",
        "operationId": "listEvents",
        "tags": ["Events"],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 },
            "description": "Number of events to return"
          },
          {
            "name": "since",
            "in": "query",
            "schema": { "type": "string", "format": "date-time" },
            "description": "Only return events after this ISO 8601 timestamp (default: 24 hours ago)"
          }
        ],
        "responses": {
          "200": {
            "description": "List of events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Event" }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "count": { "type": "integer" }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/tunnel/connect": {
      "post": {
        "summary": "Connect tunnel (authenticated)",
        "description": "Exchanges your API token for a one-time WebSocket ticket valid for 30 seconds. Requires tunnel scope.",
        "operationId": "tunnelConnect",
        "tags": ["Tunnel"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["endpoint_id"],
                "properties": {
                  "endpoint_id": {
                    "type": "string",
                    "description": "Endpoint to tunnel (e.g., ep_abc123)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WebSocket ticket issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticket": { "type": "string" },
                    "expires_in": { "type": "integer", "description": "Seconds until ticket expires" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/tunnel/connect_anonymous": {
      "post": {
        "summary": "Connect tunnel (anonymous)",
        "description": "Connects a tunnel to a temporary endpoint using a tunnel key. No authentication required.",
        "operationId": "tunnelConnectAnonymous",
        "tags": ["Tunnel"],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["tunnel_key"],
                "properties": {
                  "tunnel_key": {
                    "type": "string",
                    "description": "Tunnel key for the temporary endpoint (e.g., tk_xyz789)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WebSocket ticket issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticket": { "type": "string" },
                    "expires_in": { "type": "integer" },
                    "endpoint_id": { "type": "string" }
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/tunnel/delivery_reports": {
      "post": {
        "summary": "Report tunnel delivery",
        "description": "Reports the result of a local tunnel delivery back to CatchHook for metrics and alerts. Requires tunnel scope.",
        "operationId": "tunnelDeliveryReport",
        "tags": ["Tunnel"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["endpoint_id", "webhook_request_id", "target_url", "status_code"],
                "properties": {
                  "endpoint_id": { "type": "string" },
                  "webhook_request_id": { "type": "string" },
                  "target_url": { "type": "string", "format": "uri" },
                  "status_code": { "type": "integer" },
                  "response_message": { "type": "string" },
                  "response_time_ms": { "type": "integer" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Delivery report recorded" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token created in Account Settings. Include as: Authorization: Bearer <token>"
      }
    },
    "parameters": {
      "EndpointId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "string" },
        "description": "Endpoint ID (e.g., ep_abc123)"
      }
    },
    "schemas": {
      "Endpoint": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Prefixed ID (e.g., ep_abc123)" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri", "description": "Full webhook reception URL" },
          "provider": { "type": "string", "nullable": true, "description": "Provider preset if set" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookRequest": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Prefixed ID (e.g., wr_def456)" },
          "endpoint_id": { "type": "string" },
          "method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] },
          "path": { "type": "string" },
          "headers": { "type": "object", "additionalProperties": { "type": "string" } },
          "body": { "type": "string", "nullable": true },
          "content_type": { "type": "string", "nullable": true },
          "source_ip": { "type": "string" },
          "query_params": { "type": "object", "additionalProperties": { "type": "string" } },
          "size": { "type": "integer", "description": "Body size in bytes" },
          "received_at": { "type": "string", "format": "date-time" }
        }
      },
      "ForwardingTarget": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "enabled": { "type": "boolean" },
          "timeout_seconds": { "type": "integer" },
          "retry_count": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "SignatureConfig": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "provider": { "type": "string", "enum": ["github", "stripe", "shopify", "twilio", "generic_hmac"] },
          "enabled": { "type": "boolean" }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["webhook.received", "forwarding.failed", "alert.triggered"]
          },
          "occurred_at": { "type": "string", "format": "date-time" },
          "endpoint_id": { "type": "string" },
          "request_id": { "type": "string", "nullable": true },
          "data": { "type": "object" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid or missing API token",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "Forbidden": {
        "description": "Token doesn't have the required scope",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "ValidationError": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited — too many requests",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": { "type": "string" },
                "retry_after": { "type": "integer", "description": "Seconds to wait before retrying" }
              }
            }
          }
        }
      }
    }
  }
}
