nipdata.pl — Dokumentacja REST API

Wersja: 0.2.0 (Faza 1 MVP)
Data: maj 2026
Środowisko: produkcyjne, https://api.nipdata.pl
Kontakt techniczny:


Spis treści

  1. Wprowadzenie
  2. Quick Start
  3. Środowiska i base URL
  4. Autoryzacja
  5. Endpointy - GET /v1/company/{nip} - GET /v1/health - GET /v1/ready - GET /v1/version
  6. Schemat odpowiedzi: CompanyData
  7. Kody błędów
  8. Walidacja NIP
  9. Wydajność i limity
  10. Logowanie zgodności (compliance)
  11. Roadmap
  12. Przykłady kodu
  13. FAQ

1. Wprowadzenie

nipdata.pl to REST API do weryfikacji polskich firm po numerze NIP. Łączy dane z dwóch źródeł:

Jedno wywołanie GET /v1/company/{nip} zwraca znormalizowaną, połączoną odpowiedź z obu źródeł. API wywołuje GUS i Białą Listę równolegle, łącząc wyniki przed odesłaniem klientowi.

Zastosowania: - Weryfikacja kontrahenta przy rejestracji w aplikacji - Auto-uzupełnianie danych do faktury (nazwa, adres, REGON) - Weryfikacja statusu VAT i rachunku bankowego (split payment, należyta staranność) - Rejestracja dowodu weryfikacji w logach zgodności (KAS requestId)


2. Quick Start

Krok 1: Uzyskaj token

Token bearer otrzymuje się od administratora nipdata.pl. To jeden statyczny ciąg, np. -vxBMNkwNozPx-2uTR8XI0soD-tgTHXqK0hbNHfYYfA (43 znaki, base64-url-safe).

Przechowuj token jako secret (zmienna środowiskowa, secret manager — nigdy w repozytorium kodu).

Krok 2: Pierwszy request

curl -H "Authorization: Bearer <TWOJ_TOKEN>" \
     https://api.nipdata.pl/v1/company/5252344078

Powinieneś otrzymać HTTP 200 z danymi firmy Google Poland.

Krok 3: Parsuj odpowiedź

Odpowiedź to JSON zgodny ze schematem CompanyData. Najważniejsze pola:

data.name                  // nazwa firmy
data.regon                 // REGON
data.address.city          // miasto
data.vat.status            // "czynny" | "zwolniony" | "niezarejestrowany"
data.vat.bank_accounts     // ["IBAN1", "IBAN2", ...]
data.vat.request_id        // dowód weryfikacji MF — zachowaj w logu compliance

3. Środowiska i base URL

Środowisko Base URL Status
Produkcja https://api.nipdata.pl live
Sandbox w opracowaniu

