Dokumentacja w przeglądzie — API działa. Endpointy pod https://api.debtstrike.eu odpowiadają produkcyjnie. Cennik i regulamin są jeszcze przed publikacją, więc warunki handlowe ustalamy indywidualnie — klucz wydajemy ręcznie po kontakcie.

Rejestry API

Jedno API do siedmiu polskich rejestrów publicznych. Jedna koperta odpowiedzi, jedno uwierzytelnianie, jeden sposób obsługi błędów — zamiast siedmiu integracji, siedmiu formatów dat i siedmiu sposobów mówienia „nie wiem”.

Rejestr Co daje Endpoint Stan
KRS odpis aktualny i pełny, reprezentacja, kapitał /v1/krs/{identifier} GA
Biała lista VAT status VAT, rachunki, weryfikacja przelewu /v1/vat/{nip} GA
REGON / GUS tożsamość, adres, PKD (fallback CEIDG) /v1/regon/{identifier} GA
CRBR beneficjenci rzeczywiści /v1/crbr/{nip} GA
BZP zamówienia publiczne (wykonawca i zamawiający) /v1/bzp/{nip} GA
SUDOP pomoc publiczna i de minimis /v1/sudop/{nip} GA
Agregat tożsamość + VAT + KRS jednym zapytaniem /v1/subjects/{identifier} GA

Pierwsze zapytanie w 5 minut

1. Klucz

Klucz wydajemy ręcznie — złóż wniosek w sekcji Uzyskaj dostęp (albo napisz na kontakt@debtstrike.eu). Dostajesz dwa: ds_test_… (piaskownica, nie rusza rejestrów, nie zużywa kwoty) i ds_live_… (dane produkcyjne). Sekret pokazujemy raz — u nas w bazie jest tylko jego skrót.

2. Zapytanie

curl -s https://api.debtstrike.eu/v1/vat/5252344078 \
  -H "Authorization: Bearer ds_live_TWOJ_KLUCZ"

3. Odpowiedź

{
  "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, …"
  },
  "timestamps": {
    "requestedAt": "2026-07-26T17:12:19.552Z",
    "sourceFetchedAt": "2026-07-26T16:25:17.000Z",
    "validAsOf": "2026-07-26"
  },
  "warnings": []
}

4. Trzy rzeczy, które warto zrozumieć od razu

Czytaj status, nie kod HTTP

HTTP 200 mówi tylko, że zapytanie zostało obsłużone. O danych mówi status w kopercie. Szczegóły.

Zapisz requestId

Jest w każdej odpowiedzi, także w błędzie. Po nim odtworzymy Twoje zapytanie z audytu — to jedyna rzecz, o którą poprosimy przy reklamacji.

Użyj validAsOf, nie czasu pobrania

Rejestry mają własny stan na dzień. timestamps.validAsOf mówi, na kiedy dane są ważne w rejestrze — a nie kiedy je pobraliśmy.

Publikujesz dane? Weź attribution

Gotowa nota o źródle. Ustawa o otwartych danych wymaga jej przy ponownym wykorzystaniu — składamy ją za Ciebie.

Uwierzytelnianie

Klucz w nagłówku Authorization, schemat Bearer:

Authorization: Bearer ds_live_eH9vE1TxL6IrfAqU9Xh8Ja0U
Cecha Zachowanie
Prefiks ds_live_ — dane z rejestrów · ds_test_ — piaskownica
Przechowywanie W bazie tylko sha256(pieprz + sekret). Zgubionego sekretu nie odzyskamy — wydajemy nowy.
Zakresy Klucz ma listę źródeł. Zapytanie o źródło spoza listy → 403 source_not_in_plan.
Allowlista IP Opcjonalna, per klucz. Spoza listy → 403 ip_not_allowed.
Odwołanie Natychmiastowe, bez restartu usługi. Odwołany klucz → 401 key_revoked.
Rotacja Wydajemy drugi klucz, przepinasz ruch, dopiero potem odwołujemy pierwszy — bez okna niedostępności.

Piaskownica. Klucz ds_test_ zwraca dane przykładowe o tym samym kształcie co produkcja i nigdy nie dotyka rejestrów. Integrację możesz napisać i przetestować, nie zużywając ani jednostki kwoty i nie obciążając limitów rejestru.

Koperta odpowiedzi

