{
  "openapi": "3.1.0",
  "info": {
    "title": "PlaySport Onboarding API",
    "version": "1.0.0",
    "description": "API for crawling sports club websites and extracting structured data using AI. Submit crawl jobs, monitor progress in real-time via SSE, and retrieve extracted club information.",
    "contact": {
      "name": "PlaySport",
      "url": "https://playsport.com"
    }
  },
  "servers": [
    {
      "url": "https://onboarding-api.playsport.com",
      "description": "Production server"
    },
    {
      "url": "http://localhost:3000",
      "description": "Local development server"
    }
  ],
  "tags": [
    {
      "name": "Crawl",
      "description": "Crawl job management - submit, monitor, and retrieve crawl results"
    }
  ],
  "paths": {
    "/crawl": {
      "post": {
        "tags": ["Crawl"],
        "summary": "Submit a new crawl job",
        "description": "Queue a new crawl job for a sports club website. Returns a job ID that can be used to track progress and retrieve results.",
        "operationId": "submitCrawl",
        "requestBody": {
          "required": true,
          "description": "Crawl job configuration",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrawlRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Crawl job queued successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CrawlResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request - validation failed or invalid URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/crawl/{id}": {
      "get": {
        "tags": ["Crawl"],
        "summary": "Get crawl job status and results",
        "description": "Retrieve the current status, progress, and results (if completed) of a crawl job.",
        "operationId": "getCrawlJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Unique job identifier (UUID format)",
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status and results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CrawlResultResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid job ID format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/crawl/{id}/stream": {
      "get": {
        "tags": ["Crawl"],
        "summary": "Stream crawl job progress (SSE)",
        "description": "Server-Sent Events endpoint for real-time progress updates. Subscribes to Redis pub/sub and streams events until job completes or times out (5 minutes).",
        "operationId": "streamCrawlJob",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Unique job identifier (UUID format)",
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SSE event stream",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "event": {
                      "type": "string",
                      "enum": ["started", "page_found", "page_crawled", "extraction_started", "partial_data", "completed", "error"],
                      "description": "Event type"
                    },
                    "data": {
                      "type": "string",
                      "description": "JSON-encoded event payload"
                    },
                    "id": {
                      "type": "string",
                      "description": "Event sequence ID"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid job ID format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotFoundError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CrawlRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL of the sports club website to crawl",
            "example": "https://example-sports-club.com"
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "description": "Optional webhook URL to receive completion notifications",
            "example": "https://your-server.com/webhook"
          },
          "options": {
            "type": "object",
            "description": "Optional crawl configuration",
            "properties": {
              "maxPages": {
                "type": "integer",
                "minimum": 1,
                "maximum": 100,
                "default": 50,
                "description": "Maximum number of pages to crawl",
                "example": 25
              }
            }
          }
        }
      },
      "CrawlResponse": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the crawl job",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "status": {
            "type": "string",
            "enum": ["queued", "processing", "completed", "failed"],
            "description": "Current job status",
            "example": "queued"
          },
          "streamUrl": {
            "type": "string",
            "description": "URL for SSE progress stream",
            "example": "/crawl/550e8400-e29b-41d4-a716-446655440000/stream"
          },
          "resultUrl": {
            "type": "string",
            "description": "URL to retrieve results",
            "example": "/crawl/550e8400-e29b-41d4-a716-446655440000"
          }
        }
      },
      "CrawlResultResponse": {
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the crawl job"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL that was crawled"
          },
          "status": {
            "type": "string",
            "enum": ["queued", "processing", "completed", "failed"],
            "description": "Current job status"
          },
          "result": {
            "type": "object",
            "nullable": true,
            "description": "Extracted sports club data (when completed)",
            "properties": {
              "name": {
                "type": "string",
                "description": "Club name"
              },
              "description": {
                "type": "string",
                "description": "Club description"
              },
              "address": {
                "type": "object",
                "description": "Club location",
                "properties": {
                  "street": { "type": "string" },
                  "city": { "type": "string" },
                  "state": { "type": "string" },
                  "postalCode": { "type": "string" },
                  "country": { "type": "string" }
                }
              },
              "contact": {
                "type": "object",
                "description": "Contact information",
                "properties": {
                  "phone": { "type": "string" },
                  "email": { "type": "string", "format": "email" },
                  "website": { "type": "string", "format": "uri" }
                }
              },
              "facilities": {
                "type": "array",
                "description": "Available facilities",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "type": { "type": "string" },
                    "description": { "type": "string" }
                  }
                }
              },
              "activities": {
                "type": "array",
                "description": "Sports and activities offered",
                "items": {
                  "type": "string"
                }
              },
              "openingHours": {
                "type": "object",
                "description": "Operating hours by day",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "membership": {
                "type": "object",
                "description": "Membership options",
                "properties": {
                  "types": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string" },
                        "price": { "type": "string" },
                        "period": { "type": "string" },
                        "benefits": {
                          "type": "array",
                          "items": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "crawlError": {
            "type": "object",
            "nullable": true,
            "description": "Error details (when failed)",
            "properties": {
              "message": { "type": "string" },
              "code": { "type": "string" }
            }
          },
          "progress": {
            "type": "object",
            "nullable": true,
            "description": "Crawl progress",
            "properties": {
              "pagesFound": {
                "type": "integer",
                "description": "Total pages discovered"
              },
              "pagesCrawled": {
                "type": "integer",
                "description": "Pages successfully crawled"
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Job creation timestamp"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Last update timestamp"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Completion timestamp"
          },
          "durationMs": {
            "type": "integer",
            "nullable": true,
            "description": "Total processing time in milliseconds"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error message",
            "example": "Invalid request"
          },
          "details": {
            "type": "object",
            "description": "Additional error details",
            "additionalProperties": true
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Rate limit exceeded"
          },
          "retryAfter": {
            "type": "integer",
            "description": "Seconds until rate limit resets",
            "example": 45
          }
        }
      },
      "NotFoundError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Job not found"
          }
        }
      }
    }
  }
}