Wszystkie endpointy dostępne są wyłącznie po HTTPS (TLS 1.2+, certyfikat Let's Encrypt). Próby połączenia po HTTP są przekierowywane na HTTPS (HTTP 301).

API obsługuje wyłącznie JSON (request body — n/d, response — Content-Type: application/json; charset=utf-8).

Schemat URL endpointu danych firmowych:

https://api.nipdata.pl/v1/company/{nip}
└────────────────────┘ └┘ └─────┘ └─┘
        host           v1   res    id

4. Autoryzacja

API używa schematu HTTP Bearer Authentication (RFC 6750).

W każdym chronionym żądaniu dodaj nagłówek:

Authorization: Bearer <TWOJ_TOKEN>

Endpointy publiczne (bez tokenu)

Trzy endpointy są celowo publiczne — używają ich systemy monitoringu i load balancery:

Błędy autoryzacji

Sytuacja HTTP error.code
Brak nagłówka Authorization 401 missing_bearer_token
Nagłówek obecny ale token nie pasuje 401 invalid_token

Każda odpowiedź 401 zawiera nagłówek WWW-Authenticate: Bearer realm="nipdata.pl".

Bezpieczeństwo


5. Endpointy

5.1 GET /v1/company/{nip}

Pobiera znormalizowane dane firmy łącząc równolegle GUS i Białą Listę.

Auth: wymagana (Bearer)

Path params:

Parametr Typ Opis
nip string, 10 cyfr NIP polski. Spacje, myślniki i prefix PL są ignorowane (np. 525-234-40-78, PL5252344078 i 5252344078 są równoważne).

Query params: brak.

Headers:

Authorization: Bearer <TWOJ_TOKEN>
Accept: application/json

Przykład request:

curl -H "Authorization: Bearer <TWOJ_TOKEN>" \
     https://api.nipdata.pl/v1/company/5252344078

Przykład odpowiedzi (HTTP 200):

{
  "nip": "5252344078",
  "regon": "140182840",
  "krs": "0000240611",
  "name": "GOOGLE POLAND SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ",
  "legal_form": "Osoba prawna",
  "legal_form_code": "OSP",
  "address": {
    "street": "Rondo Ignacego Daszyńskiego",
    "building_no": "2C",
    "apartment_no": null,
    "postal_code": "00-843",
    "city": "Warszawa",
    "voivodeship": "MAZOWIECKIE",
    "country": "PL"
  },
  "pkd": [],
  "registration_date": "2005-10-06",
  "status": "aktywna",
  "vat": {
    "status": "czynny",
    "bank_accounts": [
      "93103015080000000504162006",
      "37103015080000000504162044",
      "34103015080000000504162001"
    ],
    "request_id": "bZMVJ-976hcnb",
    "checked_for_date": "2026-05-04"
  },
  "sources": ["gus", "biala_lista"],
  "fetched_at": "2026-05-04T12:38:12.035845Z",
  "cache_hit": false
}

Możliwe odpowiedzi:

HTTP Sytuacja error.code
200 OK — firma znaleziona w przynajmniej jednym źródle
401 brak/niepoprawny token missing_bearer_token / invalid_token
404 NIP poprawny ale firma nie istnieje w GUS i Białej Liście not_found
422 NIP nie ma poprawnej sumy kontrolnej invalid_nip
502 wszystkie źródła zewnętrzne niedostępne all_sources_unavailable
504 timeout (GUS lub BL przekroczyły 30 s) gateway_timeout

Pola w odpowiedzi: zob. sekcję CompanyData.

Uwagi: - Pola null lub puste tablice ([]) są pomijane w odpowiedzi (response_model_exclude_none=True). - Każde wywołanie zapisuje immutable snapshot danych w bazie nipdata (do historii i audytu — pole request_id z BL jest dowodem KAS). - sources mówi które źródło faktycznie odpowiedziało. Możliwe wartości: ["gus","biala_lista"], ["gus"] (BL padło), ["biala_lista"] (GUS padło). Jedno źródło wystarczy do HTTP 200 — brak obu daje HTTP 404 lub 502.


5.2 GET /v1/health

Liveness probe. Zawsze HTTP 200 jeśli proces FastAPI żyje. Nie sprawdza zależności.

Auth: brak.

Request:

curl https://api.nipdata.pl/v1/health

Response (HTTP 200):

{ "status": "ok" }

Używaj jako kontrola "czy serwer w ogóle odpowiada" (np. uptime monitoring).


5.3 GET /v1/ready

Readiness probe. Sprawdza dostępność bazy PostgreSQL i wszystkich adapterów (GUS, Biała Lista, VIES, KRS, CEIDG).

Auth: brak.

Request:

curl https://api.nipdata.pl/v1/ready

Response (HTTP 200):

{
  "status": "ok",
  "db": true,
  "adapters": {
    "gus": true,
    "biala_lista": true,
    "vies": false,
    "krs": false,
    "ceidg": false
  },
  "environment": "production",
  "gus_environment": "production"
}

Pola:

Pole Typ Opis
status string ok jeśli DB i kluczowe adaptery działają, degraded w przeciwnym razie
db boolean czy połączenie z PostgreSQL działa
adapters.gus boolean czy klucz GUS jest skonfigurowany (sprawdzenie statyczne)
adapters.biala_lista boolean aktywny probe API MF (timeout 5 s)
adapters.vies / krs / ceidg boolean false w MVP — adaptery w roadmapie
environment string production lub staging
gus_environment string production lub test

status: "degraded" może oznaczać częściową niedostępność, ale endpoint główny dalej może odpowiadać (np. tylko jedno źródło padło).


5.4 GET /v1/version

Zwraca wersję API. Używana do debugowania.

Auth: brak.

Response (HTTP 200):

{ "version": "0.2.0", "api_version": "v1" }

api_version to prefix URL (zawsze v1 w ramach tej dokumentacji). version to wersja kodu — bumpowana przy każdym deployu.


6. Schemat odpowiedzi: CompanyData

Główny model zwracany przez /v1/company/{nip}. Wszystkie pola są opcjonalne (null / brak), z wyjątkiem oznaczonych jako wymagane.

{
  nip:                string                  // wymagane, 10 cyfr
  regon:              string | null           // 9 lub 14 cyfr
  krs:                string | null           // 10 cyfr (z wiodącymi zerami)
  name:               string | null           // pełna nazwa firmy (z GUS lub BL)
  short_name:         string | null           // krótka nazwa — puste w MVP
  legal_form:         string | null           // np. "Osoba prawna"
  legal_form_code:    string | null           // "OSP" | "JDG" | "JL_P" | "JL_F"

  address: {
    street:           string | null
    building_no:      string | null
    apartment_no:     string | null
    postal_code:      string | null           // format "00-843"
    city:             string | null
    voivodeship:      string | null           // np. "MAZOWIECKIE"
    country:          string                   // domyślnie "PL"
  } | null

  pkd: [                                       // puste w MVP, dorzucone w kolejnej iteracji
    {
      code:           string                   // np. "73.11.Z"
      description:    string
      is_main:        boolean
    }
  ]

  registration_date:  string | null            // format ISO "YYYY-MM-DD"
  suspension_date:    string | null
  dissolution_date:   string | null
  status:             string | null            // "aktywna" | "zakończona"

  vat: {
    status:           "czynny" | "zwolniony" | "niezarejestrowany"   // wymagane
    bank_accounts:    string[]                 // lista IBAN-ów (26 cyfr)
    request_id:       string | null            // dowód weryfikacji MF (KLUCZOWE — zapisz!)
    checked_for_date: string                   // wymagane, format "YYYY-MM-DD"
  } | null

  vies_valid:         boolean | null           // Faza 1+ (VAT-UE)
  vies_checked_at:    string | null            // ISO timestamp

  krs_info: {                                  // Faza 1+ (KRS API)
    krs:              string
    legal_form:       string | null
    capital_grosze:   number | null
    board:            BoardMember[]
    partners:         Partner[]
    representation_rules: string | null
  } | null

  ceidg_active:       boolean | null           // Faza 1+

  sources:            string[]                 // wymagane, np. ["gus", "biala_lista"]
  fetched_at:         string                   // wymagane, ISO timestamp UTC
  cache_hit:          boolean                  // wymagane, czy dane z cache
}

Typy zagnieżdżone

BoardMember (członek zarządu — KRS, Faza 1+):

{ name: string, surname: string, function: string, appointed_at: string | null, revoked_at: string | null }

Partner (wspólnik — KRS, Faza 1+):

{ name: string | null, surname: string | null, company_name: string | null, shares: string | null }
Kod Pełna nazwa (legal_form) Pochodzenie
OSP Osoba prawna sp. z o.o., S.A., spółdzielnia, fundacja, etc.
JDG Jednoosobowa działalność gospodarcza osoba fizyczna prowadząca DG
JL_P Jednostka lokalna osoby prawnej oddział, filia spółki
JL_F Jednostka lokalna osoby fizycznej oddział JDG

Bardziej szczegółowa forma prawna (np. "SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ") będzie dostępna po rozszerzeniu adaptera GUS o pełny raport (kolejna iteracja).


7. Kody błędów

Wszystkie błędy zwracają jednolity format:

{
  "detail": {
    "error": {
      "code": "<machine_readable_code>",
      "message": "<human_readable_message_pl>"
    }
  }
}

Pełna lista kodów

HTTP code Komunikat Kiedy
400 (FastAPI default) Validation error NIP w URL nie pasuje do regex ^[0-9-]{10,13}$
401 missing_bearer_token Brak nagłówka Authorization: Bearer brak nagłówka Authorization
401 invalid_token Nieprawidłowy bearer token token obecny ale nie pasuje
404 not_found Firma o NIP X nie istnieje w GUS ani Białej Liście NIP poprawny syntactic + checksum, ale nie znaleziono
422 invalid_nip NIP X ma nieprawidłową sumę kontrolną NIP nie przechodzi walidacji checksum
502 all_sources_unavailable GUS i Biała Lista niedostępne oba zewnętrzne API padły lub timeout
504 gateway_timeout Timeout — GUS lub Biała Lista przekroczyły 30 s rzadkie, zwykle awaria po stronie MF/GUS

Strategia retry

HTTP Retry? Backoff
401, 422, 404 NIE błąd po stronie klienta
502, 504 TAK exponential, max 3 próby (np. 1s → 3s → 9s)
5xx inne TAK jak wyżej

8. Walidacja NIP

NIP jest walidowany dwustopniowo:

  1. Format: regex ^[0-9-]{10,13}$ (10 cyfr ± do 3 myślników, np. 525-234-40-78).
  2. Checksum: standardowy polski algorytm sumy kontrolnej:
weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]
checksum = sum(NIP[i] * weights[i] for i in 0..8) mod 11
NIP jest poprawny  checksum != 10 AND checksum == NIP[9]