Każde źródło odpowiada tą samą strukturą. Zmienia się wyłącznie zawartość data. Dzięki temu obsługę błędów, cache i audytu piszesz raz, a nie siedem razy.

Pole Znaczenie
apiVersion Zawsze "v1" w tej wersji.
requestId ULID zapytania. Do reklamacji i korelacji z Twoimi logami.
status found · not_found · unavailable · forbidden · invalid_input
subject Znormalizowana tożsamość: nip, regon, krs plus resolvedVia — czym dotarliśmy do podmiotu (direct, mf_whitelist, gus_bir).
source Który rejestr odpowiedział, jaki organ go prowadzi, czy jest oficjalny.
data Dane źródła. null zawsze, gdy statusfound — nie ma stanów pośrednich.
meta.cache hit, storedAt, expiresAt.
meta.stale true = rejestr milczał, oddaliśmy ostatnie znane dane. Patrz niżej.
meta.billed Ile jednostek pakietu zdjęła ta odpowiedź: 1 albo 0. Nigdy więcej — patrz Jak liczymy zapytania.
meta.attribution Gotowa nota o źródle (wymóg ustawy o otwartych danych).
timestamps.validAsOf Stan danych w rejestrze — to jest data, którą pokazujesz użytkownikowi.
timestamps.sourceFetchedAt Kiedy my pobraliśmy. Przy trafieniu w cache starsze niż requestedAt.
warnings[] Lista {code, message}. Nie błędy — zastrzeżenia do interpretacji. Czytaj je.

not_found kontra unavailable

To jest miejsce, w którym różnimy się od proxy na rejestry — i jedyna rzecz z tej dokumentacji, którą naprawdę warto przeczytać uważnie.

not_found unavailable
Co się stało Rejestr odpowiedział: nie mam takiego podmiotu Rejestr nie odpowiedział (awaria, timeout, limit)
Co wiesz Fakt. Wiedza negatywna, potwierdzona przez organ Nic. Brak wiedzy
Wolno oprzeć decyzję? Tak Nie — ponów później
HTTP 200 200
data null null albo dane z cache, gdy meta.stale = true

Najczęstszy błąd integracji. Potraktowanie obu przypadków jako „brak danych” i pokazanie użytkownikowi „firma nie istnieje”. Przy unavailable to jest nieprawda — to my nie wiemy. Zwykłe proxy nie robi tego rozróżnienia, bo zwraca pustkę i zostawia Cię z pytaniem, co ona znaczy.

Tryb degradacji

Gdy rejestr milczy, a mamy dane w cache, zwracamy je z meta.stale = true i meta.degraded.reason. To świadoma decyzja: lepiej oddać wczorajszy odpis z etykietą „wczorajszy”, niż nie oddać nic. Nie udajemy jednak, że są świeżetimestamps.sourceFetchedAt mówi prawdę.

Limit rejestru nigdy nie przecieka na Ciebie. Biała lista MF blokuje IP po 100 zapytaniach wyszukujących na dobę. Gdy zbliżamy się do progu, dostajesz stale albo unavailablenie 429. Kod 429 oznacza wyłącznie Twój własny limit.

Jak liczymy zapytania

Jedno zapytanie = jedna jednostka pakietu. Nie ma mnożników: ani za źródło, ani za świeżość, ani za certyfikat. Nie musisz nic przeliczać, żeby wiedzieć, ile Ci zostało — meta.billed w każdej odpowiedzi mówi to wprost.

Zasada, z której wynika cała reszta: płacisz za odpowiedź z rejestru, nie za próbę.

Sytuacja Liczy się Dlaczego
Odpowiedź ok — dane zwrócone 1 Oczywiste.
Odpowiedź not_found 1 To odpowiedź, nie błąd. Kosztowała odpytanie rejestru i ma wartość informacyjną: wiedza negatywna potwierdzona przez organ.
Odpowiedź z cache (bez fresh) 1 Dostałeś wynik. Skąd go wzięliśmy, to nasza sprawa — i nasza oszczędność.
unavailable — rejestr milczy 0 Nie obciążamy za cudzą awarię.
Błąd 5xx po naszej stronie 0 Nie obciążamy za własną awarię.
Błąd 400 — walidacja 0 Integracja musi być tania w nauce.
Błąd 401 / 403 0
Błąd 429 — limit 0 Kara już jest, nie podwajamy jej.
Zapytanie o N źródeł (agregat) N Każde źródło to osobne odpytanie rejestru.
Paczka N podmiotów N Każdy podmiot to osobna odpowiedź, którą dostajesz i na której możesz oprzeć decyzję.
Certyfikat do zapytania 0 Nie jest osobnym zapytaniem — patrz Certyfikaty.
Zapytanie kluczem ds_test_ 0 Klucze testowe nie zużywają pakietu i mają osobny limit dzienny. Patrz niżej.

