{
  "openapi": "3.1.0",
  "info": {
    "title": "snok.ai - publiczne API formularzy",
    "version": "2026-10-07",
    "summary": "Endpointy obslugujace formularze na snok.ai. Nie sa interfejsem integracyjnym.",
    "description": "To NIE jest API produktowe ani integracyjne - to endpointy, ktorych uzywaja formularze\nna stronie snok.ai. Kazdy z nich jest bramkowany: lista dozwolonych zrodel zadania (Origin),\nlimit zgloszen na adres IP oraz - poza punktacja KSC-CHECK - jednorazowy token Cloudflare\nTurnstile, ktory wydawany jest wylacznie formularzowi w przegladarce.\n\nWniosek praktyczny dla agenta: tych endpointow nie da sie wywolac programowo. Wlasciwa\ndroga kontaktu to formularz https://snok.ai/pl/kontakt/ (albo https://snok.ai/en/contact/) lub poczta\noffice@snok.ai. Opis kompetencji i zakresu uslug: https://snok.ai/llms.txt.\n\nSpec istnieje, zeby kontrakt byl jawny i weryfikowalny - nie zeby zapraszac do wywolan.\n\nBledy: jeden model dla wszystkich operacji (components.schemas.Error) - maszynowy `code`,\nkomunikat `error` w jezyku formularza, `hint` dla integratora, pola RFC 9457. Limity: naglowki\nRateLimit i RateLimit-Policy (draft-ietf-httpapi-ratelimit-headers-11) oraz RateLimit-Limit/Remaining/Reset\nna kazdej odpowiedzi endpointu z limitem, Retry-After na 429. Licznik limitu dziala w pamieci instancji\nfunkcji serwerowej: przy kilku instancjach rzeczywista przepustowosc moze byc wyzsza niz deklarowana -\ndeklaracja jest gorna granica, ktorej agent powinien sie trzymac.\nWersjonowanie: naglowek API-Version (data, biezaca 2026-10-07) na kazdej odpowiedzi JSON i opcjonalnie w zadaniu;\nszczegoly w x-versioning-policy. Dokumentacja dla czlowieka: https://snok.ai/en/developers/ (PL: https://snok.ai/pl/dla-deweloperow/).",
    "contact": {
      "name": "SNOK Sp. z o.o.",
      "email": "office@snok.ai",
      "url": "https://snok.ai/pl/kontakt/"
    },
    "license": {
      "name": "Wszelkie prawa zastrzezone",
      "url": "https://snok.ai/pl/polityka-prywatnosci/"
    }
  },
  "servers": [
    {
      "url": "https://snok.ai",
      "description": "Produkcja"
    }
  ],
  "tags": [
    {
      "name": "formularze",
      "description": "Endpointy formularzy strony, bramkowane Turnstile i kontrola Origin."
    },
    {
      "name": "narzedzia",
      "description": "Bezplatne narzedzia SNOK dostepne na stronie."
    },
    {
      "name": "materialy",
      "description": "Raporty, ksiazki i analizy SNOK udostepniane po podaniu danych kontaktowych."
    }
  ],
  "paths": {
    "/api/contact": {
      "post": {
        "operationId": "submitContactForm",
        "tags": [
          "formularze"
        ],
        "summary": "Zgloszenie z formularza kontaktowego",
        "description": "Wysyla zapytanie do SNOK i tworzy lead w CRM. Wymaga tokenu Turnstile z formularza w przegladarce oraz dozwolonego naglowka Origin. Zgoda na przetworzenie danych osobowych jest wymuszana po stronie serwera (RODO art. 7).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "firstName",
                  "lastName",
                  "company",
                  "email",
                  "area",
                  "gdpr"
                ],
                "properties": {
                  "firstName": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Imie."
                  },
                  "lastName": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Nazwisko."
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Nazwa organizacji."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 500
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Rola w organizacji."
                  },
                  "area": {
                    "type": "string",
                    "enum": [
                      "sap",
                      "automation",
                      "custom-dev",
                      "advisory",
                      "career",
                      "general"
                    ],
                    "description": "Obszar zapytania: sap (Bezpieczenstwo i Technologia SAP), automation (Inteligentna Automatyzacja i AI), custom-dev (Custom Development i Dane), advisory (Doradztwo i Integracja IT), career (Kariera), general (Konsultacja ogolna)."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "gdpr": {
                    "type": "string",
                    "description": "Zgoda na przetwarzanie danych osobowych - wartosc \"on\", \"true\" albo \"1\". Wymagana; egzekwowana po stronie serwera (RODO art. 7)."
                  },
                  "marketing": {
                    "type": "string",
                    "description": "Opcjonalna zgoda marketingowa, te same wartosci."
                  },
                  "cf-turnstile-response": {
                    "type": "string",
                    "description": "Token Cloudflare Turnstile z formularza. Bez waznego tokenu serwer zwraca blad 400 verification_failed."
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Przyjete albo odrzucone po cichu - patrz opis schematu. Limit: 5 zadan na 10 min z jednego adresu IP.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "description": "Zgloszenie przyjete. UWAGA: status 200 z ok:true oznacza takze zadanie odrzucone po cichu przez kontrole antyspamowe (wypelniona puapka, zbyt szybkie wyslanie, brak znacznika czasu). Cisza jest celowa - nie odpowiadamy skryptowi, czy jego zgloszenie przeszlo. Wygasniecie formularza (ponad 30 minut) i niewazny token Turnstile zwracaja jawny blad 400 (form_expired, verification_failed)."
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kody: missing_fields, invalid_email, consent_required, invalid_answers, invalid_linkedin, invalid_file, unsupported_version (naglowek API-Version z data pozniejsza niz biezaca albo spoza formatu).",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod origin_forbidden - zadanie z niedozwolonego zrodla (kontrola Origin).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod not_found - sciezka pod /api/ nie odpowiada zadnej operacji (odpowiada warstwa brzegowa).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "405": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod method_not_allowed - metoda spoza kontraktu; naglowek Allow wymienia dozwolone.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "Dozwolone metody dla tej sciezki."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod rate_limited. Retry-After podaje, po ilu sekundach ponowic.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sekundy do ponowienia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod internal_error.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod service_unavailable - usluga poczty niedostepna, zgloszenie NIE zostalo przyjete.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/api/apply": {
      "post": {
        "operationId": "submitJobApplication",
        "tags": [
          "formularze"
        ],
        "summary": "Zgloszenie rekrutacyjne wraz z CV",
        "description": "Formularz kariery. Przyjmuje wylacznie multipart/form-data, bo obejmuje plik CV (PDF, DOC albo DOCX, do 5 MB, weryfikowany takze po sygnaturze pliku, nie po rozszerzeniu). Dane kandydatow nie trafiaja do CRM - inny cel przetwarzania.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "firstName",
                  "lastName",
                  "email",
                  "q1",
                  "q2",
                  "q3",
                  "consent"
                ],
                "properties": {
                  "rola": {
                    "type": "string",
                    "description": "Stanowisko, o ktore aplikuje kandydat."
                  },
                  "firstName": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Imie. Zamiast pary firstName/lastName serwer przyjmuje tez pojedyncze pole \"name\"."
                  },
                  "lastName": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 500
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "linkedin": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Adres profilu w domenie linkedin.com."
                  },
                  "q1": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Odpowiedz na pierwsze pytanie rekrutacyjne z formularza. Wymagana."
                  },
                  "q2": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Odpowiedz na drugie pytanie. Wymagana."
                  },
                  "q3": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Odpowiedz na trzecie pytanie. Wymagana."
                  },
                  "notice": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Okres wypowiedzenia / dostepnosc."
                  },
                  "cv": {
                    "type": "string",
                    "format": "binary",
                    "description": "Plik CV: PDF, DOC albo DOCX, maksymalnie 5 MB."
                  },
                  "consent": {
                    "type": "string",
                    "description": "Zgoda na przetwarzanie danych w procesie rekrutacji. Wymagana; akceptowana takze pod nazwa \"gdpr\"."
                  },
                  "cf-turnstile-response": {
                    "type": "string",
                    "description": "Token Cloudflare Turnstile z formularza."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Przyjete albo odrzucone po cichu. Limit: 3 zadan na 30 min z jednego adresu IP.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "description": "Zgloszenie przyjete. UWAGA: status 200 z ok:true oznacza takze zadanie odrzucone po cichu przez kontrole antyspamowe (wypelniona puapka, zbyt szybkie wyslanie, brak znacznika czasu). Cisza jest celowa - nie odpowiadamy skryptowi, czy jego zgloszenie przeszlo. Wygasniecie formularza (ponad 30 minut) i niewazny token Turnstile zwracaja jawny blad 400 (form_expired, verification_failed)."
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kody: missing_fields, invalid_email, consent_required, invalid_answers, invalid_linkedin, invalid_file, unsupported_version (naglowek API-Version z data pozniejsza niz biezaca albo spoza formatu).",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod origin_forbidden - zadanie z niedozwolonego zrodla (kontrola Origin).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod not_found - sciezka pod /api/ nie odpowiada zadnej operacji (odpowiada warstwa brzegowa).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "405": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod method_not_allowed - metoda spoza kontraktu; naglowek Allow wymienia dozwolone.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "Dozwolone metody dla tej sciezki."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod rate_limited. Retry-After podaje, po ilu sekundach ponowic.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sekundy do ponowienia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod internal_error.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod service_unavailable.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/api/ksc-check/score": {
      "post": {
        "operationId": "scoreKscCheck",
        "tags": [
          "narzedzia"
        ],
        "summary": "Punktacja badania SNOK KSC-CHECK",
        "description": "Liczy wynik gotowosci srodowiska SAP na wymogi KSC i NIS2 na podstawie odpowiedzi. Bez danych osobowych, bez zapisu, bez wysylki poczty - i dlatego bez Turnstile. Punktacja zostaje po stronie serwera. Raport PDF obsluguje /api/ksc-check/submit.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "answers"
                ],
                "properties": {
                  "answers": {
                    "type": "object",
                    "description": "Mapa: identyfikator pytania -> INDEKS wybranej opcji (liczba calkowita od zera). Odpowiedzi o nieznanym identyfikatorze albo indeksie poza zakresem sa pomijane, nie odrzucane. Przy application/x-www-form-urlencoded to samo jako JSON zakodowany w polu tekstowym.",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "lang": {
                    "type": "string",
                    "enum": [
                      "pl",
                      "en"
                    ],
                    "default": "pl"
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wynik badania - poziom gotowosci i punktacja obszarow. Limit: 12 zadan na 15 min z jednego adresu IP.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "result"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "result": {
                      "type": "object",
                      "properties": {
                        "score": {
                          "type": "number"
                        },
                        "level": {
                          "type": "string"
                        },
                        "areas": {
                          "type": "array"
                        },
                        "weakestAreas": {
                          "type": "array"
                        },
                        "unanswered": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kody: missing_fields, invalid_email, consent_required, invalid_answers, invalid_linkedin, invalid_file, unsupported_version (naglowek API-Version z data pozniejsza niz biezaca albo spoza formatu).",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod origin_forbidden - zadanie z niedozwolonego zrodla (kontrola Origin).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod not_found - sciezka pod /api/ nie odpowiada zadnej operacji (odpowiada warstwa brzegowa).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "405": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod method_not_allowed - metoda spoza kontraktu; naglowek Allow wymienia dozwolone.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "Dozwolone metody dla tej sciezki."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod rate_limited. Retry-After podaje, po ilu sekundach ponowic.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sekundy do ponowienia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod internal_error.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/api/ksc-check/submit": {
      "post": {
        "operationId": "requestKscCheckReport",
        "tags": [
          "narzedzia"
        ],
        "summary": "Wysylka raportu PDF z badania KSC-CHECK",
        "description": "Generuje raport PDF z wynikami badania i wysyla go na podany adres, tworzac lead w CRM. To jedyny krok narzedzia, ktory zapisuje dane osobowe - dlatego tutaj wymagany jest token Turnstile oraz zgoda na przetwarzanie danych.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "answers",
                  "email",
                  "gdpr"
                ],
                "properties": {
                  "answers": {
                    "type": "object",
                    "description": "Jak w /api/ksc-check/score: identyfikator pytania -> indeks opcji.",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 500
                  },
                  "fullName": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "orgSize": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Wielkosc organizacji (wartosc z listy w formularzu)."
                  },
                  "sector": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "phoneConsent": {
                    "type": "string",
                    "description": "Zgoda na kontakt telefoniczny - \"on\", \"true\" albo \"1\"."
                  },
                  "marketing": {
                    "type": "string",
                    "description": "Opcjonalna zgoda marketingowa."
                  },
                  "lang": {
                    "type": "string",
                    "enum": [
                      "pl",
                      "en"
                    ],
                    "default": "pl"
                  },
                  "gdpr": {
                    "type": "string",
                    "description": "Prosba o przygotowanie i przeslanie raportu, czyli podstawa wysylki. Wymagana."
                  },
                  "cf-turnstile-response": {
                    "type": "string",
                    "description": "Token Cloudflare Turnstile z formularza."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Raport wyslany albo zadanie odrzucone po cichu. Limit: 3 zadan na 15 min z jednego adresu IP.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "description": "Zgloszenie przyjete. UWAGA: status 200 z ok:true oznacza takze zadanie odrzucone po cichu przez kontrole antyspamowe (wypelniona puapka, zbyt szybkie wyslanie, brak znacznika czasu). Cisza jest celowa - nie odpowiadamy skryptowi, czy jego zgloszenie przeszlo. Wygasniecie formularza (ponad 30 minut) i niewazny token Turnstile zwracaja jawny blad 400 (form_expired, verification_failed)."
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kody: missing_fields, invalid_email, consent_required, invalid_answers, invalid_linkedin, invalid_file, unsupported_version (naglowek API-Version z data pozniejsza niz biezaca albo spoza formatu).",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod origin_forbidden - zadanie z niedozwolonego zrodla (kontrola Origin).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod not_found - sciezka pod /api/ nie odpowiada zadnej operacji (odpowiada warstwa brzegowa).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "405": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod method_not_allowed - metoda spoza kontraktu; naglowek Allow wymienia dozwolone.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "Dozwolone metody dla tej sciezki."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod rate_limited. Retry-After podaje, po ilu sekundach ponowic.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sekundy do ponowienia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod internal_error.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/api/materials/request": {
      "post": {
        "operationId": "requestGatedMaterial",
        "tags": [
          "materialy"
        ],
        "summary": "Zamowienie materialu PDF (raport, ksiazka, analiza) udostepnianego po podaniu danych",
        "description": "Wysyla na podany adres podpisany link do pobrania materialu, wazny przez 7 dni, i tworzy lead w CRM. Plik nie jest zalacznikiem i nie ma publicznego adresu. Wymagany token Turnstile oraz prosba o przeslanie materialu; zgoda marketingowa jest opcjonalna i nie warunkuje wysylki.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "materialId",
                  "fullName",
                  "email",
                  "company",
                  "gdpr"
                ],
                "properties": {
                  "materialId": {
                    "type": "string",
                    "description": "Identyfikator materialu z formularza na stronie."
                  },
                  "fullName": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 500
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "marketing": {
                    "type": "string",
                    "description": "Opcjonalna zgoda marketingowa."
                  },
                  "lang": {
                    "type": "string",
                    "enum": [
                      "pl",
                      "en"
                    ],
                    "default": "pl"
                  },
                  "gdpr": {
                    "type": "string",
                    "description": "Prosba o przeslanie materialu, czyli podstawa wysylki. Wymagana."
                  },
                  "cf-turnstile-response": {
                    "type": "string",
                    "description": "Token Cloudflare Turnstile z formularza."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link wyslany albo zadanie odrzucone po cichu. Limit: 5 zadan na 15 min z jednego adresu IP.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  },
                  "description": "Zgloszenie przyjete. UWAGA: status 200 z ok:true oznacza takze zadanie odrzucone po cichu przez kontrole antyspamowe (wypelniona puapka, zbyt szybkie wyslanie, brak znacznika czasu). Cisza jest celowa - nie odpowiadamy skryptowi, czy jego zgloszenie przeszlo. Wygasniecie formularza (ponad 30 minut) i niewazny token Turnstile zwracaja jawny blad 400 (form_expired, verification_failed)."
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kody: missing_fields, invalid_email, consent_required, invalid_answers, invalid_linkedin, invalid_file, unsupported_version (naglowek API-Version z data pozniejsza niz biezaca albo spoza formatu).",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod origin_forbidden - zadanie z niedozwolonego zrodla (kontrola Origin).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod not_found - sciezka pod /api/ nie odpowiada zadnej operacji (odpowiada warstwa brzegowa).",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "405": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod method_not_allowed - metoda spoza kontraktu; naglowek Allow wymienia dozwolone.",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string"
                },
                "description": "Dozwolone metody dla tej sciezki."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod business_email_required - adres z domeny poczty prywatnej albo jednorazowej; materialy wysylamy tylko na adresy sluzbowe.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod rate_limited. Retry-After podaje, po ilu sekundach ponowic.",
            "headers": {
              "RateLimit": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
              },
              "RateLimit-Policy": {
                "schema": {
                  "type": "string"
                },
                "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
              },
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan wolno w oknie."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zadan zostalo w biezacym oknie."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund okno sie odnawia."
              },
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Sekundy do ponowienia."
              },
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Kod internal_error.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ]
      }
    },
    "/api/materials/download": {
      "get": {
        "operationId": "downloadGatedMaterial",
        "tags": [
          "materialy"
        ],
        "summary": "Pobranie materialu z podpisanego linku z wiadomosci e-mail",
        "description": "Zwraca plik PDF dla waznego tokenu z /api/materials/request. Odbiorca jest czlowiek, wiec bledy (link wygasly 410, nieprawidlowy 403, material wycofany 404) wracaja jako strona HTML, nie JSON.",
        "parameters": [
          {
            "name": "t",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Podpisany token z wiadomosci e-mail."
          },
          {
            "$ref": "#/components/parameters/ApiVersion"
          }
        ],
        "responses": {
          "200": {
            "description": "Plik PDF.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "403": {
            "description": "Token nieprawidlowy - strona HTML.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "404": {
            "description": "Material niedostepny - strona HTML.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          },
          "410": {
            "description": "Link wygasl - strona HTML.",
            "headers": {
              "API-Version": {
                "schema": {
                  "type": "string",
                  "format": "date",
                  "example": "2026-10-07"
                },
                "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "ok",
          "code",
          "error",
          "hint",
          "type",
          "title",
          "status"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Zawsze false w odpowiedzi bledu."
          },
          "code": {
            "type": "string",
            "enum": [
              "origin_forbidden",
              "rate_limited",
              "consent_required",
              "missing_fields",
              "invalid_email",
              "business_email_required",
              "invalid_linkedin",
              "invalid_file",
              "invalid_answers",
              "material_unavailable",
              "form_expired",
              "verification_failed",
              "service_unavailable",
              "method_not_allowed",
              "not_found",
              "unsupported_version",
              "internal_error"
            ],
            "description": "Stabilny identyfikator bledu do obslugi programowej. Nie tlumaczony, nie zmienia sie miedzy jezykami."
          },
          "error": {
            "type": "string",
            "description": "Komunikat dla czlowieka, w jezyku formularza (PL albo EN)."
          },
          "hint": {
            "type": "string",
            "description": "Co zrobic, zeby zadanie przeszlo - dla integratora, po angielsku."
          },
          "type": {
            "type": "string",
            "format": "uri",
            "description": "RFC 9457: adres opisu tego kodu w niniejszej specyfikacji."
          },
          "title": {
            "type": "string",
            "description": "RFC 9457: krotka nazwa klasy bledu."
          },
          "status": {
            "type": "integer",
            "description": "RFC 9457: kod HTTP powtorzony w ciele."
          }
        }
      },
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "description": "Zgloszenie przyjete. UWAGA: status 200 z ok:true oznacza takze zadanie odrzucone po cichu przez kontrole antyspamowe (wypelniona puapka, zbyt szybkie wyslanie, brak znacznika czasu). Cisza jest celowa - nie odpowiadamy skryptowi, czy jego zgloszenie przeszlo. Wygasniecie formularza (ponad 30 minut) i niewazny token Turnstile zwracaja jawny blad 400 (form_expired, verification_failed)."
      }
    },
    "headers": {
      "RateLimit": {
        "schema": {
          "type": "string"
        },
        "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";r=<pozostalo>;t=<sekundy do odnowienia okna>."
      },
      "RateLimit-Policy": {
        "schema": {
          "type": "string"
        },
        "description": "draft-ietf-httpapi-ratelimit-headers-11 (Structured Field): \"ip\";q=<limit>;w=<dlugosc okna w sekundach>."
      },
      "RateLimit-Limit": {
        "schema": {
          "type": "integer"
        },
        "description": "Ile zadan wolno w oknie."
      },
      "RateLimit-Remaining": {
        "schema": {
          "type": "integer"
        },
        "description": "Ile zadan zostalo w biezacym oknie."
      },
      "RateLimit-Reset": {
        "schema": {
          "type": "integer"
        },
        "description": "Za ile sekund okno sie odnawia."
      },
      "API-Version": {
        "schema": {
          "type": "string",
          "format": "date",
          "example": "2026-10-07"
        },
        "description": "Wersja kontraktu, ktora obsluzyla zadanie (rowna info.version)."
      }
    },
    "parameters": {
      "ApiVersion": {
        "name": "API-Version",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "format": "date",
          "example": "2026-10-07"
        },
        "description": "Opcjonalne przypiecie wersji kontraktu (data RRRR-MM-DD). Brak naglowka = wersja biezaca. Data nie pozniejsza niz 2026-10-07 jest obslugiwana przez wersje biezaca. Data pozniejsza albo wartosc spoza formatu: 400 unsupported_version."
      }
    }
  },
  "x-versioning-policy": {
    "scheme": "date-in-header",
    "header": "API-Version",
    "current": "2026-10-07",
    "request": "Opcjonalny. Brak = wersja biezaca; data <= biezacej = obsluzona wersja biezaca; pozniejsza albo spoza formatu = 400 unsupported_version.",
    "response": "Kazda odpowiedz JSON spod /api/ niesie API-Version: <wersja biezaca>; wyjatkiem jest 403 text/plain z ochrony CSRF frameworka dla POST bez naglowka Origin.",
    "compatibility": "Pola odpowiedzi sa tylko DODAWANE. Istniejace pola (ok, error) i ich znaczenie nie zmieniaja sie bez nowej sciezki.",
    "deprecation": "Zmiana lamiaca dostaje nowa sciezke; stara odpowiada przez co najmniej 90 dni z naglowkami Deprecation (RFC 9745) i Sunset (RFC 8594) oraz wpisem w tej specyfikacji. Dzis zadna operacja nie jest wycofywana.",
    "changelog": "https://snok.ai/openapi.json - pole info.version; historia zmian kodu w repozytorium strony."
  },
  "externalDocs": {
    "url": "https://snok.ai/en/developers/",
    "description": "Dokumentacja dla deweloperow i agentow: zasoby maszynowe, uwierzytelnienie, przyklady. Przewodnik po kompetencjach SNOK: /llms.txt."
  }
}