Niepoprawny NIP daje HTTP 422 zanim API w ogóle odpyta GUS lub Białą Listę (chroni przed marnowaniem zewnętrznych quot).

API akceptuje też prefix PL (PL5252344078) — jest automatycznie usuwany.


9. Wydajność i limity

Latencja

Scenariusz Typowa latencja Komentarz
Pierwsze wywołanie po starcie procesu ~700 ms login GUS + równoległy fetch BL
Kolejne wywołania (sesja GUS aktywna) ~600-700 ms równoległy fetch GUS+BL
Cache HIT (Faza 1+) <50 ms po wdrożeniu cache PostgreSQL
Niepoprawny NIP (HTTP 422) <50 ms bez wywołań zewnętrznych
Firma nieznana (HTTP 404) ~600 ms pełen fetch wykonany, brak danych

Limity zewnętrzne (warto znać)

Limity nipdata.pl (Faza 1)

W tej fazie brak rate limitingu po stronie nipdata (zaufanie do bearer token + monitoring). W Fazie 2 zostaną wprowadzone limity per-token.

Timeouts


10. Logowanie zgodności (compliance)

vat.request_id to unikalny identyfikator zapytania nadany przez Białą Listę MF. Zachowanie go w bazie 2SECURE jest dowodem że weryfikacja kontrahenta miała miejsce — może być przedstawiony przy ewentualnej kontroli KAS jako element należytej staranności.