Klucze testowe (piaskownica)

Klucze testowe nie zużywają pakietu i mają osobny limit dzienny. Klucz ds_test_ zwraca stałe dane z próbki, nie rusza rejestrów i nie zdejmuje ani jednej jednostki — w kopercie zobaczysz "billed": 0. Obowiązuje go za to własny sufit: 500 zapytań na dobę (do północy UTC) i 2 zapytania na sekundę. Stan sufitu niosą nagłówki RateLimit-Sandbox-Limit i RateLimit-Sandbox-Remaining; po jego przekroczeniu dostajesz 429 quota_exceeded.

Cel jest prosty: integracja ma być darmowa w nauce, ale na kluczu testowym nie da się pracować produkcyjnie — dane w piaskownicy są zamrożone i nieprawdziwe.

Sprawdź to sam. Sto zapytań o CRBR z fresh=true i certyfikatem zdejmie z pakietu dokładnie 100, nie 3000. Ta liczba jest pilnowana testem akceptacyjnym, nie deklaracją w cenniku.

Limity i świeżość

Trzy niezależne warstwy. Dwie pierwsze mogą zakończyć się kodem 429. Trzecia — nigdy.

Warstwa Nagłówki Co znaczy wyczerpanie Co robić
Limit chwilowy
token bucket
RateLimit-Limit
RateLimit-Remaining
RateLimit-Reset
Za dużo zapytań na sekundę Poczekaj RateLimit-Reset sekund. Wiadro napełnia się samo.
Pakiet miesięczny RateLimit-Quota-Limit
RateLimit-Quota-Used
RateLimit-Quota-Remaining
RateLimit-Quota-Reset
Pakiet wyczerpany. W planach z nadwyżką API pracuje dalej — do twardego sufitu 3× pakiet, po którym blokuje. Czekanie nie pomoże: pakiet odnawia się w nowym okresie (miesiąc UTC).
Udział świeżych pobrań RateLimit-Fresh-Limit
RateLimit-Fresh-Remaining
X-DS-Freshness
Wyczerpany udział fresh=true w pakiecie Nic. Odpowiedzi lecą dalej, tylko z cache.
RateLimit-Limit: 20
RateLimit-Remaining: 18
RateLimit-Reset: 4
RateLimit-Quota-Limit: 4000
RateLimit-Quota-Used: 1182
RateLimit-Quota-Remaining: 2818
RateLimit-Quota-Reset: 388800
RateLimit-Fresh-Limit: 800
RateLimit-Fresh-Remaining: 611

Twardy sufit nadwyżki

Po przekroczeniu pakietu zapytania idą na nadwyżkę, rozliczaną z dołu — ale nie w nieskończoność. Sufit to 3× pakiet, po nim API blokuje i wysyła alert. Blokada jest celowa: chroni Cię przed rachunkiem bez dna po błędzie w pętli. Ostrzegamy wcześniej — przy 80% i 100% pakietu, potem przy 200% i 300%.

Świeżość: ?fresh=true

fresh=true pomija cache i idzie do rejestru. Nie kosztuje więcej — ma za to własny udział w pakiecie. Po jego wyczerpaniu zapytanie nie jest odrzucane: obsługujemy je z cache i mówimy o tym wprost.

HTTP/1.1 200 OK
X-DS-Freshness: degraded
RateLimit-Fresh-Remaining: 0

{
  "status": "found",
  "meta": { "billed": 1, "cache": { "hit": true } },
  "timestamps": { "validAsOf": "2026-07-26" },
  "warnings": [
    { "code": "fresh_degraded",
      "message": "Wyczerpany udział świeżych pobrań w pakiecie — odpowiedź pochodzi z cache." }
  ]
}

