{
  "openapi": "3.1.0",
  "info": {
    "title": "eDofinansowania API dla partnerów",
    "version": "1.0.0",
    "summary": "Katalog dotacji i sprawdzanie kwalifikowalności dla systemów CRM.",
    "description": "API dla partnerów handlowych. Pozwala pobrać katalog dotacji do własnego systemu, dopasować dotacje do profilu klienta i przeprowadzić pełne sprawdzenie kwalifikowalności.",
    "contact": {
      "name": "eDofinansowania",
      "url": "https://edofinansowania.pl"
    }
  },
  "servers": [
    {
      "url": "https://edofinansowania.pl/api",
      "description": "Produkcja"
    }
  ],
  "tags": [
    {
      "name": "Uwierzytelnianie",
      "description": "Klucze API i limity zapytań."
    },
    {
      "name": "Dotacje",
      "description": "Katalog dotacji i szczegóły pojedynczej dotacji."
    },
    {
      "name": "Kwalifikowalność",
      "description": "Dopasowanie po profilu firmy i pełny silnik pytań."
    },
    {
      "name": "Słowniki",
      "description": "Dozwolone wartości filtrów."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "Uwierzytelnianie"
        ],
        "summary": "Sprawdzenie dostępności API",
        "description": "Najprostszy sposób na potwierdzenie, że klucz działa. Wymaga klucza, tak jak każdy inny endpoint.",
        "responses": {
          "200": {
            "description": "API odpowiada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "status": "ok",
                  "version": "v1"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/v1/meta/vocabularies": {
      "get": {
        "operationId": "getVocabularies",
        "tags": [
          "Słowniki"
        ],
        "summary": "Dozwolone wartości filtrów",
        "description": "Zbuduj z tego listy wyboru w swoim CRM. Rodzaje instalacji mają wartość techniczną (`value`), którą wysyłasz, i polską etykietę (`label`), którą pokazujesz handlowcowi.",
        "responses": {
          "200": {
            "description": "Słowniki.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vocabularies"
                },
                "example": {
                  "voivodeships": [
                    {
                      "value": "mazowieckie",
                      "nationwide": false
                    },
                    {
                      "value": "ogólnopolski",
                      "nationwide": true
                    }
                  ],
                  "companySizes": [
                    {
                      "value": "mikro"
                    },
                    {
                      "value": "małe"
                    }
                  ],
                  "installationTypes": [
                    {
                      "value": "PV",
                      "label": "Fotowoltaika"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/grants": {
      "get": {
        "operationId": "listGrants",
        "tags": [
          "Dotacje"
        ],
        "summary": "Katalog dotacji",
        "description": "Zwraca dotacje otwarte na dziś, posortowane po terminie naboru. Do synchronizacji katalogu u siebie użyj `updatedSince` i zapisz `nextCursor`. Odpowiedź niesie `ETag` - odesłanie go w `If-None-Match` zwróci 304 bez treści.",
        "parameters": [
          {
            "name": "voivodeship",
            "in": "query",
            "description": "Województwo klienta. Dotacje ogólnopolskie pasują do każdego.",
            "schema": {
              "type": "string"
            },
            "example": "mazowieckie"
          },
          {
            "name": "companySize",
            "in": "query",
            "description": "Wielkość firmy klienta.",
            "schema": {
              "type": "string"
            },
            "example": "małe"
          },
          {
            "name": "installationTypes",
            "in": "query",
            "description": "Rodzaje instalacji po przecinku. Dopasowanie na zasadzie LUB.",
            "schema": {
              "type": "string"
            },
            "example": "PV,Heat Pump"
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Ile wyników na stronę. Domyślnie 50, maksymalnie 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Wartość `nextCursor` z poprzedniej odpowiedzi. Nie twórz go samodzielnie - jest nieprzezroczysty, żeby sortowanie mogło się zmienić bez psucia Twojej integracji.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Tylko dotacje zmienione po tej dacie (ISO 8601).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-07-01T00:00:00Z"
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "description": "ETag z poprzedniej odpowiedzi. Jeśli katalog się nie zmienił, odpowiedź to 304 bez treści.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-If-None-Match",
            "in": "header",
            "description": "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.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Strona wyników.",
            "headers": {
              "ETag": {
                "description": "Odeślij w If-None-Match przy kolejnej synchronizacji.",
                "schema": {
                  "type": "string"
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Ile zapytań zostało w bieżącej minucie.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantPage"
                }
              }
            }
          },
          "304": {
            "description": "Katalog nie zmienił się od czasu podanego ETag. Bez treści. Uwaga: 304 nadal liczy się do limitu. Przez edofinansowania.pl/api użyj nagłówka X-If-None-Match."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/grants/{id}": {
      "get": {
        "operationId": "getGrant",
        "tags": [
          "Dotacje"
        ],
        "summary": "Szczegóły dotacji",
        "description": "Pełna treść: warunki, dokumenty, FAQ, harmonogram. Identyfikatorem może być UUID albo kod dotacji. Zwraca też dotacje po terminie, z polem `isOpen: false` - CRM trzymający starą referencję dostaje odpowiedź, a nie 404.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID albo kod dotacji.",
            "schema": {
              "type": "string"
            },
            "example": "FENG-EKO"
          }
        ],
        "responses": {
          "200": {
            "description": "Dotacja.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GrantDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/eligibility/check": {
      "post": {
        "operationId": "checkEligibility",
        "tags": [
          "Kwalifikowalność"
        ],
        "summary": "Dopasowanie dotacji do profilu klienta",
        "description": "Wysyłasz to, co i tak masz w CRM, dostajesz listę pasujących dotacji z powodem dopasowania. Kryterium, którego nie podałeś, ma `matched: null` - to znaczy \"nie sprawdzaliśmy\", a nie \"przeszło\".",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompanyProfile"
              },
              "example": {
                "voivodeship": "mazowieckie",
                "companySize": "małe",
                "installationTypes": [
                  "PV"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dopasowane dotacje.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EligibilityMatches"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/eligibility/run": {
      "post": {
        "operationId": "runEligibility",
        "tags": [
          "Kwalifikowalność"
        ],
        "summary": "Pełne sprawdzenie kwalifikowalności",
        "description": "Bezstanowe. Wysyłasz wszystkie zebrane dotąd odpowiedzi, dostajesz albo następne pytanie, albo werdykt. Pętlę prowadzi Twój system - my nie trzymamy sesji.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EligibilityRunRequest"
              },
              "example": {
                "grantId": "FENG-EKO",
                "answers": {
                  "q1": "Tak"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Następne pytanie albo werdykt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EligibilityRunResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Klucz w nagłówku `Authorization: Bearer sk_live_...`. Alternatywnie `X-API-Key`. Klucz `pk_live_` działa tylko z przeglądarki, z domen zgłoszonych do widgetu."
      }
    },
    "schemas": {
      "Vocabularies": {
        "type": "object",
        "properties": {
          "voivodeships": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "nationwide": {
                  "type": "boolean"
                }
              }
            }
          },
          "companySizes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "installationTypes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Grant": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "grantCode": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "fundingAmount": {
            "type": [
              "string",
              "null"
            ],
            "description": "Kwota dofinansowania, tak jak podaje ją operator programu."
          },
          "fundingRate": {
            "type": [
              "string",
              "null"
            ],
            "description": "Poziom dofinansowania."
          },
          "applicationDeadline": {
            "type": "string",
            "format": "date"
          },
          "documentationUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "applicationUrl": {
            "type": [
              "string",
              "null"
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "eligibleLocations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "eligibleInstallations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "eligibleCompanySizes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hasEligibilityEngine": {
            "type": "boolean",
            "description": "Czy dla tej dotacji da się uruchomić /v1/eligibility/run."
          }
        }
      },
      "GrantDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Grant"
          },
          {
            "type": "object",
            "properties": {
              "isOpen": {
                "type": "boolean"
              },
              "applicantInfo": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "supportedInvestments": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "paymentConditions": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "documents": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                }
              },
              "faqs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "question": {
                      "type": "string"
                    },
                    "answer": {
                      "type": "string"
                    }
                  }
                }
              },
              "sections": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "content": {
                      "type": "string"
                    }
                  }
                }
              },
              "timeline": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "description": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "GrantPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Grant"
            }
          },
          "pagination": {
            "type": "object",
            "properties": {
              "limit": {
                "type": "integer"
              },
              "hasMore": {
                "type": "boolean"
              },
              "nextCursor": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "CompanyProfile": {
        "type": "object",
        "properties": {
          "voivodeship": {
            "type": [
              "string",
              "null"
            ]
          },
          "companySize": {
            "type": [
              "string",
              "null"
            ]
          },
          "installationTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "MatchedOn": {
        "type": "object",
        "description": "Dlaczego dotacja pasuje. `matched: null` oznacza, że kryterium nie było sprawdzane.",
        "properties": {
          "voivodeship": {
            "type": "object",
            "properties": {
              "matched": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "via": {
                "type": "string",
                "enum": [
                  "exact",
                  "nationwide"
                ]
              },
              "value": {
                "type": "string"
              }
            }
          },
          "companySize": {
            "type": "object",
            "properties": {
              "matched": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "value": {
                "type": "string"
              }
            }
          },
          "installationTypes": {
            "type": "object",
            "properties": {
              "matched": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "values": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "EligibilityMatches": {
        "type": "object",
        "properties": {
          "profile": {
            "$ref": "#/components/schemas/CompanyProfile"
          },
          "total": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Grant"
                },
                {
                  "type": "object",
                  "properties": {
                    "matchedOn": {
                      "$ref": "#/components/schemas/MatchedOn"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "EligibilityRunRequest": {
        "type": "object",
        "required": [
          "grantId"
        ],
        "properties": {
          "grantId": {
            "type": "string",
            "description": "UUID albo kod dotacji."
          },
          "answers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Mapa: identyfikator pytania na odpowiedź."
          }
        }
      },
      "EligibilityRunResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "incomplete",
              "complete"
            ]
          },
          "engine": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "nextQuestion": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "text": {
                "type": "string"
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "verdict": {
            "type": "string",
            "enum": [
              "eligible",
              "not_eligible"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "failedCondition": {
            "type": [
              "string",
              "null"
            ]
          },
          "progress": {
            "type": "object",
            "properties": {
              "answered": {
                "type": "integer"
              },
              "totalQuestions": {
                "type": "integer",
                "description": "Górna granica - graf się rozgałęzia, więc nie każde pytanie leży na każdej ścieżce."
              }
            }
          },
          "path": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Błędy w formacie RFC 9457 Problem Details.",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Kotwica w tej dokumentacji - można ją otworzyć w przeglądarce."
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Nieprawidłowe zapytanie. Przy nieznanej wartości filtra odpowiedź zawiera pole `allowed` z listą dozwolonych.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Brak klucza, klucz nieprawidłowy, odwołany albo wygasły.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "Nie znaleziono zasobu.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Zapytanie poprawne składniowo, ale niespójne - na przykład odpowiedź spoza dozwolonych opcji albo odpowiedzi nieukładające się w jedną ścieżkę.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Przekroczony limit na minutę albo wyczerpana pula miesięczna. Nagłówek `Retry-After` mówi, po ilu sekundach ponowić.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    }
  }
}