Zalecany schemat zapisu w bazie kontrahenta:

CREATE TABLE contractor_verifications (
  id              SERIAL PRIMARY KEY,
  contractor_nip  VARCHAR(10) NOT NULL,
  vat_status      VARCHAR(32) NOT NULL,         -- "czynny" / "zwolniony" / "niezarejestrowany"
  vat_request_id  VARCHAR(32),                  -- ← klucz: dowód MF
  bank_accounts   JSONB,                        -- snapshot rachunków na moment weryfikacji
  checked_for_date DATE NOT NULL,
  verified_at     TIMESTAMPTZ DEFAULT NOW(),
  source          VARCHAR(32) DEFAULT 'nipdata.pl'
);

Zapisuj rekord przy każdym wywołaniu które kończy się 200 OK, niezależnie od tego czy używasz danych do faktury, czy do procesu wewnętrznego. To minimalny narzut a zapewnia ślad audytowy.


11. Roadmap

Faza Zakres Termin
MVP / Faza 1 GUS basic + Biała Lista, bearer auth gotowe (maj 2026)
Faza 1+ PKD + pełne pola GUS (DanePobierzPelnyRaport) kolejna iteracja
Faza 1+ Cache PostgreSQL UNLOGGED (~50 ms dla powtórek) kolejna iteracja
Faza 1+ Endpoint /v1/iban/check?nip=&iban=&date= (weryfikacja konkretnego rachunku) na żądanie
Faza 1+ VIES (VAT-UE) — vies.valid, vies.trader_name po cache
Faza 2 Per-organizacja klucze API + audit log + rate limit per-key Q3 2026
Faza 2 Webhooks (zmiana statusu VAT, wykreślenie z BL) Q3 2026
Faza 2 KRS API — zarząd, wspólnicy, kapitał Q3 2026
Faza 3 History point-in-time (/v1/company/{nip}/history?at=) Q4 2026
Faza 3 Raport PDF z pieczęcią PAdES (z archiwumpodpisu.pl) Q4 2026

12. Przykłady kodu

12.1 PHP

<?php
$nip = '5252344078';
$token = getenv('NIPDATA_TOKEN');

$ch = curl_init("https://api.nipdata.pl/v1/company/{$nip}");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer {$token}"],
    CURLOPT_TIMEOUT        => 35,
    CURLOPT_CONNECTTIMEOUT => 10,
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 200) {
    $d = json_decode($body, true);
    echo "Firma: {$d['name']}\n";
    echo "REGON: {$d['regon']}\n";
    echo "VAT: {$d['vat']['status']}\n";
    echo "Konta na BL: " . count($d['vat']['bank_accounts']) . "\n";
    echo "request_id (zapisz!): {$d['vat']['request_id']}\n";
} elseif ($status === 404) {
    echo "Brak firmy o tym NIP w GUS/BL\n";
} elseif ($status === 422) {
    echo "Niepoprawny NIP (suma kontrolna)\n";
} else {
    $err = json_decode($body, true);
    echo "Błąd HTTP {$status}: {$err['detail']['error']['message']}\n";
}

12.2 Python (httpx)

import httpx
import os
from typing import Optional

NIPDATA_TOKEN = os.environ["NIPDATA_TOKEN"]