Dlaczego degradujemy, a nie blokujemy. To ograniczenie chroni rejestry publiczne przed przeciążeniem, a nie nasz portfel. Odcięcie Cię od danych byłoby karą wymierzoną nie tam, gdzie jest problem. Dane dostajesz zawsze — timestamps.validAsOf mówi, jak świeże są naprawdę.

Błędy

Błędy transportu i autoryzacji zwracamy w formacie RFC 9457 (application/problem+json). Brak danych w rejestrze nie jest błędem — to 200 z status: not_found.

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
  "type": "https://api.debtstrike.eu/errors/invalid-key",
  "title": "Nieprawidłowy lub odwołany klucz API",
  "status": 401,
  "code": "invalid_key",
  "detail": "Nieprawidłowy format klucza API.",
  "requestId": "01KYFPM6DWBAY31GYT46C8FNEF"
}
HTTP code Znaczenie
400 invalid_identifier To nie jest NIP, KRS ani REGON.
401 invalid_key Brak nagłówka, zły format lub nieznany klucz.
401 key_revoked Klucz odwołany.
403 ip_not_allowed Adres spoza allowlisty klucza.
403 source_not_in_plan Źródło spoza zakresów klucza.
404 unknown_endpoint Nie ma takiego zasobu.
413 payload_too_large Przekroczony limit pozycji w zapytaniu zbiorczym.
422 index_does_not_cover_date Indeks pliku płaskiego nie obejmuje wskazanego dnia.
429 rate_limited Limit chwilowy — ponów za RateLimit-Reset s.
429 quota_exceeded Wyczerpana kwota miesięczna.
500 internal_error Nasz błąd. Zgłoś z requestId.

Awaria rejestru nigdy nie jest kodem 5xx. Gdy źródło padnie, dostajesz 200 ze status: unavailable albo dane stale. Kod 500 oznacza wyłącznie błąd po naszej stronie i jest zawsze błędem do zgłoszenia.

Wersjonowanie

Wersja siedzi w ścieżce: /v1/…. W obrębie v1 zobowiązujemy się, że:

Zmiana łamiąca kontrakt oznacza /v2 wystawione obok /v1, nie podmianę. Twój parser ma ignorować nieznane pola — to jedyne założenie, o które prosimy.

KRS

GET /v1/krs/{identifier}

Odpis z Krajowego Rejestru Sądowego (Ministerstwo Sprawiedliwości).

Parametr Gdzie Opis
identifier ścieżka NIP, KRS albo REGON. Most NIP→KRS robimy po swojej stronie (przez GUS, bez zużywania limitu MF).
scope query aktualny (domyślnie) albo pelny — odpis pełny zawiera historię wpisów.
fresh query true pomija cache. Koszt ×3.
certificate query true wystawia certyfikat zapytania.

Zawartość data: krs, nip, regon, name, legalForm, register, address, contact, pkd, shareCapital, representation, flags, registrationDate, lastEntryDate, lastFinancialStatement.

Przykład: podmiotu nie ma w rejestrze

{
  "status": "not_found",
  "subject": { "nip": null, "regon": null, "krs": "1132998955",
               "countryCode": "PL", "resolvedVia": "direct" },
  "data": null,
  "meta": { "cache": { "hit": false }, "stale": false, "billed": 1 },
  "timestamps": { "validAsOf": null },
  "warnings": []
}

Uwaga na krótkie numery KRS. Numer KRS ma 10 cyfr z wiodącymi zerami. /v1/krs/123 zostanie uzupełnione do 0000000123 i zwróci istniejącą spółkę — to poprawne zachowanie rejestru, nie błąd. Jeśli przekazujesz dane od użytkownika, waliduj po swojej stronie, czym one są.

Limity: MS nie publikuje limitów ani SLA dla API KRS. Przyjęliśmy własny ostrożny sufit 2000 zapytań na dobę i go mierzymy.

Biała lista VAT

Status podatnika

GET /v1/vat/{nip}
Parametr Gdzie Opis
nip ścieżka NIP podatnika (10 cyfr, separatory dozwolone).
date query Dzień, na który sprawdzamy status. Domyślnie dziś.
fresh, certificate query Jak wyżej.

data: statusVat (Czynny/Zwolniony/Niezarejestrowany), name, regon, krs, accountNumbers[], registrationLegalDate, removalDate, restorationDate, hasVirtualAccounts, adresy.

