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

zapytanie
curl 'https://edofinansowania.pl/api/v1/grants?voivodeship=PL-MZ&installationTypes=pv' \
  -H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'
odpowiedź
{
  "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 }
}
nabór do 2026-09-30zostało 55 dni

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.

uwierzytelnianie
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ń.

format klucza
sk_live_9f3Kq2mVXbT8sLpR7dWnA4uZcE1yH6jG0oB5tNfQiMx1a2b3c4d
└──┬───┘└────────────────────┬────────────────────┘└──┬───┘
prefiks         43 znaki losowe (base64url)    suma kontrolna

Klucz sekretny kontra publikowalny

Różnice między rodzajami kluczy
Klucz sekretnyKlucz publikowalny
Prefikssk_live_pk_live_
Gdzie działaWywołania serwer do serwera. Trzymaj go po stronie backendu.Wyłącznie przeglądarka, wyłącznie ze zgłoszonych domen.
CORSBrak 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.
DomenyLista 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.

Zakresy i należące do nich trasy
ZakresTrasy
grantsGET /v1/grants, GET /v1/grants/changes, GET /v1/grants/{id}
eligibilityPOST /v1/eligibility/check, POST /v1/eligibility/run
metaGET /v1/meta/vocabularies
bez zakresuGET /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ź 304 zuż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-After nic nie kosztuje poza czasem.
  • GET /v1/health nie zużywa niczego, bo kończy się przed licznikiem. Możesz go odpytywać z monitoringu tak często, jak chcesz.
  • Odpowiedzi 4xx i 5xx z 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łówki limitów
NagłówekZnaczenie
X-RateLimit-LimitIle żądań na minutę dopuszcza ten klucz.
X-RateLimit-RemainingIle zostało w bieżącej minucie.
X-RateLimit-ResetCzas uniksowy końca bieżącego okna minutowego.
X-Quota-LimitMiesięczna pula całego konta.
X-Quota-RemainingIle zostało w tym miesiącu.
X-Quota-ResetCzas uniksowy początku nowego okresu rozliczeniowego.
RateLimit, RateLimit-PolicyTen 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-AfterPo ilu sekundach ponowić. Tylko przy odpowiedzi 429.
ETagTylko 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'
Przykładowa odpowiedź
{
  "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'
Przykładowa odpowiedź
{
  "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'
Przykładowa odpowiedź
{
  "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'
Przykładowa odpowiedź
{
  "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"]}'
Przykładowa odpowiedź
{
  "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"}}'
Przykładowa odpowiedź
{
  "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'
Przykładowa odpowiedź
{
  "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.

Rodzaje instalacji
KodEtykieta
pvFotowoltaika
heat_pumpPompa ciepła
wind_turbineTurbina wiatrowa
energy_storageMagazyn energii
biomass_boilerKocioł na biomasę
solar_thermalKolektory słoneczne
thermal_modernisationTermomodernizacja
machineryMaszyny
ev_chargerŁadowarka EV
hydroelectricHydroelektrownia
biogas_plantBiogazownia
odpowiedź na wartość spoza słownika
{
  "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ą 200 pod GET /v1/grants/{id}, z polem isOpen: 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ć.

Znaczenie pola matched
WartośćZnaczenie
matched: trueKryterium zostało sprawdzone i przeszło.
matched: falseKryterium zostało sprawdzone i nie przeszło.
matched: nullKryterium nie było sprawdzane, bo nie podałeś go w profilu. Towarzyszy mu reason: not_filtered. To nie jest zgoda ani odmowa.
via: exactDotacja wymienia to konkretne województwo. Warto to pokazać, bo brzmi mocniej w rozmowie z klientem.
via: nationwideDotacja pasuje, bo obejmuje cały kraj, a nie dlatego, że wymienia województwo klienta.
valuesPrzy instalacjach: część wspólna tego, o co pytałeś, i tego, co dotacja finansuje.
dopasowanie po pełnym profilu
{
  "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"] }
      }
    }
  ]
}
profil z samym województwem
"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: incomplete z polem nextQuestion albo complete z polem verdict.
  • Pole path to pytania faktycznie odwiedzone, w kolejności. Nadaje się do zapisania w historii klienta jako ślad, na jakiej podstawie zapadł werdykt.
  • progress.totalQuestions jest 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.

pierwsza strona
curl 'https://edofinansowania.pl/api/v1/grants?limit=200' \
  -H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'
odpowiedź
{
  "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.

kolejna strona
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.

synchronizacja przyrostowa
curl 'https://edofinansowania.pl/api/v1/grants/changes?since=2026-07-30T09:00:00.000Z' \
  -H 'Authorization: Bearer sk_live_TWOJ_KLUCZ'
odpowiedź
{
  "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 syncedAt i odeślij je jako since nastę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 upserted to rekordy do wstawienia albo zaktualizowania. Klucz to id, a nie grantCode.
  • Lista removed mówi, co wypadło z katalogu, a pole reason, co z tym zrobić. expired to zakończony nabór, warto zachować rekord w historii klienta. unpublished i deleted to rekordy do usunięcia. Pokrywa się to z zachowaniem GET /v1/grants/{id}: dotacja wygasła nadal odpowiada 200, pozostałe dwie 404.
  • Endpoint nie stronicuje i nie obsługuje ETag, bo syncedAt zmienia się przy każdym wywołaniu. 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. 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.

żądanie warunkowe
# 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/api użyj nagłówka X-If-None-Match. Warstwa proxy nie przekazuje standardowego If-None-Match do serwera, więc żądanie z samym nagłówkiem standardowym zawsze dostanie pełną odpowiedź. Sprawdzone na produkcji, nie wywnioskowane z dokumentacji proxy.
  • 304 oszczędza transfer, nie limit. Licznik minutowy rusza na bramce, zanim zajrzymy do bazy.
  • ETag jest liczony z całej odpowiedzi razem ze stroną wyników, więc zmiana parametrów zapytania to inny ETag. 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.

start
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":{}}'
pierwsze pytanie
{
  "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.

kolejny krok
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.

werdykt
{
  "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ą 422 z listą unreachable.
  • Zapisz engine.version razem 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.
  • failedCondition nadaje się do pokazania handlowcowi, bo mówi, który warunek przesądził. message jest 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.

przykład
{
  "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.

przykład
{
  "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ąć.

przykład
{
  "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.

przykład
{
  "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.

przykład
{
  "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.

przykład
{
  "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.

przykład
{
  "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.

przykład
{
  "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.
przykład
{
  "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.
przykład
{
  "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.

przykład
{
  "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.
przykład
{
  "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.

przykład
{
  "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.
przykład
{
  "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.
przykład
{
  "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.
przykład
{
  "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.

przykład
{
  "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ł.
przykład
{
  "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.

przykład
{
  "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.

openapi.json