Wersja: 0.2.0 (Faza 1 MVP)
Data: maj 2026
Środowisko: produkcyjne, https://api.nipdata.pl
Kontakt techniczny:
CompanyDatanipdata.pl to REST API do weryfikacji polskich firm po numerze NIP. Łączy dane z dwóch źródeł:
requestId jako urzędowy dowód weryfikacjiJedno 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)
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).
curl -H "Authorization: Bearer <TWOJ_TOKEN>" \
https://api.nipdata.pl/v1/company/5252344078
Powinieneś otrzymać HTTP 200 z danymi firmy Google Poland.
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
| Ś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
API używa schematu HTTP Bearer Authentication (RFC 6750).
W każdym chronionym żądaniu dodaj nagłówek:
Authorization: Bearer <TWOJ_TOKEN>
Trzy endpointy są celowo publiczne — używają ich systemy monitoringu i load balancery:
GET /v1/health — liveness probeGET /v1/ready — readiness probe (status DB i adapterów)GET /v1/version — wersja APIGET /docs — interaktywna dokumentacja Swagger UIGET /openapi.json — schemat OpenAPI 3.0| 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".
.env + restart kontenera).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.
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).
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).
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.
CompanyDataGłó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
}
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 }
legal_form_code)| 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).
Wszystkie błędy zwracają jednolity format:
{
"detail": {
"error": {
"code": "<machine_readable_code>",
"message": "<human_readable_message_pl>"
}
}
}
| 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 |
| 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 |
NIP jest walidowany dwustopniowo:
^[0-9-]{10,13}$ (10 cyfr ± do 3 myślników, np. 525-234-40-78).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.
| 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 |
error.code=all_sources_unavailable (ale GUS może wciąż odpowiedzieć).W tej fazie brak rate limitingu po stronie nipdata (zaufanie do bearer token + monitoring). W Fazie 2 zostaną wprowadzone limity per-token.
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.
| 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 |
<?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";
}
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']}")
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()
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);
# 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
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