def get_company(nip: str) -> Optional[dict]:
    """Zwraca dane firmy lub None gdy NIP poprawny ale firma nieznana."""
    response = httpx.get(
        f"https://api.nipdata.pl/v1/company/{nip}",
        headers={"Authorization": f"Bearer {NIPDATA_TOKEN}"},
        timeout=35.0,
    )
    if response.status_code == 422:
        raise ValueError(f"Niepoprawny NIP: {nip}")
    if response.status_code == 404:
        return None
    response.raise_for_status()
    return response.json()


# Użycie
data = get_company("5252344078")
if data:
    print(f"Firma: {data['name']}")
    print(f"VAT status: {data['vat']['status']}")
    print(f"Rachunki: {data['vat']['bank_accounts']}")
    print(f"Dowód weryfikacji (request_id): {data['vat']['request_id']}")

12.3 Python (requests)

import requests, os

resp = requests.get(
    "https://api.nipdata.pl/v1/company/5252344078",
    headers={"Authorization": f"Bearer {os.environ['NIPDATA_TOKEN']}"},
    timeout=35,
)
resp.raise_for_status()
data = resp.json()

12.4 JavaScript (Node.js, fetch)

const NIPDATA_TOKEN = process.env.NIPDATA_TOKEN;

async function getCompany(nip) {
    const response = await fetch(
        `https://api.nipdata.pl/v1/company/${nip}`,
        {
            headers: { 'Authorization': `Bearer ${NIPDATA_TOKEN}` },
            signal: AbortSignal.timeout(35_000),
        }
    );
    if (response.status === 404) return null;
    if (response.status === 422) throw new Error('Niepoprawny NIP');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
}

const data = await getCompany('5252344078');
console.log(data.name, data.vat.status);

12.5 cURL — szybki sanity check

# GET dane firmy
curl -H "Authorization: Bearer $NIPDATA_TOKEN" \
     https://api.nipdata.pl/v1/company/5252344078 | jq

# Health (publiczne)
curl https://api.nipdata.pl/v1/ready | jq

# Test 422 — niepoprawny NIP
curl -i -H "Authorization: Bearer $NIPDATA_TOKEN" \
     https://api.nipdata.pl/v1/company/1234567890

# Test 401 — brak tokenu
curl -i https://api.nipdata.pl/v1/company/5252344078

13. FAQ

Q: Czy mogę odpytywać API z przeglądarki (frontend)?
A: Nie zalecane. CORS jest zamknięty, plus token byłby w kodzie klienta = wyciek. Wywołuj zawsze z backendu.

Q: Co jeśli oba źródła (GUS i BL) padną jednocześnie?
A: HTTP 502 z error.code=all_sources_unavailable. To rzadkie — w praktyce GUS i BL nie padają w tym samym czasie. Zaleca się retry z exponential backoff.

Q: Czy vat.bank_accounts są zawsze aktualne?
A: Tak — pochodzą z aktualnego stanu Białej Listy MF na dzień vat.checked_for_date (= dzisiaj UTC). API odpytuje BL przy każdym wywołaniu (do czasu wdrożenia cache).

Q: Co jeśli firma została wykreślona z REGON, ale GUS jeszcze ją zwraca?
A: Pole dissolution_date będzie ustawione, a status: "zakończona". Nie filtruj zakończonych po stronie API — niech klient zdecyduje (czasem trzeba pokazać historyczne dane do faktury z przeszłości).

Q: Czy API działa dla NIP-ów osób fizycznych (JDG)?
A: Tak. Pole legal_form_code będzie wówczas JDG, a niektóre pola zarządu/wspólników (które nie dotyczą JDG) będą puste. Adres siedziby = adres działalności gospodarczej.

Q: Czy mogę testować bez tokenu?
A: Endpointy publiczne (/v1/health, /v1/ready, /v1/version, /docs) — tak. Główny endpoint /v1/company/{nip} — wymaga tokenu.

Q: Jak otrzymać Swagger UI?
A: https://api.nipdata.pl/docs — interaktywna dokumentacja. Wpisz tam token w "Authorize" raz i możesz testować endpointy z przeglądarki.

Q: Jakie polskie podmioty obsługujecie?
A: Wszystkie zarejestrowane w REGON (firmy, JDG, fundacje, stowarzyszenia, urzędy, kościoły) — to ~6 mln podmiotów.


Wersja dokumentacji: 1.0
Wygenerowana: 2026-05-04
Zwróć uwagę: dokument dotyczy API v0.2.0. Sprawdź GET /v1/version dla aktualnej wersji deploya.


Wygenerowano z docs/API.md · Swagger UI · OpenAPI JSON · Markdown source