Weryfikacja rachunku bankowego

GET /v1/vat/{nip}/bank-account/{account}

Odpowiada na pytanie „czy ten rachunek należał do tego podatnika tego dnia” — czyli na pytanie, które decyduje o odpowiedzialności solidarnej i o kosztach uzyskania przychodu. account to NRB (26 cyfr) albo IBAN.

Skąd bierzemy potwierdzenie. Utrzymujemy własny indeks dziennego pliku płaskiego MF (3,3 mln par NIP+rachunek, odświeżany codziennie o 4:00). Gdy indeks obejmuje wskazany dzień, potwierdzenie idzie z niego (data.provider = "flat_file") — bez zużywania limitów MF. To dlatego weryfikacja rachunków skaluje się u nas do dziesiątek tysięcy dziennie.

Semantyka, na której łatwo się przejechać. Trafienie w pliku jest dowodem pozytywnym. Brak trafienia nie jest dowodem negatywnym — rachunki wirtualne są w pliku opisane maskami, których nie rozwijamy. Dlatego przy braku trafienia zawsze dopytujemy API MF (data.provider = "wl_api"), żeby nigdy nie odpowiedzieć „rachunek nie należy do podatnika”, gdy w rzeczywistości należy.

Limity MF, których nie musisz znać, bo trzymamy je po swojej stronie: 100 zapytań wyszukujących na dobę na adres IP (do 30 podmiotów w zapytaniu) i 5000 podmiotów metodą sprawdzającą. Przekroczenie blokuje adres IP do północy — razem z wyszukiwarką WWW.

REGON / GUS (fallback CEIDG)

GET /v1/regon/{identifier}

Tożsamość, adres i PKD z rejestru REGON (GUS BIR). Dla jednoosobowych działalności, których BIR nie zwraca w komplecie, przewidziany jest fallback na CEIDG.

data: regon, nip, krs, name, entityType, active, address, pkd, endDate, provider (gus_bir albo ceidg).

Stan faktyczny: fallback CEIDG jest zaimplementowany, ale na produkcji nieaktywny — brakuje klucza do API CEIDG. Do czasu jego uzyskania provider zawsze wynosi gus_bir. Piszemy o tym wprost, bo obietnica źródła, którego nie ma, jest gorsza niż jego brak.

Kody PKD normalizujemy do jednego formatu (19.20.Z) niezależnie od tego, że GUS oddaje je jako 1920Z, a KRS z kropkami. To jeden z powodów, dla których warto brać dane przez jedno API zamiast z siedmiu.

CRBR — beneficjenci rzeczywiści

GET /v1/crbr/{nip}

Beneficjenci rzeczywiści z oficjalnej bramki SOAP Ministerstwa Finansów — nie z nieoficjalnego backendu portalu.

RODO — ograniczenie u źródła. Z numeru PESEL beneficjenta wyprowadzamy wyłącznie rok urodzenia. Samego numeru nie zwracamy, nie logujemy i nie przechowujemy — dotyczy to także surowej odpowiedzi zapisywanej jako dowód w certyfikacie. Maskowanie dzieje się w warstwie pobrania, więc PESEL nie istnieje nigdzie poza pamięcią procesu w chwili parsowania.

not_found tu znaczy coś innego niż gdzie indziej. 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 w ogóle nie figurują. Nie interpretuj tego jako „spółka ukrywa beneficjentów”.

CRBR jest świadomie poza agregatem /v1/subjects — dane o osobach fizycznych pobieramy tylko wtedy, gdy o nie wprost poprosisz. Agregat mówi o tym jawnie w polu notIncluded.

BZP — zamówienia publiczne

GET /v1/bzp/{nip}

Postępowania, w których podmiot wystąpił jako wykonawca albo zamawiający. Parametr limit (domyślnie 50, maksimum 200) ogranicza liczbę zwróconych ogłoszeń.

{
  "status": "found",
  "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…”",
        "cpvCode": "45000000-7 (Roboty budowlane)",
        "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…" }
  ]
}

To nasz indeks, nie proxy — i mówimy, co w nim jest. API Biuletynu nie potrafi wyszukiwać po NIP, nie ma paginacji i ucina wynik na 500 rekordach, więc zbudowaliśmy własny indeks. Pole data.coverage podaje zakres, który realnie mamy zaindeksowany. Pusta lista nie jest dowodem braku postępowań poza tym zakresem — i dlatego ostrzeżenie bzp_coverage jest w każdej odpowiedzi.

