{
  "openapi": "3.0.3",
  "info": {
    "title": "SafeDine API",
    "description": "Access restaurant safety scores, inspection data, and nearby restaurant information powered by official government health inspection records.",
    "version": "1.0.0",
    "contact": {
      "name": "SafeDine API Support",
      "url": "https://safedine.ai/contact"
    },
    "termsOfService": "https://safedine.ai/terms"
  },
  "servers": [
    {
      "url": "https://safedine.ai/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/restaurants": {
      "get": {
        "summary": "Search restaurants",
        "description": "Search and filter restaurants by name, location, cuisine, and safety score.",
        "operationId": "searchRestaurants",
        "tags": ["Restaurants"],
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "description": "Free-text search by restaurant name or address",
            "schema": { "type": "string" }
          },
          {
            "name": "state",
            "in": "query",
            "description": "Two-letter state abbreviation (e.g. FL, NY, IL)",
            "schema": { "type": "string", "minLength": 2, "maxLength": 2 }
          },
          {
            "name": "city",
            "in": "query",
            "description": "City name",
            "schema": { "type": "string" }
          },
          {
            "name": "cuisine",
            "in": "query",
            "description": "Cuisine type (e.g. Italian, Mexican, Chinese)",
            "schema": { "type": "string" }
          },
          {
            "name": "minScore",
            "in": "query",
            "description": "Minimum SafeDine Score (inclusive)",
            "schema": { "type": "integer", "minimum": 0, "maximum": 100 }
          },
          {
            "name": "maxScore",
            "in": "query",
            "description": "Maximum SafeDine Score (inclusive)",
            "schema": { "type": "integer", "minimum": 0, "maximum": 100 }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results to return",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          }
        ],
        "responses": {
          "200": {
            "description": "List of matching restaurants",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "restaurants": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Restaurant" }
                    },
                    "count": {
                      "type": "integer",
                      "description": "Number of restaurants in this response"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total number of matching restaurants"
                    }
                  },
                  "required": ["restaurants", "count", "total"]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/restaurants/{id}": {
      "get": {
        "summary": "Get restaurant details",
        "description": "Get a single restaurant with full inspection history.",
        "operationId": "getRestaurant",
        "tags": ["Restaurants"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Restaurant ID",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Restaurant with full inspection history",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    { "$ref": "#/components/schemas/Restaurant" },
                    {
                      "type": "object",
                      "properties": {
                        "inspections": {
                          "type": "array",
                          "items": { "$ref": "#/components/schemas/Inspection" }
                        }
                      }
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/restaurants/nearby": {
      "get": {
        "summary": "Find nearby restaurants",
        "description": "Find restaurants near a geographic coordinate, sorted by distance.",
        "operationId": "getNearbyRestaurants",
        "tags": ["Restaurants"],
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "description": "Latitude",
            "schema": { "type": "number", "format": "double" }
          },
          {
            "name": "lng",
            "in": "query",
            "required": true,
            "description": "Longitude",
            "schema": { "type": "number", "format": "double" }
          },
          {
            "name": "radius",
            "in": "query",
            "description": "Search radius in miles",
            "schema": { "type": "number", "minimum": 0.1, "maximum": 25, "default": 5 }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results to return",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
          }
        ],
        "responses": {
          "200": {
            "description": "List of nearby restaurants with distance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "restaurants": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/RestaurantWithDistance" }
                    }
                  },
                  "required": ["restaurants"]
                }
              }
            },
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/keys": {
      "get": {
        "summary": "List API keys",
        "description": "List all API keys for the authenticated user. Requires Supabase session authentication.",
        "operationId": "listApiKeys",
        "tags": ["API Keys"],
        "security": [{ "CookieAuth": [] }],
        "responses": {
          "200": {
            "description": "List of API keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/ApiKeyInfo" }
                    }
                  },
                  "required": ["keys"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "post": {
        "summary": "Create API key",
        "description": "Create a new API key. Requires an active Pro subscription.",
        "operationId": "createApiKey",
        "tags": ["API Keys"],
        "security": [{ "CookieAuth": [] }],
        "responses": {
          "201": {
            "description": "API key created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "description": "The API key (only shown once)",
                      "example": "sk_live_abc123..."
                    },
                    "name": {
                      "type": "string",
                      "description": "Key name"
                    },
                    "tier": {
                      "type": "string",
                      "description": "API tier"
                    },
                    "dailyLimit": {
                      "type": "integer",
                      "description": "Daily request limit"
                    }
                  },
                  "required": ["key", "name", "tier", "dailyLimit"]
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "Pro subscription required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": {
                  "error": "Pro subscription required",
                  "code": "UNAUTHORIZED"
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/keys/{id}": {
      "delete": {
        "summary": "Delete API key",
        "description": "Revoke and delete an API key.",
        "operationId": "deleteApiKey",
        "tags": ["API Keys"],
        "security": [{ "CookieAuth": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "API key ID",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "description": "API key deleted"
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key in Bearer token format: `Authorization: Bearer sk_live_...`"
      },
      "CookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sb-access-token",
        "description": "Supabase session cookie (for key management endpoints only)"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Maximum requests allowed per window",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Remaining": {
        "description": "Remaining requests in current window",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp when the rate limit resets",
        "schema": { "type": "integer" }
      }
    },
    "schemas": {
      "Restaurant": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Unique restaurant identifier" },
          "name": { "type": "string", "description": "Restaurant name" },
          "address": { "type": "string", "description": "Street address" },
          "city": { "type": "string", "description": "City" },
          "state": { "type": "string", "description": "Two-letter state code" },
          "zip": { "type": "string", "description": "ZIP code" },
          "cuisine": { "type": "string", "nullable": true, "description": "Cuisine type" },
          "neighborhood": { "type": "string", "nullable": true, "description": "Neighborhood name" },
          "score": { "type": "integer", "nullable": true, "minimum": 0, "maximum": 100, "description": "SafeDine Score (0-100)" },
          "grade": { "type": "string", "nullable": true, "enum": ["A", "B", "C", "F", null], "description": "Letter grade derived from score" },
          "trend": { "type": "string", "enum": ["improving", "declining", "stable", "unknown"], "description": "Score trend direction" },
          "confidence": { "type": "string", "enum": ["high", "medium", "low"], "description": "Score confidence level based on inspection data volume" },
          "inspectionCount": { "type": "integer", "description": "Total number of inspections on record" },
          "lastInspectionDate": { "type": "string", "format": "date", "nullable": true, "description": "Date of most recent inspection" },
          "isListing": { "type": "boolean", "description": "True if this is a listing-only restaurant (no inspection data)" },
          "latitude": { "type": "number", "format": "double", "nullable": true, "description": "Latitude coordinate" },
          "longitude": { "type": "number", "format": "double", "nullable": true, "description": "Longitude coordinate" }
        },
        "required": ["id", "name", "address", "city", "state", "zip", "trend", "confidence", "inspectionCount", "isListing"]
      },
      "RestaurantWithDistance": {
        "allOf": [
          { "$ref": "#/components/schemas/Restaurant" },
          {
            "type": "object",
            "properties": {
              "distance": {
                "type": "number",
                "format": "double",
                "description": "Distance in miles from the search coordinates"
              }
            },
            "required": ["distance"]
          }
        ]
      },
      "Inspection": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Inspection record ID" },
          "date": { "type": "string", "format": "date", "description": "Inspection date" },
          "type": { "type": "string", "description": "Inspection type (e.g. Routine, Re-inspection)" },
          "result": { "type": "string", "nullable": true, "description": "Inspection result" },
          "violationCount": { "type": "integer", "description": "Number of violations found" },
          "highPriorityCount": { "type": "integer", "description": "Number of high-priority violations" },
          "violations": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Violation" }
          }
        },
        "required": ["id", "date", "type", "violationCount", "highPriorityCount", "violations"]
      },
      "Violation": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "description": "Violation code" },
          "description": { "type": "string", "description": "Violation description" },
          "severity": { "type": "string", "enum": ["high", "intermediate", "basic"], "description": "Violation severity level" },
          "corrected": { "type": "boolean", "description": "Whether the violation was corrected on-site" }
        },
        "required": ["code", "description", "severity", "corrected"]
      },
      "ApiKeyInfo": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "description": "Key ID" },
          "name": { "type": "string", "description": "Key name" },
          "prefix": { "type": "string", "description": "Key prefix for identification (e.g. sk_live_abc...)", "example": "sk_live_abc..." },
          "tier": { "type": "string", "description": "API tier" },
          "dailyLimit": { "type": "integer", "description": "Daily request limit" },
          "createdAt": { "type": "string", "format": "date-time", "description": "Key creation timestamp" },
          "lastUsedAt": { "type": "string", "format": "date-time", "nullable": true, "description": "Last time the key was used" }
        },
        "required": ["id", "name", "prefix", "tier", "dailyLimit", "createdAt"]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" },
          "code": {
            "type": "string",
            "enum": ["UNAUTHORIZED", "RATE_LIMITED", "NOT_FOUND", "VALIDATION_ERROR", "INTERNAL_ERROR"],
            "description": "Machine-readable error code"
          }
        },
        "required": ["error", "code"]
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Invalid or missing API key", "code": "UNAUTHORIZED" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Restaurant not found", "code": "NOT_FOUND" }
          }
        }
      },
      "ValidationError": {
        "description": "Invalid request parameters",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Invalid state code: must be 2 letters", "code": "VALIDATION_ERROR" }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Rate limit exceeded. Try again in 60 seconds.", "code": "RATE_LIMITED" }
          }
        },
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
          "Retry-After": {
            "description": "Seconds until the rate limit resets",
            "schema": { "type": "integer" }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "An unexpected error occurred", "code": "INTERNAL_ERROR" }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Restaurants",
      "description": "Search, retrieve, and explore restaurant safety data"
    },
    {
      "name": "API Keys",
      "description": "Manage API keys (requires Supabase session authentication)"
    }
  ]
}