{
  "openapi": "3.1.0",
  "info": {
    "title": "DebtStrike Rejestry API",
    "version": "1.0.0",
    "description": "Jedno API do polskich rejestrów publicznych: **KRS**, **biała lista VAT**, **REGON/GUS** (z fallbackiem CEIDG), **CRBR**, **BZP** i **SUDOP**. Jedna koperta odpowiedzi dla wszystkich źródeł, limity rejestrów trzymane po naszej stronie, audytowalne znaczniki czasu i gotowa atrybucja wymagana ustawą o otwartych danych.\n\n### Czym to się różni od proxy na rejestry\n\n**Rozróżniamy „rejestr odpowiedział, że nie ma” od „rejestr nie odpowiedział”.** Pierwsze to `status: not_found` — fakt, na którym możesz oprzeć decyzję. Drugie to `status: unavailable` — brak wiedzy, na którym opierać decyzji NIE WOLNO. Zwykłe proxy zwraca w obu przypadkach pustkę i zostawia klienta z pytaniem, co ona znaczy.\n\nDlatego HTTP `200` opisuje wyłącznie transport: „zapytanie dotarło i zostało obsłużone”. O danych mówi pole `status` w kopercie.\n\n### Jak liczymy zapytania\n\n**Jedno zapytanie = jedna jednostka pakietu.** Bez mnożników — za źródło, za świeżość, za certyfikat. Płacisz za ODPOWIEDŹ REJESTRU, nie za próbę:\n\n- `found` i `not_found` → **1** (brak wpisu to też informacja, i to kosztowała nas tak samo),\n- `unavailable` → **0** (nie obciążamy za cudzą awarię),\n- błędy `4xx`, `5xx` i `429` → **0** (nauka integracji jest darmowa),\n- zapytanie o N źródeł (agregat, paczka) → **N**,\n- certyfikat → **0** dodatkowo,\n- zapytanie kluczem testowym `ds_test_` → **0** (klucze testowe nie zużywają pakietu i mają osobny limit dzienny: 500/dobę, 2 zapytania/s).\n\nKażda odpowiedź niesie `meta.billed` i nagłówki `RateLimit-Quota-*`, więc stanu pakietu nie trzeba nigdzie doliczać ani zgadywać.\n\n### Wersjonowanie\n\nWersja siedzi w ścieżce (`/v1`). W obrębie `v1` **dodajemy** pola i wartości `warnings`, ale nie usuwamy pól ani nie zmieniamy znaczenia istniejących. Kody błędów (`code`) są stabilne. Zmiana łamiąca kontrakt = `/v2` obok, nie podmiana `/v1`.",
    "contact": {
      "name": "DebtStrike",
      "email": "kontakt@debtstrike.eu"
    },
    "license": {
      "name": "Warunki korzystania z Rejestry API",
      "url": "https://debtstrike.eu/regulamin-api"
    }
  },
  "servers": [
    {
      "url": "https://api.debtstrike.eu"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Podmiot",
      "description": "Agregat i katalog źródeł — punkt wejścia dla większości integracji."
    },
    {
      "name": "KRS",
      "description": "Krajowy Rejestr Sądowy (Ministerstwo Sprawiedliwości)."
    },
    {
      "name": "VAT",
      "description": "Biała lista podatników VAT (Ministerstwo Finansów) — status podmiotu i weryfikacja rachunków."
    },
    {
      "name": "REGON",
      "description": "Rejestr REGON / GUS BIR, z fallbackiem na CEIDG dla JDG."
    },
    {
      "name": "CRBR",
      "description": "Centralny Rejestr Beneficjentów Rzeczywistych."
    },
    {
      "name": "BZP",
      "description": "Biuletyn Zamówień Publicznych — własny indeks ogłoszeń."
    },
    {
      "name": "SUDOP",
      "description": "Pomoc publiczna i de minimis (UOKiK)."
    },
    {
      "name": "Certyfikaty",
      "description": "Dowód treści odpowiedzi (płatny dodatek)."
    },
    {
      "name": "Stan usługi",
      "description": "Endpointy bez klucza — do monitoringu."
    },
    {
      "name": "Dostęp",
      "description": "Wniosek o klucz. Bez uwierzytelnienia — z definicji składa go ktoś, kto klucza nie ma."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Klucz API: `Authorization: Bearer ds_live_…`"
      }
    },
    "schemas": {
      "RegistryEnvelope": {
        "type": "object",
        "required": [
          "apiVersion",
          "requestId",
          "status",
          "subject",
          "source",
          "data",
          "meta",
          "timestamps",
          "warnings"
        ],
        "properties": {
          "apiVersion": {
            "type": "string",
            "enum": ["v1"]
          },
          "requestId": {
            "type": "string",
            "description": "Identyfikator zapytania — do reklamacji i audytu."
          },
          "status": {
            "type": "string",
            "enum": [
              "found",
              "not_found",
              "unavailable",
              "forbidden",
              "invalid_input"
            ],
            "description": "Stan DANYCH, nie transportu. `not_found` = rejestr odpowiedział, że podmiotu nie ma. `unavailable` = rejestr nie odpowiedział (nie wiemy)."
          },
          "subject": {
            "type": "object",
            "properties": {
              "nip": {
                "type": ["string", "null"]
              },
              "regon": {
                "type": ["string", "null"]
              },
              "krs": {
                "type": ["string", "null"]
              },
              "countryCode": {
                "type": "string",
                "enum": ["PL"]
              },
              "resolvedVia": {
                "type": ["string", "null"],
                "enum": ["direct", "mf_whitelist", "gus_bir", null]
              }
            }
          },
          "source": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "authority": {
                "type": "string"
              },
              "official": {
                "type": "boolean"
              },
              "endpoint": {
                "type": "string"
              },
              "availability": {
                "type": "string",
                "enum": ["ga", "beta", "planned"]
              }
            }
          },
          "data": {
            "type": ["object", "null"],
            "description": "Dane źródła; `null` zawsze, gdy `status` ≠ `found`."
          },
          "meta": {
            "type": "object",
            "properties": {
              "cache": {
                "type": "object",
                "properties": {
                  "hit": {
                    "type": "boolean"
                  },
                  "storedAt": {
                    "type": ["string", "null"],
                    "format": "date-time"
                  },
                  "expiresAt": {
                    "type": ["string", "null"],
                    "format": "date-time"
                  }
                }
              },
              "stale": {
                "type": "boolean",
                "description": "true = rejestr niedostępny, zwrócono ostatnie znane dane."
              },
              "degraded": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string"
                  }
                }
              },
              "billed": {
                "type": "integer",
                "enum": [0, 1],
                "description": "Ile jednostek pakietu zdjęto za tę odpowiedź. Zawsze 1 (`found` i `not_found`) albo 0 (`unavailable` oraz źródła spoza SLA). Mnożników nie ma: jedno zapytanie = jedna jednostka, niezależnie od źródła, świeżości i certyfikatu."
              },
              "attribution": {
                "type": "string",
                "description": "Gotowa nota o źródle (wymóg ustawy o otwartych danych)."
              },
              "certificate": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  },
                  "digest": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "timestamps": {
            "type": "object",
            "properties": {
              "requestedAt": {
                "type": "string",
                "format": "date-time"
              },
              "sourceFetchedAt": {
                "type": ["string", "null"],
                "format": "date-time"
              },
              "validAsOf": {
                "type": ["string", "null"],
                "description": "Dzień/moment ważności danych w rejestrze."
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Błąd w formacie RFC 9457 (`application/problem+json`). Uwaga: dotyczy wyłącznie błędów TRANSPORTU i autoryzacji. Brak danych w rejestrze NIE jest błędem — wraca jako `200` z kopertą i `status: not_found` albo `unavailable`.",
        "required": ["type", "title", "status", "code", "requestId"],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_key",
              "key_revoked",
              "ip_not_allowed",
              "source_not_in_plan",
              "invalid_identifier",
              "rate_limited",
              "quota_exceeded",
              "payload_too_large",
              "index_does_not_cover_date",
              "unknown_endpoint",
              "internal_error"
            ],
            "description": "Kod maszynowy — stabilny w obrębie wersji API."
          },
          "detail": {
            "type": "string"
          },
          "requestId": {
            "type": "string",
            "description": "Podaj go w reklamacji — po nim odtworzymy zapytanie z audytu."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/sources": {
      "get": {
        "tags": ["Podmiot"],
        "operationId": "listSources",
        "summary": "Katalog źródeł, ich dostępność i koszt",
        "responses": {
          "200": {
            "description": "Katalog"
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/subjects/{identifier}": {
      "get": {
        "tags": ["Podmiot"],
        "operationId": "getSubject",
        "summary": "Agregat: tożsamość + VAT + KRS jednym zapytaniem",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "NIP, KRS albo REGON"
          },
          {
            "name": "fresh",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Pomija cache i pyta rejestr. NIE kosztuje więcej — jedno zapytanie to zawsze jedna jednostka. Ma za to własny udział w pakiecie (nagłówki `RateLimit-Fresh-*`); po jego wyczerpaniu odpowiedź wraca z cache z nagłówkiem `X-DS-Freshness: degraded` i ostrzeżeniem `fresh_degraded` — nigdy błędem. Ograniczenie chroni rejestry publiczne przed przeciążeniem, dlatego degradujemy zamiast blokować."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta kopert"
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/krs/{identifier}": {
      "get": {
        "tags": ["KRS"],
        "operationId": "getKrsExcerpt",
        "summary": "Odpis KRS (aktualny lub pełny)",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "NIP, KRS albo REGON — most NIP→KRS po naszej stronie"
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["aktualny", "pelny"]
            }
          },
          {
            "name": "fresh",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Pomija cache i pyta rejestr. NIE kosztuje więcej — jedno zapytanie to zawsze jedna jednostka. Ma za to własny udział w pakiecie (nagłówki `RateLimit-Fresh-*`); po jego wyczerpaniu odpowiedź wraca z cache z nagłówkiem `X-DS-Freshness: degraded` i ostrzeżeniem `fresh_degraded` — nigdy błędem. Ograniczenie chroni rejestry publiczne przed przeciążeniem, dlatego degradujemy zamiast blokować."
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z danymi KRS albo `status: not_found`",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                },
                "example": {
                  "apiVersion": "v1",
                  "requestId": "01KYFPM6DWBAY31GYT46C8FNEF",
                  "status": "not_found",
                  "subject": {
                    "nip": null,
                    "regon": null,
                    "krs": "1132998955",
                    "countryCode": "PL",
                    "resolvedVia": "direct"
                  },
                  "source": {
                    "id": "krs",
                    "name": "Krajowy Rejestr Sądowy",
                    "authority": "Ministerstwo Sprawiedliwości",
                    "official": true,
                    "endpoint": "https://api-krs.ms.gov.pl/api/krs",
                    "availability": "ga"
                  },
                  "data": null,
                  "meta": {
                    "cache": {
                      "hit": false
                    },
                    "stale": false,
                    "billed": 1
                  },
                  "timestamps": {
                    "requestedAt": "2026-07-26T17:12:20.101Z",
                    "sourceFetchedAt": "2026-07-26T17:12:20.400Z",
                    "validAsOf": null
                  },
                  "warnings": []
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/vat/{nip}": {
      "get": {
        "tags": ["VAT"],
        "operationId": "getVatStatus",
        "summary": "Status VAT na dzień (biała lista)",
        "parameters": [
          {
            "name": "nip",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Domyślnie dziś"
          },
          {
            "name": "fresh",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Pomija cache i pyta rejestr. NIE kosztuje więcej — jedno zapytanie to zawsze jedna jednostka. Ma za to własny udział w pakiecie (nagłówki `RateLimit-Fresh-*`); po jego wyczerpaniu odpowiedź wraca z cache z nagłówkiem `X-DS-Freshness: degraded` i ostrzeżeniem `fresh_degraded` — nigdy błędem. Ograniczenie chroni rejestry publiczne przed przeciążeniem, dlatego degradujemy zamiast blokować."
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z danymi VAT",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                },
                "example": {
                  "apiVersion": "v1",
                  "requestId": "01KYFPKKTXD5ENZ1Q69TYC6PDK",
                  "status": "found",
                  "subject": {
                    "nip": "5252344078",
                    "regon": "140182840",
                    "krs": "0000240611",
                    "countryCode": "PL",
                    "resolvedVia": "direct"
                  },
                  "source": {
                    "id": "vat",
                    "name": "Wykaz podatników VAT (biała lista)",
                    "authority": "Ministerstwo Finansów",
                    "official": true,
                    "endpoint": "https://wl-api.mf.gov.pl/api",
                    "availability": "ga"
                  },
                  "data": {
                    "nip": "5252344078",
                    "name": "GOOGLE POLAND SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ",
                    "statusVat": "Czynny",
                    "regon": "140182840",
                    "krs": "0000240611",
                    "accountNumbers": ["61109000040000000143936780"],
                    "registrationLegalDate": "2005-10-06"
                  },
                  "meta": {
                    "cache": {
                      "hit": true,
                      "storedAt": "2026-07-26T16:25:17.000Z",
                      "expiresAt": "2026-07-27T16:25:17.000Z"
                    },
                    "stale": false,
                    "billed": 1,
                    "attribution": "Źródło: Wykaz podatników VAT (biała lista) (Ministerstwo Finansów), stan na 2026-07-26, pozyskano 2026-07-26, dane przetworzone technicznie (normalizacja nazw pól i formatów), bez zmiany treści."
                  },
                  "timestamps": {
                    "requestedAt": "2026-07-26T17:12:19.552Z",
                    "sourceFetchedAt": "2026-07-26T16:25:17.000Z",
                    "validAsOf": "2026-07-26"
                  },
                  "warnings": []
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/vat/{nip}/bank-account/{account}": {
      "get": {
        "tags": ["VAT"],
        "operationId": "verifyBankAccount",
        "summary": "Czy rachunek należy do podatnika (weryfikacja przelewu)",
        "description": "Potwierdzenia pochodzą z dziennego pliku płaskiego MF, gdy indeks obejmuje wskazany dzień (`data.provider = \"flat_file\"`, brak zużycia limitów rejestru). Brak potwierdzenia w pliku NIE jest odpowiedzią negatywną — wtedy pytamy API MF (`data.provider = \"wl_api\"`), bo tylko rejestr może rozstrzygnąć m.in. rachunki wirtualne.",
        "parameters": [
          {
            "name": "nip",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "NRB (26 cyfr) lub IBAN"
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z wynikiem weryfikacji",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/regon/{identifier}": {
      "get": {
        "tags": ["REGON"],
        "operationId": "getRegonEntity",
        "summary": "Tożsamość, adres i PKD (GUS BIR, fallback CEIDG)",
        "parameters": [
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fresh",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Pomija cache i pyta rejestr. NIE kosztuje więcej — jedno zapytanie to zawsze jedna jednostka. Ma za to własny udział w pakiecie (nagłówki `RateLimit-Fresh-*`); po jego wyczerpaniu odpowiedź wraca z cache z nagłówkiem `X-DS-Freshness: degraded` i ostrzeżeniem `fresh_degraded` — nigdy błędem. Ograniczenie chroni rejestry publiczne przed przeciążeniem, dlatego degradujemy zamiast blokować."
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z danymi REGON",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/batch/vat": {
      "post": {
        "tags": ["VAT"],
        "operationId": "batchVatStatus",
        "summary": "Status VAT dla maksymalnie 30 NIP-ów (jedno zapytanie do rejestru)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["nips"],
                "properties": {
                  "nips": {
                    "type": "array",
                    "maxItems": 30,
                    "items": {
                      "type": "string"
                    }
                  },
                  "date": {
                    "type": "string",
                    "format": "date"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lista kopert + lista odrzuconych identyfikatorów"
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Więcej niż 30 pozycji"
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bzp/{nip}": {
      "get": {
        "tags": ["BZP"],
        "operationId": "getPublicProcurement",
        "summary": "Postępowania publiczne, w których podmiot wystąpił (BZP)",
        "description": "Odpowiedź pochodzi z NASZEGO indeksu ogłoszeń, nie z odpytania UZP: API Biuletynu nie potrafi wyszukiwać po NIP, nie ma paginacji i ucina wynik na 500 rekordach. Zwracamy postępowania, w których podmiot wystąpił jako WYKONAWCA lub jako ZAMAWIAJĄCY. UWAGA: pole `data.coverage` podaje zakres dat, który realnie mamy zaindeksowany — pusta lista NIE jest dowodem braku postępowań poza tym zakresem. Szukamy po wszystkich znanych identyfikatorach podmiotu (NIP, KRS, REGON), bo zamawiający wpisują je zamiennie.",
        "parameters": [
          {
            "name": "nip",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z listą postępowań i zakresem pokrycia",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                },
                "example": {
                  "apiVersion": "v1",
                  "requestId": "01KYFQ2R8N4TZK1M0S6VD3XB7Q",
                  "status": "found",
                  "subject": {
                    "nip": "8531456990",
                    "regon": null,
                    "krs": null,
                    "countryCode": "PL",
                    "resolvedVia": "direct"
                  },
                  "source": {
                    "id": "bzp",
                    "name": "Biuletyn Zamówień Publicznych",
                    "authority": "Urząd Zamówień Publicznych",
                    "official": true,
                    "endpoint": "https://ezamowienia.gov.pl/mo-board/api/v1/notice",
                    "availability": "ga"
                  },
                  "meta": {
                    "cache": {
                      "hit": false
                    },
                    "stale": false,
                    "billed": 1
                  },
                  "timestamps": {
                    "requestedAt": "2026-07-26T17:18:02.113Z",
                    "sourceFetchedAt": "2026-07-26T08:34:58.000Z",
                    "validAsOf": "2026-07-26"
                  },
                  "data": {
                    "summary": {
                      "asContractor": 0,
                      "asOrganization": 2,
                      "latestPublicationDate": "2026-07-02T07:13:35.000Z"
                    },
                    "coverage": {
                      "from": "2025-07-26T05:15:20.000Z",
                      "to": "2026-07-26T08:34:58.000Z",
                      "noticesIndexed": 545503
                    },
                    "notices": [
                      {
                        "noticeNumber": "2026/BZP 00318648/01",
                        "noticeType": "ContractNotice",
                        "publicationDate": "2026-07-02T07:13:35.000Z",
                        "role": "organization",
                        "orderObject": "„Przebudowa wraz z modernizacją Szkoły Podstawowej w Mielęcinie – etap II”",
                        "cpvCode": "45000000-7 (Roboty budowlane)",
                        "procedureResult": null,
                        "organizationName": "Gmina Pyrzyce"
                      }
                    ]
                  },
                  "warnings": [
                    {
                      "code": "bzp_coverage",
                      "message": "Odpowiedź obejmuje ogłoszenia z zakresu 2025-07-26 – 2026-07-26. Poza tym zakresem nie wiemy — brak wyniku nie oznacza braku postępowań."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sudop/{nip}": {
      "get": {
        "tags": ["SUDOP"],
        "operationId": "getStateAid",
        "summary": "Pomoc publiczna i de minimis otrzymana przez podmiot (SUDOP)",
        "description": "Zdarzenia pomocowe zarejestrowane przez UOKiK: kto udzielił, kiedy, w jakiej formie i za ile (PLN oraz EUR według przelicznika organu). Sekcja `data.deMinimis` sumuje pomoc de minimis w RUCHOMYM oknie trzech lat, ODDZIELNIE dla każdej puli (`e1`, `e1t`, `e1c`, `e2`, `e2c`), bo pule mają różne limity i różne podstawy prawne. ⚠️ `headroomEur` jest ORIENTACYJNY i NIE zastępuje zaświadczeń o pomocy de minimis: limit dotyczy „jednego przedsiębiorstwa” w rozumieniu rozporządzenia, czyli obejmuje także podmioty powiązane — suma liczona dla samego NIP-u może być zaniżona. Sumy i wyliczenia zawsze pochodzą z KOMPLETU zdarzeń, także gdy lista `events` została przycięta parametrem `limit` (wtedy `data.truncated` = true).",
        "parameters": [
          {
            "name": "nip",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Ile zdarzeń wypisać (od najnowszych). Nie wpływa na sumy ani na de minimis.",
            "schema": {
              "type": "integer",
              "maximum": 1000,
              "default": 200
            }
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z listą zdarzeń pomocowych i podsumowaniem de minimis",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                },
                "example": {
                  "apiVersion": "v1",
                  "requestId": "01KYFQ2SB1M7WQ4E9Z0PH5TCXK",
                  "status": "found",
                  "subject": {
                    "nip": "7740001454",
                    "regon": null,
                    "krs": null,
                    "countryCode": "PL",
                    "resolvedVia": "direct"
                  },
                  "source": {
                    "id": "sudop",
                    "name": "System Udostępniania Danych o Pomocy Publicznej",
                    "authority": "Urząd Ochrony Konkurencji i Konsumentów",
                    "official": true,
                    "endpoint": "https://api-sudop.uokik.gov.pl/sudop-api",
                    "availability": "ga"
                  },
                  "meta": {
                    "cache": {
                      "hit": false
                    },
                    "stale": false,
                    "billed": 1
                  },
                  "timestamps": {
                    "requestedAt": "2026-07-26T17:18:05.402Z",
                    "sourceFetchedAt": "2026-07-26T17:18:07.881Z",
                    "validAsOf": "2026-07-26"
                  },
                  "data": {
                    "totalEvents": 296,
                    "returnedEvents": 2,
                    "truncated": true,
                    "summary": {
                      "firstAidOn": "2017-01-17",
                      "lastAidOn": "2026-07-20",
                      "totalGrossPln": 3106694545.94,
                      "totalGrossEur": 713913825.44,
                      "deMinimisEvents": 1
                    },
                    "deMinimis": {
                      "pools": [],
                      "windowFrom": "2023-07-26",
                      "windowTo": "2026-07-26",
                      "totalGrantedEur": 0,
                      "note": "Wyliczenie ORIENTACYJNE, nie zastępuje zaświadczeń o pomocy de minimis. Limit dotyczy „jednego przedsiębiorstwa” w rozumieniu rozporządzenia, czyli także podmiotów powiązanych — suma dla samego NIP-u może być zaniżona."
                    },
                    "events": [
                      {
                        "grantedOn": "2026-07-20",
                        "grantorNip": "5262748966",
                        "grantorName": "Polskie Sieci Elektroenergetyczne S.A.",
                        "measureNumber": "SA.46100(2017/N)",
                        "measureName": "Polski mechanizm rynku mocy",
                        "legalBasis": "ustawa z dnia 8 grudnia 2017 r. o rynku mocy",
                        "formName": "dotacja lub inne bezzwrotne świadczenie",
                        "sectorName": "Wytwarzanie energii elektrycznej",
                        "value": {
                          "nominalPln": 108098.97,
                          "grossPln": 108098.97,
                          "grossEur": 24931.15
                        },
                        "isDeMinimis": false,
                        "deMinimisPool": null
                      }
                    ]
                  },
                  "warnings": [
                    {
                      "code": "sudop_truncated",
                      "message": "Lista zdarzeń przycięta do 2 z 296. Sumy i de minimis policzono z kompletu."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/crbr/{nip}": {
      "get": {
        "tags": ["CRBR"],
        "operationId": "getBeneficialOwners",
        "summary": "Beneficjenci rzeczywiści (CRBR)",
        "description": "Oficjalna bramka SOAP Ministerstwa Finansów. RODO: z numeru PESEL beneficjenta wyprowadzamy WYŁĄCZNIE rok urodzenia — samego numeru nie zwracamy ani nie przechowujemy (dotyczy też surowej odpowiedzi zapisywanej w certyfikacie). `status: not_found` oznacza brak wpisu w rejestrze, a nie brak podmiotu: z obowiązku zgłoszenia zwolnione są m.in. spółki publiczne, a jednoosobowe działalności w CRBR nie figurują.",
        "parameters": [
          {
            "name": "nip",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fresh",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Pomija cache i pyta rejestr. NIE kosztuje więcej — jedno zapytanie to zawsze jedna jednostka. Ma za to własny udział w pakiecie (nagłówki `RateLimit-Fresh-*`); po jego wyczerpaniu odpowiedź wraca z cache z nagłówkiem `X-DS-Freshness: degraded` i ostrzeżeniem `fresh_degraded` — nigdy błędem. Ograniczenie chroni rejestry publiczne przed przeciążeniem, dlatego degradujemy zamiast blokować."
          },
          {
            "name": "certificate",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Wystawia certyfikat zapytania — zamrożoną kopertę z odciskiem HMAC, czyli dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Wymaga zakresu `certificate` przy kluczu. BEZ DOPŁATY w każdym pakiecie i bez osobnej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Certyfikaty są poza retencją 90 dni; po rozwiązaniu umowy pozostają do pobrania przez 12 miesięcy."
          }
        ],
        "responses": {
          "200": {
            "description": "Koperta z danymi CRBR",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pojemność wiadra (burst) dla tego klucza."
              },
              "RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile żetonów zostało w tej chwili."
              },
              "RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund wiadro będzie pełne."
              },
              "RateLimit-Quota-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Pakiet miesięczny w zapytaniach."
              },
              "RateLimit-Quota-Used": {
                "schema": {
                  "type": "integer"
                },
                "description": "Zapytania rozliczone w bieżącym okresie."
              },
              "RateLimit-Quota-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań zostało do końca pakietu. Po zejściu do zera plan z nadwyżką pracuje dalej (do twardego sufitu 3× pakiet), plan FREE zwraca `429 quota_exceeded`."
              },
              "RateLimit-Quota-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Za ile sekund zaczyna się nowy okres rozliczeniowy (miesiąc UTC)."
              },
              "RateLimit-Fresh-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań (`fresh=true`) mieści się w pakiecie."
              },
              "RateLimit-Fresh-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile świeżych pobrań zostało w bieżącym okresie."
              },
              "RateLimit-Sandbox-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Tylko dla kluczy `ds_test_*`: dzienny sufit piaskownicy. Klucze testowe nie zużywają pakietu, ale mają własny limit — po jego przekroczeniu `429` do północy UTC."
              },
              "RateLimit-Sandbox-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Ile zapytań piaskownicy zostało do końca doby (UTC)."
              },
              "X-DS-Freshness": {
                "schema": {
                  "type": "string",
                  "enum": ["fresh", "degraded"]
                },
                "description": "Obecny tylko przy `fresh=true`. `fresh` = poszliśmy do rejestru. `degraded` = udział świeżych pobrań wyczerpany, odpowiedź pochodzi z cache (200, nie błąd) — patrz ostrzeżenie `fresh_degraded` w kopercie."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/batch/vat/bank-accounts": {
      "post": {
        "tags": ["VAT"],
        "operationId": "batchVerifyBankAccounts",
        "summary": "Zbiorcza weryfikacja rachunków z dziennego pliku płaskiego MF (bez zapytań do rejestru)",
        "description": "Odpowiada z własnego indeksu pliku płaskiego, więc nie zużywa limitów MF. UWAGA na semantykę: `confirmed` dowodzi, że rachunek należał do podatnika w danym dniu; `not_confirmed` oznacza BRAK POTWIERDZENIA (np. rachunek wirtualny opisany maską), a NIE to, że rachunek nie należy do podatnika — po odpowiedź definitywną użyj GET /v1/vat/{nip}/bank-account/{account}, który dopytuje API MF.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["pairs"],
                "properties": {
                  "pairs": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "type": "object",
                      "required": ["nip", "account"],
                      "properties": {
                        "nip": {
                          "type": "string"
                        },
                        "account": {
                          "type": "string",
                          "description": "NRB (26 cyfr) lub IBAN"
                        }
                      }
                    }
                  },
                  "date": {
                    "type": "string",
                    "format": "date",
                    "description": "Dzień, na który weryfikujemy. Musi być objęty indeksem — patrz `vatFlatIndex` w GET /v1/sources."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wyniki par + ostrzeżenie o semantyce `not_confirmed`"
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Więcej niż 200 par"
          },
          "422": {
            "description": "Indeks nie obejmuje wskazanego dnia"
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/health/vat-flat": {
      "get": {
        "tags": ["Stan usługi"],
        "operationId": "getVatFlatHealth",
        "summary": "Stan indeksu pliku płaskiego (na jaki dzień i ile wpisów)",
        "security": [],
        "responses": {
          "200": {
            "description": "Indeks wczytany"
          },
          "503": {
            "description": "Brak indeksu — weryfikacja rachunków idzie przez API MF"
          }
        }
      }
    },
    "/v1/certificates/{id}": {
      "get": {
        "tags": ["Certyfikaty"],
        "operationId": "getCertificate",
        "summary": "Certyfikat zapytania (zamrożona koperta + odcisk HMAC)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Certyfikat"
          },
          "400": {
            "description": "Nieprawidłowy identyfikator",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Brak lub zły klucz API",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Źródło spoza planu klucza",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Limit chwilowy (`rate_limited`) albo pakiet: wyczerpany w planie bez nadwyżki lub twardy sufit 3× pakiet (`quota_exceeded`).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/v1/access-requests": {
      "post": {
        "tags": ["Dostęp"],
        "operationId": "requestApiAccess",
        "summary": "Wniosek o dostęp do API (formularz portalu, bez klucza)",
        "description": "Zgłoszenie handlowe, nie rejestracja. Klucz wydajemy ręcznie po kontakcie — ten endpoint nie zakłada konta i nie zwraca żadnych danych uwierzytelniających. Dławienie: 5 wniosków na adres IP w ciągu doby.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["company", "email", "useCase"],
                "properties": {
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "nip": {
                    "type": ["string", "null"],
                    "description": "Nieobowiązkowy — ale podany musi przejść walidację sumy kontrolnej."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 200
                  },
                  "useCase": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 2000,
                    "description": "Do czego posłużą dane. Decyduje o planie i zakresie umowy."
                  },
                  "expectedMonthlyRequests": {
                    "type": ["integer", "null"]
                  },
                  "plan": {
                    "type": ["string", "null"],
                    "enum": ["free", "starter", "pro", "enterprise", null]
                  }
                }
              },
              "example": {
                "company": "Integrator sp. z o.o.",
                "nip": "5252344078",
                "email": "integracje@example.com",
                "useCase": "Weryfikacja kontrahentów przy zakładaniu konta w naszym systemie faktur.",
                "expectedMonthlyRequests": 3000,
                "plan": "starter"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Wniosek przyjęty do rozpatrzenia (klucza NIE wydaje).",
            "content": {
              "application/json": {
                "example": {
                  "status": "received",
                  "requestId": "req_01J8Z9K2M4",
                  "detail": "Wniosek przyjęty. Klucz wydajemy ręcznie, po kontakcie z naszej strony.",
                  "notified": true
                }
              }
            }
          },
          "400": {
            "description": "Braki lub błędy w danych wniosku",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Za dużo wniosków z jednego adresu",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": ["Stan usługi"],
        "operationId": "getHealth",
        "summary": "Liveness (bez klucza)",
        "security": [],
        "responses": {
          "200": {
            "description": "ok"
          },
          "503": {
            "description": "Usługa nie przyjmuje ruchu (zamykanie procesu).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  }
}