Szukamy po wszystkich znanych identyfikatorach podmiotu (NIP, KRS, REGON), bo zamawiający wpisują je zamiennie w jedno pole — na realnej próbce 19% wykonawców miało tam coś innego niż NIP. Bez tego wyszukiwanie gubiłoby co piątego wykonawcę.

SUDOP — pomoc publiczna i de minimis

GET /v1/sudop/{nip}

Zdarzenia pomocowe zarejestrowane przez UOKiK: kto udzielił, kiedy, w jakiej formie i za ile (PLN oraz EUR według przelicznika organu). Parametr limit (domyślnie 200, maksimum 1000) ogranicza listę events.

{
  "status": "found",
  "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…"
    },
    "events": [
      {
        "grantedOn": "2026-07-20",
        "grantorName": "Polskie Sieci Elektroenergetyczne S.A.",
        "measureName": "Polski mechanizm rynku mocy",
        "legalBasis": "ustawa z dnia 8 grudnia 2017 r. o rynku mocy",
        "formName": "dotacja lub inne bezzwrotne świadczenie",
        "value": { "nominalPln": 108098.97, "grossPln": 108098.97, "grossEur": 24931.15 },
        "isDeMinimis": false, "deMinimisPool": null
      }
    ]
  }
}

De minimis — co liczymy, a czego nie obiecujemy

Sekcja data.deMinimis sumuje pomoc 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 — od 100 tys. EUR dla transportu drogowego towarów po 750 tys. EUR dla usług w ogólnym interesie gospodarczym.

Nie sprzedajemy jednej liczby „zostało Ci X limitu” i chcemy być w tym wprost. Powody: limit dotyczy jednego przedsiębiorstwa w rozumieniu rozporządzenia, czyli obejmuje podmioty powiązane — więc suma dla samego NIP-u bywa zaniżona. Do tego pomoc sprzed 2024 r. podlega innemu rozporządzeniu i innemu sposobowi liczenia okresu. Pole headroomEur jest orientacyjne, oznaczone ostrzeżeniem de_minimis_indicative i nie zastępuje zaświadczeń o pomocy de minimis. Wiążącą wykładnię daje organ udzielający pomocy.

Sumy zawsze pochodzą z kompletu zdarzeń, także gdy lista events została przycięta parametrem limit — wtedy truncated = true i ostrzeżenie sudop_truncated. Przycięcie listy nigdy nie zafałszuje podsumowania.

Agregat podmiotu

GET /v1/subjects/{identifier}

Tożsamość + status VAT + dane KRS jednym zapytaniem. Zwraca „kopertę kopert”: pole sources zawiera pełne koperty poszczególnych źródeł, więc każde z nich ma własny status i własne timestamps.

Pole completeness mówi, ile źródeł odpowiedziało, a notIncluded — które źródła z oferty świadomie pominięto i dlaczego. Dziś są to CRBR (minimalizacja danych osobowych), BZP i SUDOP (osobne, cięższe zapytania).

Zapytania zbiorcze

Status VAT dla wielu NIP-ów

POST /v1/batch/vat
curl -s https://api.debtstrike.eu/v1/batch/vat \
  -H "Authorization: Bearer ds_live_TWOJ_KLUCZ" \
  -H "Content-Type: application/json" \
  -d '{"nips":["5252344078","7740001454"],"date":"2026-07-26"}'

Maksimum 30 NIP-ów. MF liczy to jako jedno zapytanie i my też — koszt 1 jednostki niezależnie od liczby podmiotów. Powyżej 30 pozycji: 413.

Weryfikacja wielu rachunków

POST /v1/batch/vat/bank-accounts
{
  "pairs": [
    { "nip": "5252344078", "account": "61109000040000000143936780" }
  ],
  "date": "2026-07-26"
}

Maksimum 200 par. Odpowiada wyłącznie z naszego indeksu pliku płaskiego, więc nie zużywa limitów MF — koszt 1 jednostki.

Wynik not_confirmed to nie to samo co „rachunek nie należy”. Oznacza brak potwierdzenia w pliku (np. rachunek wirtualny opisany maską). Po odpowiedź definitywną użyj pojedynczego GET /v1/vat/{nip}/bank-account/{account}, który dopytuje API MF. Ta różnica jest w odpowiedzi powtórzona jako ostrzeżenie.

