REST API i integracje — Deweloper GOV 2.1
Dokument opisuje publiczny kontrakt integracyjny Deweloper GOV 2.1: katalog danych dla zewnętrznego frontendu, operacje administracyjne, importy, publikację oraz zgodność ze starszym REST v1.
Dokumentację podpisanych webhooków HMAC zawiera WEBHOOKI-PRO.md. Kontrakt maszynowy REST v2 znajduje się w openapi-deweloper-gov-v2.yaml.
1. Zakres i wymagania
Bazowy adres REST v2:
https://twoja-domena.pl/wp-json/deweloper-gov/v2
API wymaga WordPressa 6.4 lub nowszego, HTTPS w integracji
produkcyjnej oraz uwierzytelnienia właściwego dla danej grupy
endpointów. Katalog, status, wykonanie publikacji i webhook
wymagają aktywnego PRO z api_webhooks. Import plików
wymaga bulk_import, a preflight wymaga
publishing_gov; kod sprawdza entitlement, nie samą
nazwę planu.
API nie omija walidacji wtyczki. Importy, publikacja i webhook przechodzą przez te same reguły cen, historii, kontraktu prawnego oraz audytu co panel i cron.
1.1. Mechanizmy dostępu
| Grupa | Uwierzytelnienie | Uprawnienie | Przeznaczenie |
|---|---|---|---|
/catalog/* |
WordPress Application Password | dg_read_catalog |
odczyt przez backend zewnętrznego frontendu |
/status, /imports/*,
/publications/* |
Application Password albo sesja WordPress z nonce | manage_options |
automatyzacje administracyjne i panel |
/webhooks/properties |
HMAC-SHA256 w nagłówkach | sekret webhooka | zdarzeniowa aktualizacja mieszkania lub domu |
/wp-json/dg/v1/generate |
X-DG-Webhook-Key |
wewnętrzna kontrola sekretu | zgodność ze starszymi integracjami |
Application Password i sekret webhooka są niezależne.
2. WordPress Application Password
2.1. Katalog dla zewnętrznego frontendu
- Utwórz osobnego użytkownika WordPress.
- Nadaj mu rolę Deweloper GOV — odczyt API
(
dg_catalog_reader). - W profilu utwórz Application Password.
- Jednorazowo pokazane hasło zapisz w menedżerze sekretów backendu.
- Wywołuj API wyłącznie z serwera, nigdy z JavaScriptu wysyłanego do przeglądarki.
export DG_CATALOG_API_AUTH='api-user:APPLICATION_PASSWORD'
curl --fail-with-body \
--user "$DG_CATALOG_API_AUTH" \
'https://example.com/wp-json/deweloper-gov/v2/catalog/properties?status=dostepne&per_page=100'Wtyczka nie zapisuje Application Password. WordPress przechowuje jego hash i pozwala unieważnić pojedynczy token.
2.2. Endpointy administracyjne
Zewnętrzna automatyzacja powinna używać osobnego konta administratora i osobnego Application Password:
export DG_ADMIN_API_AUTH='automation-admin:APPLICATION_PASSWORD'
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
'https://example.com/wp-json/deweloper-gov/v2/status'Konto musi mieć manage_options. Konto
dg_catalog_reader celowo nie może uruchamiać importów
ani publikacji. W panelu WordPress te endpointy mogą być
wywoływane przez zalogowaną sesję z poprawnym nonce REST
(X-WP-Nonce).
3. Reguły wspólne
- kodowanie: UTF-8;
- JSON:
Content-Type: application/json; - daty kalendarzowe:
YYYY-MM-DD; - daty i czasy REST: ISO 8601, zwykle UTC;
- kwoty katalogu: całkowita liczba groszy w
centsi tekst wformatted; - kwoty importu i webhooka: tekst dziesiętny, np.
699022.88, bez binarnegofloat; - ID joba: 32 znaki
[a-f0-9]; - klucz idempotencji importu: 8–128 znaków
[A-Za-z0-9._:-].
Endpoint może zwracać błąd WordPress:
{
"code": "dg_catalog_forbidden",
"message": "Użytkownik nie ma uprawnienia do odczytu katalogu.",
"data": { "status": 403 }
}albo odpowiedź wtyczki:
{
"error": "pro_feature_required",
"feature": "api_webhooks"
}Klient powinien opierać logikę na kodzie HTTP i polu
code albo error, nie na treści
komunikatu.
| HTTP | Znaczenie |
|---|---|
200 |
operacja zakończona lub odczyt zwrócony |
202 |
webhook przyjęty i zapisany |
304 |
katalog nie zmienił się |
400 |
nieprawidłowy format żądania |
401 |
brak albo błędne uwierzytelnienie |
403 |
brak capability, PRO, entitlementu lub HTTPS |
404 |
zasób albo job nie istnieje |
409 |
operacja trwa, wymaga odzyskania albo klucz ma inne wejście |
422 |
błąd walidacji biznesowej |
429 |
limit katalogu; respektuj Retry-After |
503 |
nie można bezpiecznie potwierdzić stanu, blokady albo limitu |
4. Katalog dla zewnętrznego frontendu
Katalog zwraca wyłącznie wpisy ze statusem WordPress
publish. Pozwala zbudować własną tabelę, grid, popup,
galerię, kartę oferty i sekcję podobnych ofert bez bezpośredniego
odczytywania bazy.
4.1. Odkrywanie API
GET /catalog
{
"api_version": "2.1",
"plugin_version": "2.1.0",
"contract": {
"id": "contract-id",
"valid_until": "2026-11-10"
},
"resources": {
"investments": "https://example.com/wp-json/deweloper-gov/v2/catalog/investments",
"properties": "https://example.com/wp-json/deweloper-gov/v2/catalog/properties"
},
"extension_options": {}
}4.2. Inwestycje
GET /catalog/investments
GET /catalog/investments/{id}
| Parametr | Typ | Domyślnie | Opis |
|---|---|---|---|
page |
integer | 1 |
numer strony |
per_page |
integer | 20 |
1–100 wyników |
search |
string | — | wyszukiwanie po nazwie |
_fields |
string | — | projekcja, np. id,title,location |
Kolekcja zwraca nagłówki X-WP-Total,
X-WP-TotalPages, ETag,
Last-Modified i
Cache-Control: private, must-revalidate.
Skrót inwestycji zawiera:
id,title,permalink;location,city,thumbnail;frontend_visible;gov_publication— osobny stan udziału w publikacji GOV;published_gmt,modified_gmt.
Szczegół dodaje content, excerpt,
wspólny context, media, dokumenty, widoczność i
custom_meta.
4.3. Oferty
GET /catalog/properties
GET /catalog/properties/{id}
| Parametr | Typ | Opis |
|---|---|---|
investment |
integer | ID inwestycji |
status |
string | znormalizowany status |
offer_kind |
string | property albo standalone_part |
type |
string | typ nieruchomości |
rooms |
integer | liczba pokoi |
floor |
integer | piętro, także ujemne |
area_min, area_max |
decimal string | zakres powierzchni |
price_min, price_max |
integer | zakres ceny w groszach |
search |
string | nazwa lub oznaczenie |
modified_after |
date-time | zmiany późniejsze niż podany czas |
orderby |
enum | modified, title, price,
area |
order |
enum | ASC albo DESC |
page, per_page |
integer | paginacja, maksimum 100 |
_fields |
string | projekcja, np. id,title,price.cents |
Skrót oferty zawiera dane identyfikacyjne, inwestycję, typ, status, powierzchnię, pokoje, piętro, cenę, cenę za m², miniaturę i czas modyfikacji.
Szczegół zawiera:
- dane wpisu i permalink;
offer— pełny DTO używany również przez szablony wtyczki;investment— skrót opublikowanej inwestycji albonull;location;components— części, pomieszczenia przynależne, prawa i świadczenia;- pełne
price_history; media.thumbnailimedia.galleryz ID, URL, MIME, alt i wymiarami;documents.prospect_url,documents.documents_url,documents.items;contact;visibility.frontend,visibility.gov_publication;- bezpieczne
custom_meta.
Pola wspólnego obiektu offer:
| Pole | Typ | Znaczenie |
|---|---|---|
id |
integer | ID wpisu WordPress |
title, code |
string | nazwa i oznaczenie oferty |
offer_kind |
string | property albo standalone_part |
property_type |
string | typ lokalu, domu albo samodzielnej części |
is_standalone_part |
boolean | czy oferta jest samodzielną częścią |
offer_type_label |
string | czytelna etykieta typu |
url |
URI | publiczny permalink |
status, status_label |
string | kod i czytelna nazwa statusu |
area, area_label |
string | wartość źródłowa i prezentacyjna |
rooms, floor,
building |
string | parametry oferty; mogą być puste dla samodzielnej części |
price, price_cents |
string, integer | cena prezentacyjna i grosze |
price_m2, price_m2_cents |
string, integer | cena za m²; pusta/0, gdy nie dotyczy |
price_date, price_m2_date |
string | sformatowane daty obowiązywania |
variant, variant_label |
string | techniczny wariant i czytelna etykieta |
variant_components |
string[] | czytelne składniki wariantu |
image |
URI/string | URL miniatury |
gallery |
integer[] | ID załączników; pełne media są w
media.gallery |
investment_id |
integer | powiązana inwestycja |
Każda pozycja components zawiera
group, description, prezentacyjną
price, date, relation,
requirement, variant oraz
legal_title.
Obiekt contact zawiera
developer_name, phone,
email i sales_office_address. Nie
zawiera danych nabywców.
{
"price": {
"cents": 69902288,
"formatted": "699 022,88 zł",
"valid_from": "2026-08-25"
}
}4.4. Historia cen
GET /catalog/properties/{id}/price-history
{
"property_id": 207,
"items": [
{
"record_type": "change",
"price_type": "price_total",
"valid_from": "2026-08-25",
"recorded_at": "2026-08-25",
"before": { "cents": 69900000, "formatted": "699 000,00 zł" },
"after": { "cents": 69902288, "formatted": "699 022,88 zł" },
"label": "Zmiana historyczna",
"reason": "",
"correction": false
}
]
}before.cents albo after.cents może
być null, np. przy pierwszej cenie lub wycofaniu
ceny. Wartość X nie jest przedstawiana jako
kwota.
4.5. Podobne oferty
GET /catalog/properties/{id}/related?page=1&per_page=6
Zwraca inne opublikowane oferty z tej samej inwestycji w formacie skróconej kolekcji.
4.6. Cache warunkowy
curl --user "$DG_CATALOG_API_AUTH" \
-H 'If-None-Match: "WCZESNIEJSZY_ETAG"' \
'https://example.com/wp-json/deweloper-gov/v2/catalog/properties'Obsługiwane są mocne i słabe ETag, lista wartości oraz
If-Modified-Since. Niezmieniona odpowiedź ma status
304.
4.7. Limit i CORS
Katalog ma limit 120 żądań na minutę dla pary użytkownik–IP. Po
przekroczeniu zwraca 429 z Retry-After.
Jeśli nie może atomowo potwierdzić licznika, zwraca
503.
Wtyczka celowo nie otwiera CORS. Zalecany przepływ:
przeglądarka → backend strony klienta → Deweloper GOV REST API
5. Status systemu
GET /status
Wymaga manage_options, aktywnego PRO i
api_webhooks.
{
"version": "2.1.0",
"contract_id": "contract-id",
"license_status": "active",
"continuity_mode": false,
"last_publication": {},
"public_dataset_id": "12345"
}6. Import oferty CSV/XLSX
Import jest dwuetapowy: podgląd i pełna walidacja, potem świadome wykonanie. Podgląd niczego nie zapisuje w ofertach.
Wymagania:
CSVlubXLSXbez makr;- maksymalnie 20 MB albo niższy limit WordPressa;
- maksymalnie 10 000 wierszy plus nagłówek;
- formuły są odrzucane;
- wymagane mapowanie
source_investment_idisource_property_id; - obsługiwane są mieszkania i domy; samodzielne części i struktury zagnieżdżone wymagają panelu.
6.1. Pola kanoniczne
source_investment_id, source_property_id, prop_no, prop_type,
property_status, prop_area, prop_rooms, prop_floor,
price_m2, price_m2_from, price_total, price_total_from,
price_combined, price_combined_from, price_combined_variant,
price_combined_status, proj_voivod, proj_powiat, proj_gmina,
proj_city, proj_street, proj_build_no, proj_local_no, proj_zip
prop_type, jeśli podany, musi być
mieszkanie albo dom.
6.2. Podgląd
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
-F 'file=@oferta.xlsx' \
-F 'mode=patch' \
-F 'mapping={"source_investment_id":"ID inwestycji","source_property_id":"ID lokalu","prop_no":"Numer","price_total":"Cena brutto"}' \
'https://example.com/wp-json/deweloper-gov/v2/imports/offer'patch— puste pole nie zmienia wartości, brak rekordu niczego nie archiwizuje;snapshot— pełny snapshot; brakujące rekordy są przygotowane do archiwizacji wymagającej potwierdzenia.
{
"id": "6c01b012304f4dcdb5de0c8a7851104b",
"preview": {
"valid": true,
"mode": "patch",
"operations": [
{
"action": "upsert",
"key": "INV-1:A-01",
"values": {
"source_investment_id": "INV-1",
"source_property_id": "A-01",
"price_total": "699022.88"
}
}
],
"errors": []
}
}Niepoprawny podgląd zwraca 422, ID joba i błędy
row, field, code,
message. Nie wykonuj joba z
valid=false.
6.3. Wykonanie
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
-H 'Idempotency-Key: import-crm-2026-09-02-001' \
--data-urlencode 'job_id=6c01b012304f4dcdb5de0c8a7851104b' \
--data-urlencode 'confirm_archiving=false' \
'https://example.com/wp-json/deweloper-gov/v2/imports/offer'Dla snapshotu z archiwizacją ustaw
confirm_archiving=true dopiero po pokazaniu zakresu
operatorowi.
{
"id": "6c01b012304f4dcdb5de0c8a7851104b",
"result": {
"status": "completed",
"processed": 120,
"created": 2,
"updated": 118,
"archived": 0,
"run_id": 71
}
}Import jest atomowy. completed oznacza trwale
zatwierdzony wynik. import_pending,
import_conflict i
import_recovery_required zwracają 409, a
trwająca operacja także Retry-After: 5.
7. Import gotowego raportu urzędowego
Import raportu nie tworzy i nie aktualizuje ofert ani frontendu. Przekształca CSV/XLSX do kanonicznego CSV, wymaga 58 kolumn i pełnej walidacji kontraktu.
7.1. Podgląd
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
-F 'file=@raport.xlsx' \
'https://example.com/wp-json/deweloper-gov/v2/imports/report'Odpowiedź zawiera id oraz preview:
valid, techniczne csv_path,
row_count, warning i
errors. Klient nie powinien zapisywać ani
interpretować csv_path.
7.2. Potwierdzenie
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
--data-urlencode 'job_id=e1c5bb7edbde49f29e14a69e54b737dd' \
--data-urlencode 'confirm=true' \
--data-urlencode 'date=2026-09-02' \
'https://example.com/wp-json/deweloper-gov/v2/imports/report'Brak potwierdzenia, pliku albo joba zwraca
422 report_confirmation_required lub
404 import_not_found.
8. Status joba
GET /imports/{id}?page=1&per_page=100
per_page ma maksimum 200. Odpowiedź zawiera
id, type, mode,
status, operatora, liczniki, daty,
result, stronicowane items,
page, per_page i
total_items.
Statusy:
preview_ready— poprawny podgląd;invalid— błędy podglądu;running— trwa zapis;completed— trwale zatwierdzony wynik;failed— potwierdzona porażka;recovery_required— wynik wymaga rozstrzygnięcia, bez ślepego retry;
Podglądy są przechowywane 24 godziny, zakończone wyniki 7 dni. Wygasłe joby są usuwane przez retencję. Retencja nie usuwa historii cen.
9. Preflight publikacji
POST /publications/preflight
Parametry: opcjonalna date oraz
verify_portal. Odpowiedź rozdziela
valid, production_allowed, kontrakt,
liczbę rekordów, mode, artifact_status,
delivery_status, delivery_checked_at,
delivery_issues, portal_status,
issues i opcjonalny wynik portal.
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
-H 'Content-Type: application/json' \
-d '{"date":"2026-09-02","verify_portal":false}' \
'https://example.com/wp-json/deweloper-gov/v2/publications/preflight'Przy 422 pole issues zawiera stabilny
kod i komunikat naprawczy.
10. Publikacja
POST /publications
curl --fail-with-body \
--user "$DG_ADMIN_API_AUTH" \
-H 'Content-Type: application/json' \
-d '{"date":"2026-09-02"}' \
'https://example.com/wp-json/deweloper-gov/v2/publications'Endpoint uruchamia wspólny atomowy pipeline. Nie wywołuj go
równolegle z cronem ani importem raportu. Błąd zwraca
422 i nie powinien usuwać poprzedniego poprawnego
snapshotu. Przed publikacją zalecany jest jawny preflight.
11. Webhook właściwości
POST /webhooks/properties
Webhook używa HMAC i trwałej idempotencji, nie Application Password. Pełny opis: WEBHOOKI-PRO.md.
12. REST v1 — zgodność przejściowa
GET|POST /wp-json/dg/v1/generate
curl --fail-with-body \
-H 'X-DG-Webhook-Key: WEBHOOK_SECRET' \
'https://example.com/wp-json/dg/v1/generate?date=2026-09-02'Data musi być realną datą YYYY-MM-DD. Endpoint
wymaga PRO i api_webhooks.
Parametr ?key= jest domyślnie wyłączony.
Przejściowe włączenie dodaje nagłówki Deprecation i
Warning oraz wpis audytowy. Query key zostanie
usunięty w 3.0.0, ponieważ sekrety w URL trafiają do historii i
logów.
13. Bezpieczne pola dodatkowe
custom_meta może zawierać pola Carbon Fields, pola
ACF jawnie oznaczone do REST, meta z
show_in_rest=true i klucze wskazane filtrem
dg_rest_business_meta_keys.
Wtyczka bezwarunkowo odrzuca hasła, tokeny, nonce, sekrety,
klucze licencyjne i techniczne prefiksy, m.in.
_edit_*, _wp_*, _oembed_*,
_elementor_*, _transient_*,
_esticrm_*. Nie serializuje obiektów PHP, zasobów ani
nadmiernych struktur.
14. Rozszerzanie API i szablonów
<?php
declare(strict_types=1);
use Carbon_Fields\Container;
use Carbon_Fields\Field;
add_action('carbon_fields_register_fields', static function (): void {
Container::make('post_meta', 'Marketing')
->where('post_type', '=', 'dg_property')
->add_fields([
Field::make('text', 'marketing_badge', 'Etykieta marketingowa'),
]);
});
add_filter('dg_frontend_property_data', static function (array $data, int $postId): array {
$data['marketing_badge'] = (string) carbon_get_post_meta($postId, 'marketing_badge');
return $data;
}, 10, 2);
add_filter('dg_rest_business_meta_keys', static function (array $keys, string $postType): array {
if ($postType === 'dg_property') {
$keys[] = 'marketing_badge';
}
return $keys;
}, 10, 2);
add_action('dg_after_property_details', static function (int $postId): void {
$badge = (string) carbon_get_post_meta($postId, 'marketing_badge');
if ($badge !== '') {
echo '<p class="custom-badge">' . esc_html($badge) . '</p>';
}
});Globalna opcja:
add_filter('dg_extension_settings_fields', static fn (array $fields): array => $fields + [
'marketing_phone' => [
'type' => 'text',
'label' => 'Telefon kampanii',
'description' => 'Numer używany przez zewnętrzny frontend.',
'default' => '',
'sanitizer' => 'sanitize_text_field',
'expose_in_catalog' => true,
],
]);Bez expose_in_catalog=true opcja nie trafia do
/catalog.
Stabilne filtry:
dg_rest_business_meta_keys;dg_frontend_property_data;dg_frontend_investment_data;dg_rest_catalog_property_query_args;dg_rest_catalog_investment_query_args;dg_extension_settings_fields.
Dostępne są pary dg_before_* i
dg_after_* dla listing,
investment, property oraz sekcji
details, price_history,
documents i contact.
15. Klient Node.js po stronie serwera
const endpoint = new URL('/wp-json/deweloper-gov/v2/catalog/properties', baseUrl);
endpoint.searchParams.set('per_page', '100');
const authorization = Buffer.from(`${username}:${applicationPassword}`).toString('base64');
const response = await fetch(endpoint, {
headers: {
Authorization: `Basic ${authorization}`,
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error(`Deweloper GOV API: HTTP ${response.status}`);
}
const properties = await response.json();16. Lista kontrolna wdrożenia
- osobne konto integracyjne i minimalna rola;
- Application Password w menedżerze sekretów;
- wyłącznie HTTPS i komunikacja serwer–serwer;
- obsługa paginacji,
ETag,429iRetry-After; - import przez podgląd, idempotencję i status joba;
recovery_requiredzatrzymuje automatyczne ponowienie;- osobny sekret i procedura rotacji webhooka;
- redakcja
Authorization,X-DG-Signaturei sekretów w logach; - brak produkcyjnych tokenów w środowisku testowym.
17. Wersjonowanie
Zmiany addytywne pozostają w v2. Usunięcie pola,
zmiana jego znaczenia albo zasad uwierzytelnienia wymaga nowego
namespace, np. v3.
Maszynowy kontrakt: openapi-deweloper-gov-v2.yaml.