{
  "openapi": "3.1.0",
  "info": {
    "title": "Auto Serwis Mińska 37 – Public REST API",
    "version": "1.0.0",
    "summary": "Wersjonowane publiczne REST API (v1) warsztatu Auto Serwis Mińska 37 w Warszawie",
    "description": "Publiczne endpointy umożliwiające sprawdzenie zajętych terminów w kalendarzu warsztatu, odczyt bieżącego statusu naprawy pojazdu po kodzie M37 (w tym kodów testowych Sandbox M37-TEST1 i M37-TEST2) oraz weryfikację i przesyłanie zgłoszeń serwisowych online.\n\nPolityka wersjonowania i wycofywania (Versioning & Deprecation Policy): Stabilne endpointy udostępniane są pod ścieżką /api/v1/* oraz wspierają nagłówek X-API-Version (odpowiedzi zawierają nagłówki API-Version: v1 i X-API-Version: 1.0.0). Zmiany niekompatybilne wstecznie będą publikowane wyłącznie pod nową ścieżką (np. /api/v2/*), a ewentualne wycofanie starszej wersji ogłaszane jest z minimum 6-miesięcznym wyprzedzeniem poprzez standardowe nagłówki HTTP RFC 8594 Deprecation oraz Sunset.\n\nLimity zapytań (Rate Limiting): Limit wynosi 60 zapytań na 60 sekund na adres IP. Każda odpowiedź zwraca nagłówki RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset oraz RateLimit-Policy, a odpowiedź HTTP 429 zawiera dodatkowo nagłówek Retry-After.",
    "contact": {
      "name": "Auto Serwis Mińska 37",
      "url": "https://testmm1.netlify.app/contact",
      "email": "kontakt@minska37.pl"
    }
  },
  "externalDocs": {
    "description": "Developer Portal, dokumentacja integracyjna i środowisko Sandbox",
    "url": "https://testmm1.netlify.app/developers"
  },
  "x-versioning-policy": {
    "strategy": "url-path-and-header",
    "basePath": "/api/v1",
    "header": "X-API-Version",
    "deprecationHeaders": ["Deprecation", "Sunset", "Link"],
    "minimumNoticeDays": 180
  },
  "servers": [
    {
      "url": "https://testmm1.netlify.app",
      "description": "Serwer produkcyjny"
    },
    {
      "url": "http://localhost:3000",
      "description": "Serwer lokalny / podgląd deweloperski"
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "Pobierz indeks API v1, status usługi i metadane polityki wersjonowania",
        "description": "Zwraca status działania API, aktywną wersję, limity zapytań, kody środowiska Sandbox oraz listę dostępnych endpointów.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Metadane API v1 zwrócone pomyślnie",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/ApiVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndexResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/busy-slots": {
      "get": {
        "operationId": "getBusySlots",
        "summary": "Pobierz zajęte terminy kalendarza warsztatu",
        "description": "Zwraca listę zajętych slotów godzinowych w formacie YYYY-MM-DD HH:mm. Warsztat przyjmuje pojazdy od poniedziałku do piątku w godzinach 08:00–16:00 (co pełną godzinę). Endpoint nie wymaga klucza API.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Lista zajętych terminów zwrócona pomyślnie",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/ApiVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BusySlotsResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "Błąd komunikacji z systemem kalendarza",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/repair-status": {
      "get": {
        "operationId": "getRepairStatus",
        "summary": "Sprawdź status naprawy pojazdu po kodzie śledzenia",
        "description": "Zwraca bieżący etap naprawy pojazdu (indeks 0–5 oraz nazwę etapu) przypisany do kodu śledzenia otrzymanego przez klienta (np. M37-0018) lub kodu testowego Sandbox (M37-TEST1, M37-TEST2). Endpoint nie wymaga klucza API.",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "description": "Indywidualny kod śledzenia naprawy (np. M37-TEST1 lub M37-0018).",
            "schema": {
              "type": "string",
              "minLength": 4,
              "maxLength": 32,
              "default": "M37-TEST1",
              "example": "M37-TEST1"
            }
          },
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Aktualny status naprawy pojazdu",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/ApiVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RepairStatusResponse"
                }
              }
            }
          },
          "400": {
            "description": "Brak lub nieprawidłowy format parametru code",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Nie znaleziono zgłoszenia o podanym kodzie śledzenia",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/siteverify": {
      "post": {
        "operationId": "verifyTurnstileToken",
        "summary": "Zweryfikuj token zabezpieczający Cloudflare Turnstile",
        "description": "Weryfikuje jednorazowy token widżetu Cloudflare Turnstile wygenerowany w przeglądarce użytkownika przed wysłaniem formularza rezerwacji.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TurnstileVerifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wynik weryfikacji tokenu Turnstile",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/ApiVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TurnstileVerifyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Brak wymaganego tokenu lub niepoprawny format JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/v1/submit": {
      "post": {
        "operationId": "submitServiceRequest",
        "summary": "Wyślij rezerwację terminu lub zgłoszenie usterki",
        "description": "Przekazuje publiczne zgłoszenie serwisowe lub prośbę o rezerwację wizyty. Wymaga poprawnych danych kontaktowych, danych pojazdu oraz ważnego tokenu Cloudflare Turnstile z formularza na stronie /formularz.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersionHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServiceSubmissionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Zgłoszenie zostało przyjęte do przetworzenia",
            "headers": {
              "API-Version": { "$ref": "#/components/headers/ApiVersion" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceSubmissionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowe dane wejściowe lub brak wymaganych pól zgłoszenia",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ApiVersionHeader": {
        "name": "X-API-Version",
        "in": "header",
        "required": false,
        "description": "Opcjonalny nagłówek wersji kontraktu REST API (domyślnie 1.0.0 / v1).",
        "schema": {
          "type": "string",
          "default": "1.0.0",
          "enum": ["1.0.0", "v1"]
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "Aktywna główna wersja REST API",
        "schema": { "type": "string", "example": "v1" }
      },
      "RateLimitLimit": {
        "description": "Maksymalna liczba zapytań w oknie czasowym (RFC RateLimit)",
        "schema": { "type": "integer", "example": 60 }
      },
      "RateLimitRemaining": {
        "description": "Pozostała liczba zapytań w bieżącym oknie czasowym",
        "schema": { "type": "integer", "example": 59 }
      },
      "RateLimitReset": {
        "description": "Liczba sekund do zresetowania limitu zapytań",
        "schema": { "type": "integer", "example": 60 }
      },
      "RetryAfter": {
        "description": "Liczba sekund oczekiwania przed ponowieniem zapytania po kodzie 429",
        "schema": { "type": "integer", "example": 30 }
      }
    },
    "responses": {
      "TooManyRequests": {
        "description": "Przekroczono limit 60 zapytań na 60 sekund",
        "headers": {
          "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "Retry-After": { "$ref": "#/components/headers/RetryAfter" }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "ApiIndexResponse": {
        "type": "object",
        "required": ["status", "service", "version", "semver", "endpoints"],
        "properties": {
          "status": { "type": "string", "enum": ["ok"] },
          "service": { "type": "string", "example": "Auto Serwis Mińska 37 – Public REST API" },
          "version": { "type": "string", "example": "v1" },
          "semver": { "type": "string", "example": "1.0.0" },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["method", "path", "summary"],
              "properties": {
                "method": { "type": "string" },
                "path": { "type": "string" },
                "summary": { "type": "string" }
              }
            }
          }
        }
      },
      "BusySlotsResponse": {
        "type": "object",
        "required": ["status", "busySlots"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["ok"]
          },
          "busySlots": {
            "type": "array",
            "description": "Zajęte terminy w formacie YYYY-MM-DD HH:mm",
            "items": {
              "type": "string",
              "example": "2026-11-03 10:00"
            }
          },
          "openingHours": {
            "type": "object",
            "properties": {
              "days": {
                "type": "string",
                "example": "Monday-Friday"
              },
              "slots": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": ["08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00"]
              }
            }
          }
        }
      },
      "RepairStatusResponse": {
        "type": "object",
        "required": ["status", "code", "statusIndex", "stageName"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["ok"]
          },
          "code": {
            "type": "string",
            "example": "M37-TEST1"
          },
          "statusIndex": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5,
            "example": 2
          },
          "stageName": {
            "type": "string",
            "example": "W trakcie naprawy"
          },
          "car": {
            "type": "string",
            "example": "BMW M3 (2021) - Diagnoza silnika"
          }
        }
      },
      "TurnstileVerifyRequest": {
        "type": "object",
        "required": ["token"],
        "properties": {
          "token": {
            "type": "string",
            "description": "Jednorazowy token z widżetu Cloudflare Turnstile"
          }
        }
      },
      "TurnstileVerifyResponse": {
        "type": "object",
        "required": ["success"],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "error-codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ServiceSubmissionRequest": {
        "type": "object",
        "required": ["type", "category", "brand", "clientName", "clientEmail", "phone"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["book", "fault"],
            "description": "book = rezerwacja konkretnego terminu, fault = zgłoszenie usterki"
          },
          "category": {
            "type": "string",
            "example": "Diagnostyka komputerowa"
          },
          "brand": {
            "type": "string",
            "example": "Audi A4 B9"
          },
          "year": {
            "type": "string",
            "example": "2019"
          },
          "description": {
            "type": "string",
            "maxLength": 350
          },
          "clientName": {
            "type": "string",
            "example": "Jan Kowalski"
          },
          "clientEmail": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "example": "+48 600 000 000"
          },
          "date": {
            "type": "string",
            "description": "Data wizyty YYYY-MM-DD (wymagana dla type=book)",
            "example": "2026-11-05"
          },
          "time": {
            "type": "string",
            "description": "Godzina wizyty HH:mm (wymagana dla type=book)",
            "example": "10:00"
          },
          "turnstileToken": {
            "type": "string",
            "description": "Token weryfikacji Cloudflare Turnstile"
          }
        }
      },
      "ServiceSubmissionResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "trackingCode": {
            "type": "string",
            "example": "M37-0042"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["status", "code", "error", "message", "hint"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["error"]
          },
          "code": {
            "type": "string",
            "description": "Stabilny maszynowy kod błędu",
            "example": "INVALID_REQUEST"
          },
          "error": {
            "type": "string",
            "description": "Zwięzły opis błędu"
          },
          "message": {
            "type": "string",
            "description": "Czytelne wyjaśnienie przyczyny błędu"
          },
          "hint": {
            "type": "string",
            "description": "Wskazówka jak poprawić zapytanie"
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "example": "https://testmm1.netlify.app/docs"
          },
          "openapi": {
            "type": "string",
            "format": "uri",
            "example": "https://testmm1.netlify.app/openapi.json"
          }
        }
      }
    }
  }
}
