API v1.0.0
eDofinansowania Partner API
Komercyjne API dla partnerów. Zaciągnij katalog dotacji do swojego systemu, dopasuj dotacje do profilu klienta i przeprowadź pełne sprawdzenie kwalifikowalności. Wartości słownikowe są kodami: województwa jako ISO 3166-2:PL, wielkość firmy i rodzaje instalacji w snake_case. Każdy kod przychodzi z polską etykietą do wyświetlenia, więc nie musisz jej wymyślać; filtry przyjmują wyłącznie kod. Odpowiedzi w silniku pytań mają stabilne identyfikatory, a tekst obok nich to etykieta, którą można przeredagować bez zmiany ścieżki. Komunikaty błędów są po angielsku, tak jak reszta kontraktu. Treść redakcyjna - tytuły i opisy dotacji, treść pytań kwalifikacyjnych i komunikaty werdyktów - jest po polsku i po polsku zostaje. To dane, nie interfejs: polskie programy dotacyjne mają polskie nazwy. Każda odpowiedź niesie nagłówek Content-Language, więc niczego nie trzeba zakładać.
Szybki start
curl 'https://edofinansowania.pl/api/v1/grants?voivodeship=PL-MZ&installationTypes=pv' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"data": [
{
"grantCode": "EDF-0004",
"title": "Kredyt ekologiczny",
"fundingAmount": "do 80% kosztów kwalifikowanych",
"applicationDeadline": "2026-09-30",
"eligibleInstallations": ["pv", "heat_pump"],
"hasEligibilityEngine": true
}
],
"pagination": { "hasMore": false, "nextCursor": null }
}Odpowiedzi w skrócie
- Adres bazowy
- https://edofinansowania.pl/api
- Uwierzytelnianie
- Authorization: Bearer sk_live_... (albo nagłówek X-API-Key)
- Limit
- 60 zapytań na minutę, pula miesięczna ustalana per partner
- Format błędów
- RFC 9457 Problem Details, application/problem+json
- Wersjonowanie
- Wersja w ścieżce (/v1). Zmiana łamiąca oznaczałaby nową wersję, a wycofanie starej ogłosimy z co najmniej 6-miesięcznym wyprzedzeniem.
- Synchronizacja
- GET /v1/grants/changes zwraca, co doszło i co wypadło z katalogu. Zapisz syncedAt i odeślij je jako since.
Klucze, limity, nagłówki
Zanim wyślesz pierwsze zapytanie
Klucz wystawiasz sobie sam, bez kontaktu z nami. Zaloguj się, wejdź na /profile, otwórz zakładkę dostępu do API i podaj nazwę firmy - dostaniesz klucz sk_live_ od razu, w całości, jeden jedyny raz. Zapisz go w tym momencie: trzymamy wyłącznie skrót, więc odtworzyć go nie umiemy, a zgubiony wymienia się na nowy.
Wymaga aktywnej subskrypcji - to samo, co odblokowuje treść dotacji w serwisie. Jeśli jej nie masz, zakładka po prostu się nie pokaże, a nie pokaże ci odmowy po wypełnieniu formularza. Klucz z panelu żyje 90 dni i panel pokazuje datę wygaśnięcia; klucz kontraktowy nie wygasa.
Środowiska testowego nie ma i nie planujemy go, bo nie ma czego odseparowywać: katalog dotacji to dane publiczne, a wszystkie endpointy tylko czytają - żadne wywołanie niczego u nas nie zmienia. Eksperymentuj na swoim kluczu; jedyne, co zużyjesz, to przydział zapytań, a ten wraca pierwszego dnia miesiąca.
Pierwsze wywołanie zrób na GET /v1/meta/vocabularies, a nie na /v1/health. Health odpowiada bez klucza, więc dowodzi tylko tego, że API żyje - nie że twój klucz działa. Słowniki wymagają klucza, zwracają dane i od razu dają ci wartości, których potrzebujesz do filtrów.
Jak podpisać żądanie
Każde żądanie poza GET /v1/health wymaga klucza. Klucz podajesz w nagłówku Authorization ze schematem Bearer albo, jeśli tak wygodniej Twojej bibliotece, w nagłówku X-API-Key. Obie drogi są równoważne i prowadzą do tego samego sprawdzenia.
curl 'https://edofinansowania.pl/api/v1/health' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'
# to samo, jeśli Twoja biblioteka woli własny nagłówek
curl 'https://edofinansowania.pl/api/v1/health' \
-H 'X-API-Key: sk_live_TWOJ_KLUCZ'Budowa klucza
Klucz to prefiks, 43 znaki losowe i 8 znaków sumy kontrolnej. Suma kontrolna jest liczona z części losowej, więc klucz z literówką albo obcięty przy kopiowaniu odpada, zanim dotkniemy bazy. Praktyczny wniosek: jeśli dostajesz problem-invalid-key natychmiast, to prawie zawsze wina kopiowania, a nie uprawnień.
sk_live_9f3Kq2mVXbT8sLpR7dWnA4uZcE1yH6jG0oB5tNfQiMx1a2b3c4d
└──┬───┘└────────────────────┬────────────────────┘└──┬───┘
prefiks 43 znaki losowe (base64url) suma kontrolnaKlucz sekretny kontra publikowalny
| Klucz sekretny | Klucz publikowalny | |
|---|---|---|
| Prefiks | sk_live_ | pk_live_ |
| Gdzie działa | Wywołania serwer do serwera. Trzymaj go po stronie backendu. | Wyłącznie przeglądarka, wyłącznie ze zgłoszonych domen. |
| CORS | Brak nagłówka Access-Control-Allow-Origin. Wyciekniętego klucza nie da się użyć ze strony WWW. | Odpowiedź niesie Access-Control-Allow-Origin dla domeny, z której przyszła. |
| Domeny | Lista domen musi być pusta. | Lista domen musi mieć co najmniej jedną pozycję. Żądanie spoza niej albo bez nagłówka Origin dostaje 403. |
| Limit na minutę | 60 na koncie kontraktowym, 30 na założonym samodzielnie. | Domyślnie 30, osobny licznik. Widget na publicznej stronie nie może przepalić limitu integracji z CRM. |
| Ważność | Klucz kontraktowy bezterminowo, klucz założony samodzielnie w panelu 90 dni. | Tak samo jak dla klucza sekretnego. |
Blokada domeny przy kluczu publikowalnym jest zabezpieczeniem przed nadużyciem, nie zabezpieczeniem kryptograficznym. Prawdomówny nagłówek Origin wysyła tylko przeglądarka, a narzędzie wiersza poleceń może go podrobić. Dlatego klucz publikowalny nie dostaje niczego, czego nie można pokazać publicznie.
Zakresy
Konto partnera ma włączone zakresy, a każda trasa należy do jednego z nich. Trasa spoza Twoich zakresów odpowiada problem-endpoint-not-enabled i jest to ustawienie po naszej stronie, nie przełącznik w panelu.
| Zakres | Trasy |
|---|---|
| grants | GET /v1/grants, GET /v1/grants/changes, GET /v1/grants/{id} |
| eligibility | POST /v1/eligibility/check, POST /v1/eligibility/run |
| meta | GET /v1/meta/vocabularies |
| bez zakresu | GET /v1/health, jedyna trasa bez klucza |
Limity
Obowiązują dwa liczniki naraz: na minutę i na miesiąc, a wartości zależą od tego, skąd masz konto. Konto założone samodzielnie w panelu dostaje 30 żądań na minutę i 2000 miesięcznie. Konto kontraktowe startuje z 60 na minutę dla klucza sekretnego, 30 dla publikowalnego i 50 000 miesięcznie, i te liczby ustalamy per partner. Wiążące zawsze jest to, co przychodzi w nagłówkach - panel pokazuje wartości twojego konta. Okno minutowe jest stałe, nie przesuwne: zaczyna się o pełnej sekundzie zerowej każdej minuty.
- Odpowiedź
304zużywa limit. Licznik rusza na bramce, zanim ustalimy, czy cokolwiek się zmieniło. Warunkowe żądanie oszczędza transfer, nie pulę. - Żądanie odrzucone z powodu limitu minutowego nie zjada puli miesięcznej. Ponowienie po
Retry-Afternic nie kosztuje poza czasem. GET /v1/healthnie zużywa niczego, bo kończy się przed licznikiem. Możesz go odpytywać z monitoringu tak często, jak chcesz.- Odpowiedzi
4xxi5xxz uwierzytelnionej ścieżki liczą się normalnie. Pętla ponawiająca błędne żądanie wyczerpie pulę tak samo jak pętla poprawna.
Nagłówki odpowiedzi
| Nagłówek | Znaczenie |
|---|---|
| X-RateLimit-Limit | Ile żądań na minutę dopuszcza ten klucz. |
| X-RateLimit-Remaining | Ile zostało w bieżącej minucie. |
| X-RateLimit-Reset | Czas uniksowy końca bieżącego okna minutowego. |
| X-Quota-Limit | Miesięczna pula całego konta. |
| X-Quota-Remaining | Ile zostało w tym miesiącu. |
| X-Quota-Reset | Czas uniksowy początku nowego okresu rozliczeniowego. |
| RateLimit, RateLimit-Policy | Ten sam stan w postaci z draftu draft-ietf-httpapi-ratelimit-headers. Draft nie jest jeszcze RFC, więc wiążące są nagłówki X-, a te jadą dla klientów, które draft już wspierają. |
| Retry-After | Po ilu sekundach ponowić. Tylko przy odpowiedzi 429. |
| ETag | Tylko przy GET /v1/grants. Odeślij w If-None-Match, a przy braku zmian dostaniesz 304 bez treści. |
Jedna rzecz, której nie widać po samych nazwach: nagłówki limitów pojawiają się dopiero wtedy, gdy wiadomo, czyj jest klucz. Odpowiedź 401 i trzy z czterech odmian 403 zapadają wcześniej i nie niosą żadnego licznika. Nie buduj więc sterowania ponawianiem na założeniu, że X-RateLimit-Remaining jest zawsze obecny.
Przy kluczu publikowalnym przeglądarka pokaże te nagłówki tylko dlatego, że wymieniamy je w Access-Control-Expose-Headers. Bez tego widget nie miałby jak się wycofać przed wyczerpaniem limitu.
Authentication
Klucze API i przydziały żądań.
GET/v1/health
Sprawdź, czy API odpowiada
Jedyna trasa dostępna bez klucza. Odpowiada na jedno pytanie - czy API żyje - i na nic więcej: żadnych danych katalogu, żadnych danych partnera, i nie zużywa twojego przydziału. Dzięki temu można ją wpiąć we własny monitoring bez trzymania tam żywego klucza, a podczas awarii odróżnia nasz problem od twojego.
curl -X GET 'https://edofinansowania.pl/api/v1/health' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"status": "ok",
"version": "v1",
"revision": "0ee0a0abbf75e321edcf81440f94ab761158a3e2"
}Responses
- 200
- API odpowiada.
Grants
Katalog dotacji i szczegóły pojedynczej dotacji.
GET/v1/grants
Katalog dotacji
Zwraca opublikowane dotacje, których termin naboru jeszcze nie minął, posortowane rosnąco po tym terminie. Filtry są opcjonalne, a pominięty niczego nie zawęża. Stronicowanie jest kursorowe: zapisz nextCursor i wysyłaj go przy kolejnym żądaniu, dopóki hasMore nie będzie false. Do utrzymywania katalogu w synchronizacji użyj GET /v1/grants/changes zamiast updatedSince, bo tamten endpoint zgłasza także dotacje, które z katalogu wypadły. Odpowiedź niesie ETag: odeślij go w If-None-Match, żeby dostać 304 bez ciała.
Parameters
voivodeshipstring · query- Kod województwa (ISO 3166-2:PL). Dotacja oznaczona NATIONWIDE pasuje do każdego województwa, więc wraca niezależnie od tego, które wyślesz. Wysłanie NATIONWIDE jest filtrem samym w sobie i zawęża odpowiedź do programów bez ograniczenia regionalnego - nie znaczy "dowolny region".
Dozwolonych wartości (17)
PL-DS- dolnośląskie
PL-KP- kujawsko-pomorskie
PL-LU- lubelskie
PL-LB- lubuskie
PL-LD- łódzkie
PL-MA- małopolskie
PL-MZ- mazowieckie
PL-OP- opolskie
PL-PK- podkarpackie
PL-PD- podlaskie
PL-PM- pomorskie
PL-SL- śląskie
PL-SK- świętokrzyskie
PL-WN- warmińsko-mazurskie
PL-WP- wielkopolskie
PL-ZP- zachodniopomorskie
NATIONWIDE- ogólnopolski
companySizestring · query- Wielkość firmy klienta.
Dozwolonych wartości (6)
micro- Mikro (< 10 pracowników)
small- Małe (< 50 pracowników)
medium- Średnie (< 250 pracowników)
large- Duże (≥ 250 pracowników)
small_mid_cap- Small mid-cap (250-499 pracowników)
mid_cap- Mid-cap (500-3000 pracowników)
installationTypesarray · query- Kody rodzajów instalacji, rozdzielone przecinkami. Łączone przez OR: dotacja obejmująca którykolwiek z nich jest dopasowaniem.
Dozwolonych wartości (11)
pv- Fotowoltaika
heat_pump- Pompa ciepła
wind_turbine- Turbina wiatrowa
energy_storage- Magazyn energii
biomass_boiler- Kocioł na biomasę
solar_thermal- Kolektory słoneczne
thermal_modernisation- Termomodernizacja
machinery- Maszyny
ev_charger- Ładowarka EV
hydroelectric- Hydroelektrownia
biogas_plant- Biogazownia
limitinteger · query · domyślnie 50- Wierszy na stronę. Koercjonowane, nie odrzucane: wartość powyżej 200 staje się 200, a zero, liczba ujemna albo cokolwiek nieparsowalnego staje się domyślną pięćdziesiątką. `pagination.limit` w odpowiedzi zawsze niesie wartość faktycznie użytą, więc odczytaj ją zamiast zakładać, że twoja została przyjęta.
cursorstring · query- Wartość `nextCursor` z poprzedniej odpowiedzi. Nie konstruuj jej sam - jest nieprzejrzysta po to, żeby kolejność sortowania mogła się zmienić bez psucia twojej integracji.
updatedSincestring · query- Tylko dotacje zmienione po tej chwili (ISO 8601). Porównuj z polem `updatedAt` rekordów, które już masz, a nie z własnym zegarem: znacznik wyprzedzający nasz o więcej niż minutę jest odrzucany 400-tką, bo spieszący zegar pomijałby wszystkie zmiany z tego okresu i nigdy by po nie nie wrócił.
If-None-Matchstring · header- ETag z poprzedniej odpowiedzi. Jeśli katalog się nie zmienił, odpowiedzią jest 304 bez ciała.
X-If-None-Matchstring · header- To samo co If-None-Match. Użyj tego nagłówka, jeśli łączysz się przez edofinansowania.pl/api - warstwa proxy nie przekazuje standardowego If-None-Match do serwera źródłowego.
includearray · query- Dodatkowe bloki do wysłania razem z każdą dotacją, rozdzielone przecinkami. `eligibility` dokłada graf pytań dotacji, co pozwala przeprowadzić sprawdzenie w całości u siebie: przejście grafu samodzielnie nie kosztuje żadnego żądania, a POST /v1/eligibility/run kosztuje jedno na pytanie. Domyślnie pominięte, bo dokłada około 700 bajtów na dotację.
eligibility
curl -X GET 'https://edofinansowania.pl/api/v1/grants?voivodeship=PL-MZ&companySize=small&installationTypes=pv%2Cheat_pump&updatedSince=2026-07-01T00%3A00%3A00Z&include=eligibility' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"data": [
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"grantCode": "EDF-0004",
"title": "Kredyt ekologiczny",
"description": "Kredyt na inwestycje podnoszące efektywność energetyczną przedsiębiorstwa.",
"fundingAmount": "do 50 000 000 zł",
"fundingRate": "do 80%",
"applicationDeadline": "2026-09-30",
"documentationUrl": "https://www.bgk.pl/kredyt-ekologiczny",
"applicationUrl": null,
"updatedAt": "2026-07-31T09:14:22.117Z",
"eligibleLocations": [
"NATIONWIDE"
],
"eligibleInstallations": [
"heat_pump",
"pv"
],
"eligibleCompanySizes": [
"medium",
"small"
],
"hasEligibilityEngine": true
}
],
"pagination": {
"limit": 50,
"hasMore": true,
"nextCursor": "eyJkIjoiMjAyNi0wOS0zMCIsImkiOiIzZjI1MDRlMCJ9"
}
}Responses
- 200
- Strona wyników.
- 304
- Katalog nie zmienił się od podanego ETagu. Bez ciała odpowiedzi. Uwaga: 304 też liczy się do przydziału. Przez edofinansowania.pl/api użyj nagłówka X-If-None-Match, bo warstwa proxy nie przekazuje standardowego.
- 400
- Nieprawidłowe zapytanie - odpowiedź zawiera listę dozwolonych wartości.
- 401
- Brak klucza albo klucz nieprawidłowy, odwołany lub wygasły.
- 403
- Klucz jest poprawny, ale to zapytanie jest niedozwolone: domena spoza listy, zawieszone konto, wygasła subskrypcja albo endpoint poza zakresem klucza. Pole type mówi który z tych czterech. Wymiana klucza nie pomoże na żaden.
- 429
- Przekroczony limit. Nagłówek Retry-After mówi, kiedy ponowić.
- 500
- Błąd po naszej stronie. Osobnym przypadkiem jest niespójny silnik kwalifikowalności - odpowiada tym samym statusem pod własnym polem type i ponowienie go nie naprawi.
GET/v1/grants/changes
Co się zmieniło od ostatniej synchronizacji
Zwraca dwie listy: `upserted` (dotacje do dodania lub zaktualizowania) i `removed` (dotacje, które wypadły z katalogu). Bez tej drugiej katalog partnera po kilku miesiącach zbiera martwe rekordy z nieaktualnymi terminami. Zapisz `syncedAt` z odpowiedzi i odeślij jako `since` przy kolejnym wywołaniu. Nie używaj do tego własnego zegara: system spieszący się o kilka minut trwale pomijałby okno zmian, bez żadnego objawu. `reason` mówi, co zrobić z rekordem: `expired` znaczy, że okno naboru się zamknęło (warto zachować dla historii), a `unpublished` i `deleted` znaczą, że rekord ma zniknąć. Zgadza się to z tym, co zwraca `GET /v1/grants/{id}`: dotacja `expired` nadal odpowiada 200, pozostałe 404. Endpoint nie stronicuje i nie obsługuje `ETag` - `syncedAt` zmienia się przy każdym wywołaniu, więc żądanie warunkowe nigdy by nie trafiło. Obie listy mają sufit: `upserted` 200 pozycji, `removed` 500. Kiedy którakolwiek go dotknie, `truncated` jest true - wtedy nie zapisuj `syncedAt`, tylko pobierz katalog w całości przez `GET /v1/grants`, bo reszta zmian nie przyjdzie w kolejnym wywołaniu.
Parameters
sincestring · query- Wartość `syncedAt` z poprzedniej odpowiedzi. Pominięta oznacza pełny zrzut katalogu.
curl -X GET 'https://edofinansowania.pl/api/v1/grants/changes?since=2026-07-30T09%3A00%3A00.000Z' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"since": "2026-07-20T00:00:00Z",
"syncedAt": "2026-07-31T09:14:22.117Z",
"upserted": [
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"grantCode": "EDF-0004",
"title": "Kredyt ekologiczny",
"description": "Kredyt na inwestycje podnoszące efektywność energetyczną przedsiębiorstwa.",
"fundingAmount": "do 50 000 000 zł",
"fundingRate": "do 80%",
"applicationDeadline": "2026-09-30",
"documentationUrl": "https://www.bgk.pl/kredyt-ekologiczny",
"applicationUrl": null,
"updatedAt": "2026-07-31T09:14:22.117Z",
"eligibleLocations": [
"NATIONWIDE"
],
"eligibleInstallations": [
"heat_pump",
"pv"
],
"eligibleCompanySizes": [
"medium",
"small"
],
"hasEligibilityEngine": true
}
],
"removed": [
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3399",
"grantCode": "EDF-0002",
"reason": "expired",
"removedAt": "2026-07-30T10:00:00.000Z"
}
],
"truncated": false
}Responses
- 200
- Co się zmieniło od podanego znacznika.
- 400
- Nieprawidłowe zapytanie - odpowiedź zawiera listę dozwolonych wartości.
- 401
- Brak klucza albo klucz nieprawidłowy, odwołany lub wygasły.
- 403
- Klucz jest poprawny, ale to zapytanie jest niedozwolone: domena spoza listy, zawieszone konto, wygasła subskrypcja albo endpoint poza zakresem klucza. Pole type mówi który z tych czterech. Wymiana klucza nie pomoże na żaden.
- 429
- Przekroczony limit. Nagłówek Retry-After mówi, kiedy ponowić.
- 500
- Błąd po naszej stronie. Osobnym przypadkiem jest niespójny silnik kwalifikowalności - odpowiada tym samym statusem pod własnym polem type i ponowienie go nie naprawi.
GET/v1/grants/{id}
Szczegóły dotacji
Pełny rekord: warunki, dokumenty, FAQ, harmonogram i sekcje opisowe. Identyfikatorem może być UUID albo kod dotacji, na przykład EDF-0004. Dotacje po terminie też są zwracane, z isOpen ustawionym na false, żeby CRM trzymający stare odwołanie dostał treść, a nie 404. Odpowiedź 404 znaczy tu, że dotacja została wycofana z katalogu albo skasowana.
Parameters
idstring · path · wymagany- UUID dotacji albo jej kod - przyjmujemy jedno i drugie.
includearray · query- Dodatkowe bloki do wysłania razem z każdą dotacją, rozdzielone przecinkami. `eligibility` dokłada graf pytań dotacji, co pozwala przeprowadzić sprawdzenie w całości u siebie: przejście grafu samodzielnie nie kosztuje żadnego żądania, a POST /v1/eligibility/run kosztuje jedno na pytanie. Domyślnie pominięte, bo dokłada około 700 bajtów na dotację.
eligibility
curl -X GET 'https://edofinansowania.pl/api/v1/grants/EDF-0004?include=eligibility' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"data": {
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"grantCode": "EDF-0004",
"title": "Kredyt ekologiczny",
"description": "Kredyt na inwestycje podnoszące efektywność energetyczną przedsiębiorstwa.",
"fundingAmount": "do 50 000 000 zł",
"fundingRate": "do 80%",
"applicationDeadline": "2026-09-30",
"documentationUrl": "https://www.bgk.pl/kredyt-ekologiczny",
"applicationUrl": null,
"updatedAt": "2026-07-31T09:14:22.117Z",
"eligibleLocations": [
"NATIONWIDE"
],
"eligibleInstallations": [
"heat_pump",
"pv"
],
"eligibleCompanySizes": [
"medium",
"small"
],
"hasEligibilityEngine": true,
"applicantInfo": "Mikro, małe i średnie przedsiębiorstwa oraz small mid-caps.",
"supportedInvestments": "Modernizacja energetyczna budynków, wymiana źródeł ciepła, instalacje OZE.",
"paymentConditions": "Premia ekologiczna wypłacana po zakończeniu inwestycji.",
"security": "Weksel in blanco wraz z deklaracją wekslową.",
"supportForm": "Kredyt z premią ekologiczną",
"isOpen": true,
"documents": [
{
"name": "Regulamin naboru",
"url": "https://www.bgk.pl/regulamin.pdf",
"position": 1
}
],
"faqs": [
{
"question": "Kto może złożyć wniosek?",
"answer": "Przedsiębiorcy z sektora MŚP oraz small mid-caps.",
"position": 1
}
],
"sections": [
{
"title": "Zakres wsparcia",
"content": "Inwestycje podnoszące efektywność energetyczną.",
"position": 1
}
],
"timeline": [
{
"title": "Nabór wniosków",
"description": "Od 1 marca do 30 września 2026.",
"position": 1
}
]
}
}Responses
- 200
- Dotacja.
- 401
- Brak klucza albo klucz nieprawidłowy, odwołany lub wygasły.
- 403
- Klucz jest poprawny, ale to zapytanie jest niedozwolone: domena spoza listy, zawieszone konto, wygasła subskrypcja albo endpoint poza zakresem klucza. Pole type mówi który z tych czterech. Wymiana klucza nie pomoże na żaden.
- 404
- Zasób nie istnieje albo partner tego klucza go nie widzi. Wycofana dotacja odpowiada tak samo - wycofania zgłasza /v1/grants/changes.
- 429
- Przekroczony limit. Nagłówek Retry-After mówi, kiedy ponowić.
- 500
- Błąd po naszej stronie. Osobnym przypadkiem jest niespójny silnik kwalifikowalności - odpowiada tym samym statusem pod własnym polem type i ponowienie go nie naprawi.
Eligibility
Dopasowanie po profilu firmy i pełny silnik pytań.
POST/v1/eligibility/check
Dopasowanie dotacji do profilu klienta
Wyślij to, co twój CRM już ma, i odbierz dotacje, które pasują, każdą z powodem dopasowania. Kryterium, którego nie podałeś, ma matched równe null, co znaczy "nie sprawdzaliśmy", a nie "przeszło". Reguła dopasowania jest ta sama co w katalogu: kryteria łączą się przez AND, lista instalacji przez OR, a dotacja ogólnopolska pasuje do każdego województwa. Odpowiedź nie stronicuje i jest ucinana na 200 dotacjach, więc bardzo szerokie profile warto zawęzić filtrami.
Request body
Wszystkie pola są opcjonalne. Pominięte kryterium nie jest filtrowane, co znaczy co innego niż dopasowanie do wszystkiego.
voivodeshipstring · wymagany · może być null- Województwo klienta jako kod ISO 3166-2:PL. Null albo brak pola zostawia kryterium niefiltrowane - to nie to samo co dopasowanie do wszędzie. GET /v1/meta/vocabularies zwraca te kody razem z polskimi nazwami. Wysłanie NATIONWIDE jest filtrem samym w sobie i zawęża odpowiedź do programów bez ograniczenia regionalnego - nie znaczy "dowolny region".
Dozwolonych wartości (17)
PL-DS- dolnośląskie
PL-KP- kujawsko-pomorskie
PL-LU- lubelskie
PL-LB- lubuskie
PL-LD- łódzkie
PL-MA- małopolskie
PL-MZ- mazowieckie
PL-OP- opolskie
PL-PK- podkarpackie
PL-PD- podlaskie
PL-PM- pomorskie
PL-SL- śląskie
PL-SK- świętokrzyskie
PL-WN- warmińsko-mazurskie
PL-WP- wielkopolskie
PL-ZP- zachodniopomorskie
NATIONWIDE- ogólnopolski
companySizestring · wymagany · może być null- Wielkość firmy klienta według definicji MŚP obowiązującej w UE. Null albo brak pola zostawia kryterium niefiltrowane.
Dozwolonych wartości (6)
micro- Mikro (< 10 pracowników)
small- Małe (< 50 pracowników)
medium- Średnie (< 250 pracowników)
large- Duże (≥ 250 pracowników)
small_mid_cap- Small mid-cap (250-499 pracowników)
mid_cap- Mid-cap (500-3000 pracowników)
installationTypesarray · wymagany · może być null- Co klient chce zainstalować. Łączone przez OR: dotacja obejmująca którykolwiek z tych rodzajów jest dopasowaniem. Null znaczy, że wywołujący nie przysłał listy w ogóle, co jest czym innym niż lista pusta - i odpowiedź odsyła to z powrotem dokładnie tak, jak przyszło.
Dozwolonych wartości (11)
pv- Fotowoltaika
heat_pump- Pompa ciepła
wind_turbine- Turbina wiatrowa
energy_storage- Magazyn energii
biomass_boiler- Kocioł na biomasę
solar_thermal- Kolektory słoneczne
thermal_modernisation- Termomodernizacja
machinery- Maszyny
ev_charger- Ładowarka EV
hydroelectric- Hydroelektrownia
biogas_plant- Biogazownia
curl -X POST 'https://edofinansowania.pl/api/v1/eligibility/check' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ' \
-H 'Content-Type: application/json' \
-d '{"voivodeship":"PL-MZ","companySize":"small","installationTypes":["pv"]}'{
"profile": {
"voivodeship": "PL-MZ",
"companySize": "small",
"installationTypes": [
"pv"
]
},
"total": 1,
"truncated": false,
"data": [
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"grantCode": "EDF-0004",
"title": "Kredyt ekologiczny",
"description": "Kredyt na inwestycje podnoszące efektywność energetyczną przedsiębiorstwa.",
"fundingAmount": "do 50 000 000 zł",
"fundingRate": "do 80%",
"applicationDeadline": "2026-09-30",
"documentationUrl": "https://www.bgk.pl/kredyt-ekologiczny",
"applicationUrl": null,
"updatedAt": "2026-07-31T09:14:22.117Z",
"eligibleLocations": [
"NATIONWIDE"
],
"eligibleInstallations": [
"heat_pump",
"pv"
],
"eligibleCompanySizes": [
"medium",
"small"
],
"hasEligibilityEngine": true,
"matchedOn": {
"voivodeship": {
"matched": true,
"via": "nationwide",
"value": "NATIONWIDE"
},
"companySize": {
"matched": true,
"value": "small"
},
"installationTypes": {
"matched": true,
"values": [
"pv"
]
}
}
}
]
}Responses
- 200
- Dopasowane dotacje, każda z powodem dopasowania.
- 400
- Nieprawidłowe zapytanie - odpowiedź zawiera listę dozwolonych wartości.
- 401
- Brak klucza albo klucz nieprawidłowy, odwołany lub wygasły.
- 403
- Klucz jest poprawny, ale to zapytanie jest niedozwolone: domena spoza listy, zawieszone konto, wygasła subskrypcja albo endpoint poza zakresem klucza. Pole type mówi który z tych czterech. Wymiana klucza nie pomoże na żaden.
- 429
- Przekroczony limit. Nagłówek Retry-After mówi, kiedy ponowić.
- 500
- Błąd po naszej stronie. Osobnym przypadkiem jest niespójny silnik kwalifikowalności - odpowiada tym samym statusem pod własnym polem type i ponowienie go nie naprawi.
POST/v1/eligibility/run
Pełne sprawdzenie kwalifikowalności
Bezstanowe. Wyślij komplet zebranych dotąd odpowiedzi, a dostaniesz albo kolejne pytanie, albo werdykt. Przebiegiem steruje twój system: my odtwarzamy przejście od pytania startowego, więc nie ma sesji do wygaszenia ani stanu do posprzątania. Jeśli wolisz przejść graf u siebie i nie płacić żądaniem za pytanie, pobierz go przez GET /v1/grants?include=eligibility - ten endpoint pozostaje rozstrzygający i tylko on zwraca komunikat werdyktu.
Request body
grantIdstring · wymagany- UUID dotacji albo jej kod - przyjmujemy jedno i drugie.
answersobject- Mapa z identyfikatora pytania na identyfikator wybranej opcji. Wyślij komplet zebranych dotąd odpowiedzi: endpoint jest bezstanowy i odtwarza przejście od początku.
curl -X POST 'https://edofinansowania.pl/api/v1/eligibility/run' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ' \
-H 'Content-Type: application/json' \
-d '{"grantId":"EDF-0004","answers":{"q1":"yes"}}'{
"status": "incomplete",
"engine": {
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3401",
"version": "1.2.0"
},
"nextQuestion": {
"id": "q1",
"text": "Czy prowadzisz działalność gospodarczą?",
"type": "yes-no",
"options": [
{
"id": "yes",
"label": "Tak"
},
{
"id": "no",
"label": "Nie"
}
],
"description": null,
"helpText": null
},
"progress": {
"answered": 0,
"totalQuestions": 6
},
"path": []
}Responses
- 200
- Kolejne pytanie albo werdykt.
- 400
- Nieprawidłowe zapytanie - odpowiedź zawiera listę dozwolonych wartości.
- 401
- Brak klucza albo klucz nieprawidłowy, odwołany lub wygasły.
- 403
- Klucz jest poprawny, ale to zapytanie jest niedozwolone: domena spoza listy, zawieszone konto, wygasła subskrypcja albo endpoint poza zakresem klucza. Pole type mówi który z tych czterech. Wymiana klucza nie pomoże na żaden.
- 404
- Zasób nie istnieje albo partner tego klucza go nie widzi. Wycofana dotacja odpowiada tak samo - wycofania zgłasza /v1/grants/changes.
- 422
- Zapytanie poprawne składniowo, ale niespójne.
- 429
- Przekroczony limit. Nagłówek Retry-After mówi, kiedy ponowić.
- 500
- Błąd po naszej stronie. Osobnym przypadkiem jest niespójny silnik kwalifikowalności - odpowiada tym samym statusem pod własnym polem type i ponowienie go nie naprawi.
Vocabularies
Wartości, które przyjmują filtry.
GET/v1/meta/vocabularies
Wartości, które przyjmują filtry
Zbuduj z tego listy rozwijane w swoim CRM. Każda pozycja niesie kanoniczny kod, który przyjmują filtry, oraz polską etykietę, którą czyta twój użytkownik - etykiety to treść redakcyjna o polskich programach i mechaniczne ich tłumaczenie byłoby błędem, którego czytelnik nie miałby jak wykryć. NATIONWIDE nie jest kodem terytorialnym; oznacza dotację obejmującą cały kraj.
curl -X GET 'https://edofinansowania.pl/api/v1/meta/vocabularies' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"voivodeships": [
{
"code": "PL-MZ",
"label": "mazowieckie",
"nationwide": false
},
{
"code": "NATIONWIDE",
"label": "ogólnopolski",
"nationwide": true
}
],
"companySizes": [
{
"code": "micro",
"label": "mikro"
},
{
"code": "small",
"label": "małe"
}
],
"installationTypes": [
{
"code": "pv",
"label": "Fotowoltaika"
},
{
"code": "heat_pump",
"label": "Pompa ciepła"
}
]
}Responses
- 200
- Słowniki.
- 401
- Brak klucza albo klucz nieprawidłowy, odwołany lub wygasły.
- 403
- Klucz jest poprawny, ale to zapytanie jest niedozwolone: domena spoza listy, zawieszone konto, wygasła subskrypcja albo endpoint poza zakresem klucza. Pole type mówi który z tych czterech. Wymiana klucza nie pomoże na żaden.
- 429
- Przekroczony limit. Nagłówek Retry-After mówi, kiedy ponowić.
- 500
- Błąd po naszej stronie. Osobnym przypadkiem jest niespójny silnik kwalifikowalności - odpowiada tym samym statusem pod własnym polem type i ponowienie go nie naprawi.
Kwalifikowalność
Dopasowanie dotacji do klienta opiera się na trzech kryteriach: gdzie klient prowadzi działalność, jak duża jest firma i co chce zainstalować. Te same trzy kryteria działają w katalogu jako filtry zapytania i w POST /v1/eligibility/check jako profil firmy. Rozdział poniżej opisuje dokładnie, jakie wartości są dopuszczalne i jak liczymy trafienie, żeby nie trzeba było tego odtwarzać z zachowania.
Dopuszczalne wartości
Porównanie jest dokładne co do znaku. Nie normalizujemy niczego celowo: wartość spoza słownika kończy się odpowiedzią 400 z listą allowed, a nie pustym wynikiem, bo puste wyniki są znacznie trudniejsze do wykrycia w cudzym systemie.
Województwa jako kody ISO 3166-2:PL. Wartość NATIONWIDE nie jest kodem podziału terytorialnego: tak oznaczamy dotacje obejmujące cały kraj i pasują one do klienta z dowolnego miejsca.
- PL-DS
- PL-KP
- PL-LU
- PL-LB
- PL-LD
- PL-MA
- PL-MZ
- PL-OP
- PL-PK
- PL-PD
- PL-PM
- PL-SL
- PL-SK
- PL-WN
- PL-WP
- PL-ZP
- NATIONWIDE
Wielkość firmy.
- micro
- small
- medium
- large
- small_mid_cap
- mid_cap
Rodzaje instalacji. Do API wysyłasz kod z lewej kolumny, użytkownikowi pokazujesz etykietę z prawej. Oba pola zwraca też GET /v1/meta/vocabularies, więc nie trzeba ich przepisywać do siebie.
| Kod | Etykieta |
|---|---|
| pv | Fotowoltaika |
| heat_pump | Pompa ciepła |
| wind_turbine | Turbina wiatrowa |
| energy_storage | Magazyn energii |
| biomass_boiler | Kocioł na biomasę |
| solar_thermal | Kolektory słoneczne |
| thermal_modernisation | Termomodernizacja |
| machinery | Maszyny |
| ev_charger | Ładowarka EV |
| hydroelectric | Hydroelektrownia |
| biogas_plant | Biogazownia |
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "Unknown voivodeship: PL-XX",
"instance": "/v1/grants",
"allowed": ["PL-DS", "PL-KP", "PL-MZ", "NATIONWIDE"]
}Reguła dopasowania
Dotacja trafia do wyniku, gdy spełnia wszystkie poniższe warunki naraz. Kolejność nie ma znaczenia, ale warto przeczytać je jako całość, bo dwa pierwsze punkty odpowiadają za większość pytań w rodzaju "dlaczego nie widzę dotacji, którą widziałem wczoraj".
- Jest opublikowana. Rekord wycofany z katalogu znika z wyników natychmiast, również wtedy, gdy jego nabór nadal trwa.
- Termin naboru nie minął. Porównujemy z dzisiejszą datą, więc dotacja z terminem dzisiejszym jeszcze się liczy, a jutro zniknie. Szczegóły takiej dotacji nadal odpowiadają
200podGET /v1/grants/{id}, z polemisOpen: false. - Województwo pasuje, gdy dotacja wymienia dokładnie ten kod albo gdy jest oznaczona jako
NATIONWIDE. - Wielkość firmy pasuje, gdy dotacja wymienia dokładnie tę wartość. Nie ma tu odpowiednika jokera ogólnopolskiego.
- Rodzaje instalacji łączą się alternatywą: wystarczy, że dotacja obsługuje jeden z podanych. Kryterium ma sens sprzedażowy, bo klient pytający o fotowoltaikę i magazyn energii chce zobaczyć również program finansujący samą fotowoltaikę.
- Kryterium pominięte nie zawęża niczego. Pusty profil zwraca cały otwarty katalog.
Dlaczego dotacja pasuje
POST /v1/eligibility/check dokłada do każdej dotacji obiekt matchedOn, żeby handlowiec widział powód, a nie samą listę. Trzy stany są tu istotne i nie należy ich mylić.
| Wartość | Znaczenie |
|---|---|
| matched: true | Kryterium zostało sprawdzone i przeszło. |
| matched: false | Kryterium zostało sprawdzone i nie przeszło. |
| matched: null | Kryterium nie było sprawdzane, bo nie podałeś go w profilu. Towarzyszy mu reason: not_filtered. To nie jest zgoda ani odmowa. |
| via: exact | Dotacja wymienia to konkretne województwo. Warto to pokazać, bo brzmi mocniej w rozmowie z klientem. |
| via: nationwide | Dotacja pasuje, bo obejmuje cały kraj, a nie dlatego, że wymienia województwo klienta. |
| values | Przy instalacjach: część wspólna tego, o co pytałeś, i tego, co dotacja finansuje. |
{
"profile": {
"voivodeship": "PL-MZ",
"companySize": "small",
"installationTypes": ["pv", "heat_pump"]
},
"total": 2,
"data": [
{
"grantCode": "EDF-0004",
"title": "Kredyt ekologiczny",
"eligibleLocations": ["NATIONWIDE"],
"eligibleInstallations": ["pv", "heat_pump", "energy_storage"],
"eligibleCompanySizes": ["micro", "small", "medium"],
"matchedOn": {
"voivodeship": { "matched": true, "via": "nationwide", "value": "NATIONWIDE" },
"companySize": { "matched": true, "value": "small" },
"installationTypes": { "matched": true, "values": ["pv", "heat_pump"] }
}
}
]
}"matchedOn": {
"voivodeship": { "matched": true, "via": "exact", "value": "PL-SL" },
"companySize": { "matched": null, "reason": "not_filtered" },
"installationTypes": { "matched": null, "reason": "not_filtered" }
}Ten endpoint nie stronicuje i zwraca najwyżej 200 dotacji, a pole total liczy to, co faktycznie przyszło. Kiedy lista mogła zostać ucięta na tym pułapie, truncated jest true - wtedy zawęź profil albo przejdź na stronicowane GET /v1/grants. False znaczy, że komplet masz.
Sprawdzanie po swojej stronie: include=eligibility
Silnik można pobrać w całości razem z dotacją i przejść go u siebie. GET /v1/grants?include=eligibility dokłada do każdej dotacji graf pytań: treści, opcje i krawędzie. Kosztuje to około 700 bajtów na dotację i ani jednego dodatkowego żądania - cały katalog z silnikami mieści się w jednym wywołaniu przy limit=200. Ten sam parametr działa na GET /v1/grants/{id}.
Różnica jest w liczbie żądań, nie w wyniku. POST /v1/eligibility/run kosztuje jedno żądanie na pytanie, więc mediana pięciu pytań to sześć wywołań na jedną dotację. Graf przechodzony lokalnie kosztuje zero i pozwala pokazać klientowi wszystkie pytania naraz albo cofnąć się do poprzedniego - czego endpoint bezstanowy nie umie, bo cofnięcie zgłasza jako problem-contradictory-answers.
Pole next to albo jeden cel dla każdej odpowiedzi, albo mapa z identyfikatora opcji na cel. Cel wskazujący pytanie z listy questions to następne pytanie; wszystko inne to werdykt, a werdykty są dwa: eligible i not_eligible.
Rozstrzygający zostaje POST /v1/eligibility/run: to jest implementacja, którą uruchamiamy my, i tylko ona zwraca komunikat werdyktu oraz failedCondition. Graf celowo ich nie niesie. Licz lokalnie, żeby przesiać szybko; zawołaj run, żeby mieć pewność i treść do pokazania klientowi.
Pełne sprawdzenie: silnik pytań
Dopasowanie po profilu odpowiada na pytanie "co w ogóle wchodzi w grę". Właściwą kwalifikowalność sprawdza POST /v1/eligibility/run, który prowadzi przez pytania konkretnego programu. Uruchamiaj go tylko dla dotacji z hasEligibilityEngine: true, bo pozostałe nie mają skonfigurowanego silnika.
- Endpoint jest bezstanowy. Za każdym razem wysyłasz komplet zebranych dotąd odpowiedzi, a my odtwarzamy przejście od pytania startowego. Nie ma identyfikatora sesji do przechowywania.
- Odpowiedź ma jeden z dwóch kształtów, rozróżnianych polem
status:incompletez polemnextQuestionalbocompletez polemverdict. - Pole
pathto pytania faktycznie odwiedzone, w kolejności. Nadaje się do zapisania w historii klienta jako ślad, na jakiej podstawie zapadł werdykt. progress.totalQuestionsjest górnym ograniczeniem, nie liczbą dokładną. Graf pytań się rozgałęzia, więc konkretna ścieżka bywa krótsza. Pasek postępu zbudowany na tej liczbie nie dojdzie do końca i lepiej pokazać sam licznik odpowiedzi.- Odsyłaj identyfikator wybranej opcji z
nextQuestion.options, nie jej tekst. Tekst jest etykietą i może zostać przeredagowany; identyfikator jest tym, po czym silnik wybiera gałąź.
Cztery rzeczy mogą pójść nie tak i każda ma własny wpis w katalogu błędów: odpowiedź na pytanie, którego silnik nie zna, odpowiedź spoza dozwolonych opcji, odpowiedzi nieukładające się w jedną ścieżkę oraz wada w samym grafie pytań. Pierwsze trzy to 422 i wynikają z żądania, czwarta to 500 i wynika z naszych danych.
Scenariusze integracji
1. Katalog u siebie i jego odświeżanie
Zacznij od jednego pełnego przejścia po katalogu. Bierz największą dozwoloną stronę, bo limit liczy żądania, a nie rekordy, więc 200 na stronę kosztuje cztery razy mniej niż 50.
curl 'https://edofinansowania.pl/api/v1/grants?limit=200' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"data": [ /* 200 dotacji */ ],
"pagination": {
"limit": 200,
"hasMore": true,
"nextCursor": "eyJkIjoiMjAyNi0wOS0zMCIsImkiOiI3YzQuLi4ifQ"
}
}Dopóki hasMore jest prawdą, powtarzaj z cursor równym nextCursor z poprzedniej odpowiedzi. Przepisuj tę wartość w całości. Kursor jest nieprzezroczysty celowo: pod spodem siedzi pozycja w sortowaniu po terminie naboru, a opakowanie go pozwala nam zmienić sortowanie bez psucia gotowych integracji.
curl 'https://edofinansowania.pl/api/v1/grants?limit=200&cursor=eyJkIjoiMjAyNi0wOS0zMCIsImkiOiI3YzQuLi4ifQ' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'Od tego momentu nie powtarzaj pełnego przejścia. Raz dziennie wystarczy jedno wywołanie GET /v1/grants/changes ze znacznikiem z poprzedniego razu.
curl 'https://edofinansowania.pl/api/v1/grants/changes?since=2026-07-30T09:00:00.000Z' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'{
"since": "2026-07-30T09:00:00.000Z",
"syncedAt": "2026-07-31T09:00:04.812Z",
"upserted": [
{ "id": "7c4...", "grantCode": "EDF-0012", "title": "...", "applicationDeadline": "2026-11-30" }
],
"removed": [
{ "id": "1b9...", "grantCode": "EDF-0031", "reason": "expired", "removedAt": "2026-07-31T00:00:00.000Z" }
],
"truncated": false
}- Zapisz
syncedAti odeślij je jakosincenastępnym razem. Nie używaj do tego własnego zegara. System spieszący się o kilka minut na stałe pomijałby okno zmian, i to bez żadnego objawu, bo odpowiedzi wyglądałyby poprawnie. - Lista
upsertedto rekordy do wstawienia albo zaktualizowania. Klucz toid, a niegrantCode. - Lista
removedmówi, co wypadło z katalogu, a polereason, co z tym zrobić.expiredto zakończony nabór, warto zachować rekord w historii klienta.unpublishedideletedto rekordy do usunięcia. Pokrywa się to z zachowaniemGET /v1/grants/{id}: dotacja wygasła nadal odpowiada200, pozostałe dwie404. - Endpoint nie stronicuje i nie obsługuje
ETag, bosyncedAtzmienia się przy każdym wywołaniu. Obie listy mają sufit:upserted200 pozycji,removed500. Kiedy którakolwiek go dotknie,truncatedjest true - wtedy nie zapisujsyncedAt, tylko pobierz katalog w całości przezGET /v1/grants, bo reszta zmian nie przyjdzie w kolejnym wywołaniu. Po dłuższej przerwie w synchronizacji zrób to samo od razu.
2. Odświeżanie warunkowe
Jeśli odpytujesz katalog z konkretnym filtrem częściej niż raz dziennie, na przykład żeby odświeżyć widok w CRM, użyj ETag. Przy braku zmian odpowiedź to 304 bez treści.
# pierwsze pobranie: zapisz ETag z odpowiedzi
curl -i 'https://edofinansowania.pl/api/v1/grants?voivodeship=PL-MZ' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'
# ETag: W/"3f9a1c08b2d4e6f7a8b9c0d1e2f3a4b5"
# kolejne: odeślij go i licz się z odpowiedzią 304 bez treści
curl -i 'https://edofinansowania.pl/api/v1/grants?voivodeship=PL-MZ' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ' \
-H 'X-If-None-Match: W/"3f9a1c08b2d4e6f7a8b9c0d1e2f3a4b5"'- Przez adres
edofinansowania.pl/apiużyj nagłówkaX-If-None-Match. Warstwa proxy nie przekazuje standardowegoIf-None-Matchdo serwera, więc żądanie z samym nagłówkiem standardowym zawsze dostanie pełną odpowiedź. Sprawdzone na produkcji, nie wywnioskowane z dokumentacji proxy. 304oszczędza transfer, nie limit. Licznik minutowy rusza na bramce, zanim zajrzymy do bazy.ETagjest liczony z całej odpowiedzi razem ze stroną wyników, więc zmiana parametrów zapytania to innyETag. Trzymaj go obok filtra, którego dotyczy.
3. Prowadzenie sprawdzenia kwalifikowalności
Pętlę prowadzi Twój system. Zaczynasz od samego grantId z pustą mapą odpowiedzi.
curl -X POST 'https://edofinansowania.pl/api/v1/eligibility/run' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ' \
-H 'Content-Type: application/json' \
-d '{"grantId":"EDF-0004","answers":{}}'{
"status": "incomplete",
"engine": { "id": "eng_kredyt_eko", "version": "3" },
"nextQuestion": {
"id": "q1",
"text": "Czy firma prowadzi działalność dłużej niż 12 miesięcy?",
"type": "yes-no",
"options": [{ "id": "yes", "label": "Tak" }, { "id": "no", "label": "Nie" }],
"description": null,
"helpText": "Liczy się data rozpoczęcia działalności z CEIDG lub KRS."
},
"progress": { "answered": 0, "totalQuestions": 6 },
"path": []
}Zadajesz pytanie użytkownikowi, dokładasz do mapy id wybranej opcji, nie jej tekst, i wołasz ponownie z kompletem. Nie wysyłasz samej ostatniej odpowiedzi, bo endpoint nie pamięta poprzednich.
curl -X POST 'https://edofinansowania.pl/api/v1/eligibility/run' \
-H 'Authorization: Bearer sk_live_TWOJ_KLUCZ' \
-H 'Content-Type: application/json' \
-d '{"grantId":"EDF-0004","answers":{"q1":"yes"}}'Powtarzasz, dopóki status to incomplete. Werdykt przychodzi w tej samej strukturze, tylko z innym kształtem.
{
"status": "complete",
"engine": { "id": "eng_kredyt_eko", "version": "3" },
"verdict": "not_eligible",
"message": "Program nie obejmuje firm bez historii kredytowej.",
"failedCondition": "Brak zdolności kredytowej potwierdzonej przez bank",
"progress": { "answered": 3, "totalQuestions": 6 },
"path": ["q1", "q2", "q4"]
}- Przy cofnięciu użytkownika usuń z mapy wszystkie odpowiedzi po zmienianym pytaniu. Zostawione odpowiedzi z porzuconej gałęzi dają
422z listąunreachable. - Zapisz
engine.versionrazem z werdyktem. Jeśli zmieni się w trakcie rozmowy, zebrane odpowiedzi mogą przestać układać się w ścieżkę i rozmowę trzeba zacząć od nowa. Zapisana wersja tłumaczy też później, dlaczego ten sam klient dostał inny wynik. failedConditionnadaje się do pokazania handlowcowi, bo mówi, który warunek przesądził.messagejest pisane pod klienta końcowego.
Katalog błędów
Błędy przychodzą jako application/problem+json w formacie RFC 9457. Rozpoznawaj je po polu type, nie po treści title: adres w type jest identyfikatorem i się nie zmienia, a tytuł jest zdaniem dla człowieka i może zostać przeredagowany.
Poza polami type, title, status, detail i instance dokument bywa uzupełniony o pola dodatkowe, wymienione przy odpowiednich wpisach. Zgodnie z RFC leżą one na najwyższym poziomie, a nie w zagnieżdżonym obiekcie.
401Missing API key
problem-missing-key
Żądanie przyszło bez nagłówka Authorization i bez X-API-Key.
Dodaj nagłówek Authorization: Bearer sk_live_... albo X-API-Key. Sprawdź też, czy twoja biblioteka HTTP nie gubi nagłówków przy przekierowaniu, bo kilka robi to domyślnie.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-missing-key",
"title": "Missing API key",
"status": 401,
"detail": "Send the key in an Authorization: Bearer <key> or X-API-Key header.",
"instance": "/v1/grants"
}401Invalid API key
problem-invalid-key
Klucz nie przechodzi kontroli formatu albo sumy kontrolnej, albo nie ma go w naszej bazie. Oba przypadki dają tę samą odpowiedź, żeby nie dało się nią sprawdzać, które klucze istnieją.
Porównaj klucz znak po znaku z tym, co panel pokazał przy jego tworzeniu. Zwykłe przyczyny to ucięta końcówka przy kopiowaniu i spacja doklejona do wartości nagłówka.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-invalid-key",
"title": "Invalid API key",
"status": 401,
"detail": "The key has an invalid format or checksum.",
"instance": "/v1/grants"
}401API key revoked
problem-key-revoked
Klucz istnieje, ale ktoś w twojej organizacji go odwołał.
Wygeneruj nowy klucz w panelu partnera i podmień go w konfiguracji. Odwołania nie da się cofnąć.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-key-revoked",
"title": "API key revoked",
"status": 401,
"instance": "/v1/grants"
}401API key expired
problem-key-expired
Klucz miał datę ważności i ta data minęła. Klucze tworzone w panelu żyją 90 dni; klucze kontraktowe nie wygasają.
Wygeneruj nowy klucz. Jeśli integracja ma działać bez przerwy, ustaw przypomnienie na tydzień przed datą expires_at, którą panel pokazuje przy każdym kluczu.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-key-expired",
"title": "API key expired",
"status": 401,
"instance": "/v1/grants"
}403Origin not allowed for this key
problem-origin-not-allowed
Klucz publikowalny pk_live_ przyszedł z domeny spoza swojej listy albo w ogóle bez nagłówka Origin.
Dodaj domenę do klucza w panelu. Jeśli wołasz z serwera, użyj klucza sekretnego: pk_live_ jest wyłącznie do przeglądarki.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-origin-not-allowed",
"title": "Origin not allowed for this key",
"status": 403,
"instance": "/v1/grants"
}403Partner access suspended
problem-partner-suspended
Konto partnera nie jest aktywne. Blokada obejmuje wszystkie klucze tego konta naraz.
Skontaktuj się z nami. Wygenerowanie nowego klucza niczego nie zmieni, bo blokada jest na koncie, a nie na kluczu.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-partner-suspended",
"title": "Partner access suspended",
"status": 403,
"instance": "/v1/grants"
}403Subscription lapsed
problem-subscription-lapsed
Konto partnera założone samodzielnie, w którym żaden członek nie ma już aktywnej subskrypcji. Sprawdzane przy każdym żądaniu, nie tylko przy zakładaniu konta.
Odnów subskrypcję na koncie dowolnego członka. Dostęp wraca natychmiast, bez zmiany klucza.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-subscription-lapsed",
"title": "Subscription lapsed",
"status": 403,
"instance": "/v1/grants"
}403Endpoint not enabled for your account
problem-endpoint-not-enabled
Trasa należy do zakresu, którego twoje konto nie ma włączonego. Zakresy to grants, eligibility i meta.
Napisz do nas, który zakres włączyć. To zmiana po naszej stronie, nie ustawienie w panelu.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-endpoint-not-enabled",
"title": "Endpoint not enabled for your account",
"status": 403,
"instance": "/v1/eligibility/run"
}429Per-minute rate limit exceeded
problem-rate-limit-exceeded
Przekroczyłeś przydział żądań na minutę. Domyślnie 60 dla klucza sekretnego i 30 dla publikowalnego, ale wartość ustalamy per partner i przychodzi w nagłówku X-RateLimit-Limit.
Odczekaj tyle sekund, ile podaje Retry-After, i ponów. Żądanie odrzucone limitem nie zużywa miesięcznej puli, więc ponowienie kosztuje tylko czas.
- retryAfter
- Sekundy do końca bieżącego okna. Ta sama wartość co w nagłówku Retry-After.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-rate-limit-exceeded",
"title": "Per-minute rate limit exceeded",
"status": 429,
"instance": "/v1/grants",
"retryAfter": 17
}429Monthly quota exhausted
problem-quota-exceeded
Pula na bieżący miesiąc jest wyczerpana. Zeruje się pierwszego dnia następnego, a X-Quota-Reset podaje dokładny moment jako czas uniksowy.
Poczekaj na nowy okres rozliczeniowy albo napisz do nas o większą pulę. Zanim to zrobisz, sprawdź, czy nie synchronizujesz katalogu częściej, niż potrzebujesz: GET /v1/grants/changes kosztuje jedno żądanie tam, gdzie pełne przejście katalogu kosztuje kilkanaście.
- retryAfter
- Sekundy do początku nowego okresu rozliczeniowego.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-quota-exceeded",
"title": "Monthly quota exhausted",
"status": 429,
"instance": "/v1/grants",
"retryAfter": 216000
}404Resource not found
problem-not-found
Ścieżka nie pasuje do żadnej trasy albo nie istnieje zasób o podanym identyfikatorze.
Sprawdź ścieżkę i identyfikator. GET /v1/grants/{id} przyjmuje UUID albo kod dotacji i odpowiada także dla dotacji po terminie, więc 404 znaczy tutaj, że rekord został wycofany albo skasowany, a nie że zamknęło się okno naboru.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-not-found",
"title": "Resource not found",
"status": 404,
"detail": "No published grant with the identifier EDF-9999.",
"instance": "/v1/grants/EDF-9999"
}400Invalid request
problem-invalid-request
Wartość filtra spoza słownika, data w złym formacie, brak wymaganego pola albo ciało, które nie jest poprawnym JSON-em.
Przeczytaj pole detail, które mówi, który parametr jest zły. Przy wartości spoza słownika odpowiedź niesie dodatkowo pole allowed z pełną listą dopuszczalnych wartości, więc nie trzeba zgadywać ani osobno wołać słowników.
- allowed
- Pełna lista dopuszczalnych wartości dla odrzuconego parametru. Obecne tylko przy błędzie słownikowym.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-invalid-request",
"title": "Invalid request",
"status": 400,
"detail": "Unknown voivodeship: PL-XX",
"instance": "/v1/grants",
"allowed": [
"PL-DS",
"PL-KP",
"PL-MZ",
"NATIONWIDE"
]
}400Invalid cursor
problem-invalid-cursor
Parametr cursor nie pochodzi od nas albo został uszkodzony w transporcie.
Kopiuj nextCursor z poprzedniej odpowiedzi w całości i nie składaj go samodzielnie. Kursor jest nieprzejrzysty celowo, żeby zmiana kolejności sortowania nie psuła działających integracji. Po tym błędzie zacznij stronicowanie od początku.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-invalid-cursor",
"title": "Invalid cursor",
"status": 400,
"detail": "A cursor comes from the nextCursor field of a previous response and must not be constructed by hand.",
"instance": "/v1/grants"
}422Unknown question in the answers
problem-unknown-question
Mapa answers zawiera identyfikator pytania, którego silnik tej dotacji nie zna.
Nie przenoś odpowiedzi między dotacjami - identyfikatory pytań są lokalne dla silnika. Pole questionIds w odpowiedzi zawiera wszystkie pytania, które ten silnik zna, co pokazuje, co poszło nie tak.
- unknownQuestions
- Identyfikatory, których silnik nie rozpoznał.
- questionIds
- Wszystkie pytania, które ten silnik zna.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-unknown-question",
"title": "Unknown question in the answers",
"status": 422,
"detail": "The engine does not know these questions: q9.",
"instance": "/v1/eligibility/run",
"unknownQuestions": [
"q9"
],
"questionIds": [
"q1",
"q2",
"q3"
]
}422Invalid answer
problem-invalid-answer
Odpowiedź na pytanie nie jest żadnym z identyfikatorów jego opcji.
Odsyłaj pole id z nextQuestion.options, a nie etykietę ani własny kod. Pole options w odpowiedzi powtarza dopuszczalne opcje dla pytania, które się nie powiodło.
- questionId
- Pytanie, którego odpowiedź została odrzucona.
- options
- Odpowiedzi dopuszczalne dla tego pytania.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-invalid-answer",
"title": "Invalid answer",
"status": 422,
"detail": "The answer to question q2 is not one of its options.",
"instance": "/v1/eligibility/run",
"questionId": "q2",
"options": [
{
"id": "yes",
"label": "Tak"
},
{
"id": "no",
"label": "Nie"
}
]
}422The answers do not form a single path
problem-contradictory-answers
Odpowiedzi dotyczą pytań, do których wybrana ścieżka nie dochodzi. Zwykle dlatego, że użytkownik cofnął się i zmienił wcześniejszą odpowiedź, a odpowiedzi z porzuconej gałęzi zostały w zestawie.
Kiedy użytkownik się cofa, usuń wszystkie odpowiedzi po pytaniu, które zmienił. Pole path pokazuje, którędy przebieg faktycznie poszedł, a unreachable wymienia odpowiedzi do odrzucenia.
- unreachable
- Odpowiedzi na pytania, do których ta ścieżka nie dochodzi.
- path
- Pytania faktycznie odwiedzone, w kolejności.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-contradictory-answers",
"title": "The answers do not form a single path",
"status": 422,
"detail": "The answers given belong to questions this path does not reach.",
"instance": "/v1/eligibility/run",
"unreachable": [
"q5"
],
"path": [
"q1",
"q2"
]
}404No eligibility engine for this grant
problem-no-engine-for-grant
Dotacja nie ma aktywnego silnika pytań.
Sprawdź pole hasEligibilityEngine z katalogu, zanim zaproponujesz pełne sprawdzenie. Dla dotacji bez silnika zostaje POST /v1/eligibility/check, który dopasowuje po profilu firmy.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-no-engine-for-grant",
"title": "No eligibility engine for this grant",
"status": 404,
"detail": "Grant EDF-0004 has no active question engine.",
"instance": "/v1/eligibility/run"
}500The eligibility engine is inconsistent
problem-engine-misconfigured
Graf pytań ma wadę: pytanie bez wyjścia, krawędź do nieistniejącego pytania albo cykl. To usterka w naszych danych, nie w twoim żądaniu.
Zgłoś nam tę dotację razem z polami cause i path z odpowiedzi. Ponawianie tego samego żądania niczego nie da, bo wynik jest deterministyczny.
- cause
- Rodzaj wady: question-not-found, no-next-question, next-question-not-found, start-question-not-found albo cycle-detected.
- path
- Pytania odwiedzone, zanim przebieg się zatrzymał.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-engine-misconfigured",
"title": "The eligibility engine is inconsistent",
"status": 500,
"detail": "Stopped at question q3 (no-next-question).",
"instance": "/v1/eligibility/run",
"cause": "no-next-question",
"path": [
"q1",
"q2",
"q3"
]
}500Server error
problem-internal
Trasa rzuciła wyjątek.
Ponów z rosnącym odstępem. Jeśli utrzymuje się dłużej niż kilka minut, napisz do nas ze ścieżką z pola instance i przybliżonym czasem zdarzenia.
{
"type": "https://edofinansowania.pl/dokumentacja-api#problem-internal",
"title": "Server error",
"status": 500,
"instance": "/v1/grants"
}Kontrakt maszynowy
Ta strona jest renderowana z dokumentu OpenAPI 3.1, a test w naszym CI porównuje ten dokument z faktycznie zaimplementowanymi trasami. Jeśli coś tu przeczytasz, to tak właśnie działa API. Plik nadaje się do wrzucenia w Postmana albo generator klienta.