Rozliczenie paczki. Każde sprawdzenie to jedna jednostka — paczka 50 NIP-ów = 50 zapytań, niezależnie od wyniku sprawdzenia. „Niepotwierdzone" liczy się tak samo jak „potwierdzone", bo jedno i drugie jest odpowiedzią. Zero kosztują wyłącznie pozycje odrzucone na walidacji (invalid_input) — tak samo jak 400 na pojedynczym zapytaniu.

Gdy indeks nie obejmuje wskazanego dnia, dostajesz 422 index_does_not_cover_date zamiast cichego zgadywania. Aktualny dzień indeksu sprawdzisz w katalogu źródeł.

Certyfikaty zapytań

GET /v1/certificates/{id}

Dodaj ?certificate=true do dowolnego zapytania, a zamrozimy odpowiedź i policzymy jej odcisk HMAC. Dostajesz identyfikator, adres i sam odcisk w meta.certificate.

Do czego to służy: dowód, że o tej godzinie rejestr odpowiedział dokładnie tak. Przydaje się w audycie, w sporze z kontrahentem i wszędzie tam, gdzie trzeba wykazać należytą staranność — np. że w dniu przelewu rachunek widniał na białej liście.

Bez dopłaty, w każdym pakiecie. Certyfikat nie jest osobnym zapytaniem i nie zużywa dodatkowej jednostki — to ten sam, już rozliczony fakt z rejestru, tylko zamrożony i podpisany. Liczba certyfikatów jest z natury ograniczona liczbą zapytań w pakiecie, więc nie potrzebuje osobnego limitu.

Certyfikat wystawiamy wyłącznie dla odpowiedzi ze statusem found; przy not_found dostajesz ostrzeżenie certificate_skipped. Wymagany jest zakres certificate przy kluczu.

Jak długo żyją

Certyfikaty są poza retencją, która po 90 dniach czyści cache, a po 400 dniach audyt — w trakcie trwania umowy nie usuwa ich nic. Po jej rozwiązaniu pozostają dostępne do pobrania przez 12 miesięcy, a potem są usuwane. To celowy kompromis: „na zawsze” nie miałoby podstawy prawnej ani końca, a skasowanie ich z dnia na dzień zniszczyłoby dowody, na których klient oparł swój audyt.

Katalog źródeł

GET /v1/sources

Lista źródeł z ich dostępnością, organem prowadzącym, stanem indeksów (m.in. na jaki dzień mamy plik płaski VAT i jaki zakres pokrywa indeks BZP). Pytaj o to zamiast zaszywać stałe w kodzie — dodanie źródła zobaczysz tu pierwszy.

Stan usługi

GET /health bez klucza
GET /health/vat-flat bez klucza

Pierwszy to liveness. Drugi mówi, na jaki dzień mamy indeks pliku płaskiego i ile wpisów zawiera — 503 oznacza brak indeksu, czyli że weryfikacja rachunków idzie przez API MF (wolniej i za budżetem, ale działa).

Kontrakt OpenAPI

GET /v1/openapi.json bez klucza

Pełny kontrakt w OpenAPI 3.1, dostępny bez uwierzytelniania — bo klient ma móc wygenerować sobie SDK, nie pytając nas o nic. Przykłady w kontrakcie pochodzą z prawdziwych odpowiedzi produkcji, nie z wyobraźni.

# klient TypeScript
npx openapi-typescript https://api.debtstrike.eu/v1/openapi.json -o registry-api.d.ts

# klient dowolny (Java, C#, Go, Python…)
openapi-generator-cli generate -i https://api.debtstrike.eu/v1/openapi.json \
  -g java -o ./registry-api-client

Uzyskaj dostęp

POST /v1/access-requests bez klucza

Klucze wydajemy ręcznie, po rozmowie — ten formularz nie zakłada konta i nie zwraca klucza. Zbieramy z niego dokładnie to, co i tak musielibyśmy dopytać mailem: kto pyta, do czego i w jakiej skali. Dostaniesz parę ds_test_… (piaskownica, darmowa) i ds_live_… (dane produkcyjne).

Wolisz mailem? kontakt@debtstrike.eu — napisz to samo własnymi słowami.