Podziemie Analityczne

Dokumentacja API

Wszystkie liczby, które serwis pokazuje, można pobrać maszynowo. Endpointy są otwarte: bez klucza i bez rejestracji. Dokumentacja jest po polsku, endpointy mówią JSON-em.

Przykłady odpowiedzi endpointów danych zarejestrowano w produkcji 3 sierpnia 2026 — w trakcie awarii bazy. Dlatego endpointy danych odpowiadają tu kopertą błędu; poza /api/zrodla, którego przykład odpowiada aktualnemu katalogowi kodu i od bazy nie zależy. To nie jest usterka dokumentacji: kontrakt zakłada, że błąd jest jawny, a przykład błędu jest tak samo prawdziwy jak przykład danych. Aktualny stan pokazuje status danych.

Spis endpointów

MetodaEndpointCo zwraca
GET/api/metricskatalog wskaźników (koperta danych)
POST/api/metrics/valueswartości wybranych wskaźników (koperta danych)
GET/api/statusstan maszynerii danych (raport, zawsze 200)
GET/api/zrodlarejestr źródeł wskaźników (z kodu, zawsze 200)
POST/api/charts/sharetworzy link do udostępnionego wykresu
GET/api/charts/share/{id}zapisana konfiguracja udostępnionego wykresu
GET/feed.xmlprzekierowanie 301 na /analizy/feed.xml (Atom)

Zasady

  • Pochodzenie danych. Każdy wskaźnik ma udokumentowane identyfikatory serii źródłowej w /wykresy/zrodla (wersja maszynowa: /api/zrodla); jak pilnujemy liczb — w /jak-to-dziala.
  • Bez obietnic stabilności schematu przed v1. Pola i kody mogą się zmienić. Zmiany odnotowujemy w /zmiany, nigdy po cichu.
  • Bez SLA. Historii dostępności nie prowadzimy, więc niczego o niej nie obiecujemy. /api/status mówi, co jest prawdą teraz.

Koperta danych: live / archival / error

Każda odpowiedź endpointów danych (/api/metrics, /api/metrics/values) jest opakowana w kopertę z jawnym statusem:

type DataStatus = 'live' | 'archival' | 'error';

interface DataEnvelope<T> {
  dataStatus: DataStatus;
  asOf: string | null; // vintage danych: '2026-06', '2026-Q2' albo '2026'; null gdy nie dotyczy
  data: T;
}
dataStatusHTTPZnaczenie
live200dane z bazy podstawowej
archival200oznaczona kopia zapasowa — nigdy nie udaje danych na żywo, a asOf mówi, z kiedy jest
error503nie ma nic do pokazania; data jest puste

Czwartej, nieoznaczonej ścieżki nie ma. Odpowiedź 503 z pustą kopertą jest zamierzona: audyt z 2 sierpnia 2026 wykazał błędy w 47 z 98 ręcznie przepisanych wartości i obie serie zostały usunięte zamiast poprawione „na oko” (szczegóły w /jak-to-dziala) — kopia archiwalna została wtedy opróżniona. Gdy baza nie odpowiada, endpointy danych mówią wprost: błąd.

Formaty okresów: 2026-06 (miesiąc), 2026-Q2 (kwartał), 2026 (rok). Uwaga praktyczna: porównywanie okresów różnych częstotliwości jako napisów daje błędne wyniki — w ASCII '2026-Q1' > '2026-08', choć kalendarzowo jest odwrotnie.

Obsługa koperty po stronie klienta:

const res = await fetch('https://www.podziemieanalityczne.pl/api/metrics');
const envelope = await res.json();

if (envelope.dataStatus === 'error') {
  // res.status === 503; envelope.data jest puste — nie ma danych do pokazania.
} else if (envelope.dataStatus === 'archival') {
  // Kopia zapasowa z vintage envelope.asOf — oznacz ją tak w swoim UI.
} else {
  // 'live' — dane z bazy podstawowej.
}

Błędy walidacji

Odrzucone żądanie (błędne ciało, przekroczony limit) dostaje wspólny kształt błędu — inny niż koperta danych:

{ "code": "INVALID_BODY", "message": "Nieprawidłowe żądanie." }

code jest stały i maszynowy (do rozgałęziania w kliencie), message to polski komunikat gotowy do pokazania użytkownikowi. Kody wymienione są przy każdym endpoincie.

GET /api/metrics

Katalog wskaźników — lista tego, o co można pytać /api/metrics/values. Bez parametrów. Odpowiedź jest liczona przy każdym żądaniu, nie z cache'u builda.

Odpowiedź: DataEnvelope<Metric[]>, gdzie każdy element data ma pola:

PoleTypZnaczenie
idstringidentyfikator do /api/metrics/values (w bazie podstawowej UUID)
slugstringstały, czytelny identyfikator, np. inflacja-cpi
namestringpolska nazwa wskaźnika
unitstringjednostka: %, tys., pkt
sourcestringźródło serii
descriptionstring | nullopis (może być pusty)
colorstringkolor linii (hex, motyw jasny), jakim seria jest rysowana samodzielnie — tak jak na własnej stronie /dane/<slug>. Od 8 sierpnia 2026 kolory na wykresie zależą od całego zestawienia, nie od serii, więc każdy wiersz katalogu niesie tę samą wartość; kolorów porównania nie da się wyprowadzić z tego pola. Pole zostaje dla zgodności.
dashstringwzór linii serii rysowanej samodzielnie — z tego samego powodu co color dziś w każdym wierszu solid (możliwe wartości: solid, dashed, dotted, dash-dot). Nie identyfikuje serii w zestawieniu.
sort_ordernumberporządek w katalogu
displayTransformstring?opis tego, jak źródło publikuje serię — nie tego, co robi serwis. index-to-percent-change znaczy: GUS publikuje tę serię jako indeks (100 = brak zmiany); none — wartość jest już w jednostce docelowej. Serwis niczego nie przelicza: wartości serwujemy i rysujemy tak, jak publikuje je źródło (errata z 7 sierpnia 2026). Brak pola: katalog nie klasyfikuje tej serii.
breaksobject[]?nieciągłości metodologiczne serii wpisane do katalogu. Najważniejsze pola elementu: period — pierwszy okres liczony już na nowej podstawie; comparableAcross — czy odczyty sprzed i po nieciągłości wolno porównywać; obsStatus — kod SDMX (B przełamanie szeregu, D różnica definicji bez przełamania); titlePl/descriptionPl/sourceUrl — opis i adres, pod którym można to sprawdzić. Brak pola znaczy „katalog nie odnotował nieciągłości”, nie „nieciągłości nie ma”.
comparableFromstring?najwcześniejszy okres, od którego seria jest na dzisiejszej podstawie metodycznej. Brak pola znaczy „nie ustalono”, nigdy „porównywalna od zawsze”.
licenceobject?warunki ponownego użycia wartości serii. Pola: id (identyfikator SPDX, np. CC-BY-4.0, albo null, gdy nie ma identyfikatora SPDX (np. regulamin wydawcy)), namePl, url, permits.display / permits.redistribute (czy wolno wyświetlać / serwować dalej), attributionPl — treść atrybucji, którą trzeba nieść przy wyświetlaniu i redystrybucji, verifiedAt i evidenceUrl — kiedy i gdzie warunki zostały odczytane. Kto re-serwuje te wartości, jest związany tymi warunkami.

Pola oznaczone ? występują tylko wtedy, gdy katalog coś o serii orzeka — ich brak jest odpowiedzią („nie ustalono”), a nie błędem.

Statusy: 200 (koperta live lub archival), 503 (koperta error).

curl -s https://www.podziemieanalityczne.pl/api/metrics

Przykład zarejestrowany 3 sierpnia 2026, baza w stanie error — kompletna odpowiedź, nie skrót:

HTTP/1.1 503 Service Unavailable
Content-Type: application/json

{"dataStatus":"error","asOf":null,"data":[]}

POST /api/metrics/values

Wartości wybranych wskaźników. Metoda wyłącznie POST z ciałem JSON (GET na tym adresie dostaje 405):

{
  "metricIds": ["<id z katalogu /api/metrics>"],
  "startPeriod": "2024-01"
}
PoleTypZasady
metricIdsstring[]wymagane; maksymalnie 20 pozycji, każda niepusta i ≤64 znaki. Identyfikatory pochodzą z pola id katalogu. Walidowany jest tylko kształt żądania — id spoza katalogu nie dają 400. Gdy katalog odpowiada na żywo, odpowiedź ma status 200, a nieznane identyfikatory są wymienione w polu unknownIds: brak takiego wskaźnika to fakt o żądaniu, a nie awaria naszego źródła. Gdy katalogu nie da się odczytać na żywo, nie odróżnimy id nieistniejącego od takiego, którego chwilowo nie widzimy — wtedy odpowiedzią jest koperta error. Pusta lista zwraca pustą kopertę live — nic nie zażądano, więc koperta o niczym nie orzeka.
startPeriodstringopcjonalne; dolna granica okna (włącznie) w formacie 2026-06, 2026-Q2 albo 2026 (lata 1900–2999). Serwer przelicza granicę do częstotliwości danych — częstotliwość każdej serii podaje pole frequencyPl w /api/zrodla. Okres niepoprawny kalendarzowo (np. 2026-13) → 400.

Odpowiedź: DataEnvelope<MetricValue[]>, gdzie każdy element data ma pola id, metric_id, period (format jak wyżej) i value (liczba). Jeżeli któryś z żądanych identyfikatorów nie istnieje w katalogu serwowanym na żywo, koperta dokłada pole unknownIds — tablicę tych identyfikatorów. Pola nie ma, gdy nie ma czego wypisać.

Kody błędów walidacji (400): INVALID_JSON, INVALID_BODY, INVALID_PERIOD, TOO_MANY_METRIC_IDS, INVALID_METRIC_IDS. Poza tym: 200 z kopertą live/archival albo 503 z kopertą error.

curl -s -X POST https://www.podziemieanalityczne.pl/api/metrics/values \
  -H 'Content-Type: application/json' \
  -d '{"metricIds":["<id-z-katalogu>"],"startPeriod":"2024-01"}'

Przykłady zarejestrowane 3 sierpnia 2026, baza w stanie error. Poprawne żądanie:

HTTP/1.1 503 Service Unavailable
Content-Type: application/json

{"dataStatus":"error","asOf":null,"data":[]}

Żądanie z 21 identyfikatorami (limit to 20):

HTTP/1.1 400 Bad Request
Content-Type: application/json

{"code":"TOO_MANY_METRIC_IDS","message":"Za dużo wskaźników w jednym żądaniu."}

Żądanie z pustą listą {"metricIds":[]} — jedyna odpowiedź live osiągalna przy leżącej bazie, bo o niczym nie orzeka:

HTTP/1.1 200 OK
Content-Type: application/json

{"dataStatus":"live","asOf":null,"data":[]}

GET /api/status

Stan maszynerii danych — ten sam raport, który strona /status pokazuje ludziom. Zawsze 200: ten endpoint raportuje stan, a nie serwuje dane, więc zepsuty pipeline to udany raport z "overall": "error" w ciele. Odpowiedź ma Cache-Control: no-store — każde żądanie to świeże sprawdzenie.

Sprawdzenie bazy to dokładnie ta ścieżka kodu, którą serwuje /api/metrics — nie osobny pomiar HTTP. Sekcja catalog opisuje katalog zapisany w kodzie (fakty statyczne, nie wynik sprawdzenia na żywo) — każdy komponent raportu deklaruje swój rodzaj w polu kind.

overall mówi o dostępności danych, nie o ich wieku. Wiek jest osobnym komponentem: components.freshness z polem status o wartościach ok, stale albo unknown, licznikiem sprawdzonych serii i tablicą stale, w której każda przeterminowana seria jest wymieniona z nazwą, ostatnim okresem i liczbą okresów opóźnienia. Seria, która przestała się aktualizować, nie zmienia overall na error — baza odpowiada, więc awarii nie ma; monitoring, który chce wiedzieć o wieku danych, czyta components.freshness.status. unknown nie znaczy ok: znaczy, że przy tym żądaniu nie dało się zapytać.

curl -s https://www.podziemieanalityczne.pl/api/status

Przykład zarejestrowany 3 sierpnia 2026 (sformatowany dla czytelności, treść bez zmian), baza w stanie error. Zapis jest starszy niż komponent freshness i dlatego go nie zawiera — przepisanie zarejestrowanej odpowiedzi zrobiłoby z niej zapis, którego nikt nigdy nie otrzymał:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "overall": "error",
  "checkedAt": "2026-08-03T20:27:53.723Z",
  "components": {
    "database": {
      "kind": "live-check",
      "configured": true,
      "status": "failed",
      "detail": "Odczyt katalogu z bazy nie powiódł się (baza nieosiągalna albo pusty katalog)."
    },
    "metricsApi": {
      "kind": "live-check",
      "endpoint": "/api/metrics",
      "dataStatus": "error",
      "asOf": null,
      "httpStatus": 503,
      "servedMetrics": 0,
      "detail": "Stan pochodzi z tej samej ścieżki kodu, którą serwuje /api/metrics — to nie jest osobny pomiar HTTP."
    },
    "catalog": {
      "kind": "static",
      "metricsDefined": 25,
      "wired": { "dbw": 9, "nbp": 3 },
      "unwired": 13,
      "detail": "Fakty o katalogu zapisanym w kodzie (src/lib/gus/mappings.ts). To nie jest wynik sprawdzenia na żywo."
    }
  },
  "note": "Raport z chwili sprawdzenia. Historii dostępności nie prowadzimy, więc żadnej tu nie ma — ani procentów uptime, ani listy incydentów."
}

GET /api/zrodla

Rejestr źródeł wskaźników — maszynowa wersja strony /wykresy/zrodla: dokładne identyfikatory serii, które odpytuje kod pobierający dane (zmienna, przekrój i sposób prezentacji GUS DBW; host i seria NBP), wraz z kanonicznym adresem strony każdego podłączonego wskaźnika. Bez parametrów.

Ten endpoint nie dotyka bazy danych: rejestr jest zapisany w kodzie, więc odpowiedź jest deterministyczna, zawsze 200 — także przy leżącej bazie — i zmienia się wyłącznie z wdrożeniem. Nagłówki: Content-Type: application/json; charset=utf-8 oraz Cache-Control: public, max-age=3600 — ta sama polityka cache co dla /llms.txt i kanałów Atom. Odpowiedź celowo nie zawiera żadnych wartości liczbowych — to spis źródeł; po liczby jest /api/metrics/values.

PoleTypZnaczenie
descriptionPlstringjednozdaniowy opis odpowiedzi
generatedstringskąd bierze się odpowiedź (z kodu, bez migawki i bez liczb)
humanPageUrlstringadres wersji dla ludzi
countnumberliczba wskaźników we wszystkich trzech grupach łącznie
gusDbwobjectpola system, host i metrics[] — serie z kompletem identyfikatorów: zmienna (variable), sposób prezentacji (basis) i okna przekrojów (windows[]: section, from, tonull gdy okno trwa)
nbpobjectpola system, apiHost, staticHost i metrics[] — każda seria z polem series: kursy (kind: "fx" z kodem waluty i agregacją) albo stopa referencyjna (kind: "reference-rate" z id pozycji w XML)
unwiredobjectpola descriptionPl i metrics[] — wskaźniki nazwane w katalogu, ale celowo niepodłączone do czasu weryfikacji identyfikatorów; bez adresu strony, bo strony nie mają
curl -s https://www.podziemieanalityczne.pl/api/zrodla

Przykłady zarejestrowane lokalnie z kodu, który obsługuje ten endpoint — odpowiedź jest deterministyczna, więc rejestracja nie ma daty, a test pilnuje, by fragmenty poniżej były bajt w bajt zgodne z aktualną odpowiedzią. Nagłówki:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=3600

Pierwszy element gusDbw.metrics (sformatowany dla czytelności, treść bez zmian) — CPI z trzema oknami API i starszą publikacją w polu archives. Od danych za 2026 r. GUS zmienił klasyfikację COICOP:

{
  "slug": "inflacja-cpi",
  "namePl": "Inflacja CPI (r/r)",
  "variable": 305,
  "basis": 5,
  "basisLabelPl": "analogiczny okres roku poprzedniego = 100",
  "windows": [
    {
      "section": 736,
      "filter": {
        "2": 33617,
        "562": 6902025,
        "563": 6656078
      },
      "from": "2010-01",
      "to": "2013-12",
      "rangePl": "styczeń 2010 – grudzień 2013"
    },
    {
      "section": 909,
      "filter": {
        "2": 33617,
        "562": 6902025,
        "784": 7215815
      },
      "from": "2014-01",
      "to": "2025-12",
      "rangePl": "styczeń 2014 – grudzień 2025"
    },
    {
      "section": 1698,
      "filter": {
        "2": 33617,
        "562": 6902025,
        "1337": 14916914
      },
      "from": "2026-01",
      "to": null,
      "rangePl": "styczeń 2026 – nadal"
    }
  ],
  "frequencyPl": "miesięczna",
  "unitPl": "indeks",
  "archives": [
    {
      "slug": "inflacja-cpi",
      "from": "1982-01",
      "to": "2009-12",
      "sources": [
        {
          "file": "miesieczne_wskazniki_cen_towarow_i_uslug_konsumpcyjnych_od_1982_roku__2_2.csv",
          "url": "https://stat.gov.pl/download/gfx/portalinformacyjny/pl/defaultstronaopisowa/4741/1/1/miesieczne_wskazniki_cen_towarow_i_uslug_konsumpcyjnych_od_1982_roku__2_2.csv",
          "sha256": "14f3a0a617c98e12eb49aa2fd669cc8e72e300948767b5efd316c6842e21b923"
        }
      ],
      "methodPl": "Polska; analogiczny miesiąc poprzedniego roku = 100."
    }
  ],
  "pageUrl": "https://www.podziemieanalityczne.pl/dane/inflacja-cpi"
}

Element nbp.metrics dla kursu EUR/PLN — identyfikacja serii w polu series, prozą w sourcePl/detailPl:

{
  "slug": "kurs-eur-pln",
  "namePl": "Kurs EUR/PLN",
  "frequencyPl": "miesięczna",
  "unitPl": "zł",
  "host": "api.nbp.pl",
  "series": {
    "kind": "fx",
    "code": "EUR",
    "aggregation": "month-end"
  },
  "sourcePl": "api.nbp.pl — tabela A kursów średnich",
  "detailPl": "kod waluty EUR; wartość miesiąca = ostatnie notowanie w miesiącu (tabela A wychodzi tylko w dni robocze)",
  "archives": [
    {
      "slug": "kurs-eur-pln",
      "from": "1999-01",
      "to": "2001-12",
      "sources": [
        {
          "file": "archiwum_tab_a_1999.xls",
          "url": "https://static.nbp.pl/dane/kursy/Archiwum/archiwum_tab_a_1999.xls",
          "sha256": "d0e98f9ce3ed2476377e924ce58d4d54f5d08b51105654d07b1fb427883b5ccf"
        },
        {
          "file": "archiwum_tab_a_2000.xls",
          "url": "https://static.nbp.pl/dane/kursy/Archiwum/archiwum_tab_a_2000.xls",
          "sha256": "a89a80286d3499bbd1754332cc445adf2699d3d2785923e2be4979ad1ba99056"
        },
        {
          "file": "archiwum_tab_a_2001.xls",
          "url": "https://static.nbp.pl/dane/kursy/Archiwum/archiwum_tab_a_2001.xls",
          "sha256": "1af39509050e27cae17403fd2f144d6e120c578823592e752e78a45602a75d7e"
        }
      ],
      "methodPl": "Ostatnie notowanie tabeli A w miesiącu, w PLN za 1 EUR."
    }
  ],
  "pageUrl": "https://www.podziemieanalityczne.pl/dane/kurs-eur-pln"
}

POST /api/charts/share

Tworzy link do udostępnionego wykresu: zapisuje wybór wskaźników i konfigurację, zwraca {"id":"..."} (8 znaków), a wykres jest potem dostępny pod /wykresy/s/{id}. Ciało żądania:

{
  "selectedIds": ["<id z katalogu /api/metrics>"],
  "config": {
    "mode": "values",
    "timeframe": "1R",
    "dateMarkers": [],
    "levelLines": []
  }
}
PoleZasady
selectedIdswymagane; co najmniej jedna pozycja, każda niepusta i ≤64 znaki; każde id musi istnieć w żywym katalogu — inaczej 400 SHARE_UNKNOWN_METRICS; najwyżej 12 różnych serii w jednym wykresie (duplikaty liczymy raz) — inaczej 400 SHARE_TOO_MANY_SERIES
config.modevalues albo indexed
config.timeframe1M, 3M, 6M, 1R, 2L, 5L, max albo custom
config.customStartPeriodokres w formacie 2026-06 / 2026-Q2 / 2026
config.forceSingleAxisboolean
config.showTrendboolean
config.dateMarkerstablica ≤50 elementów; każdy element musi mieć poprawne pole period. Pola label i color są opcjonalne, ale sprawdzane: etykieta to tekst ≤80 znaków bez znaków sterujących, kolor to zapis szesnastkowy #RGB albo #RRGGBB
config.levelLinestablica ≤50 elementów; każdy element musi mieć liczbowe pole value. label i color jak wyżej

Pole config jest wymagane i musi zawierać tablice dateMarkers oraz levelLines — mogą być puste, ale muszą istnieć, a ich elementy nie mogą być puste (null). Bez nich generator wykresu przerywa rysowanie, więc taki link byłby martwy pod publicznym adresem — odpowiadamy 400 SHARE_CONFIG_UNRENDERABLE. Do 7 sierpnia 2026 r. przyjmowaliśmy tu pusty obiekt {} i ta dokumentacja tak mówiła; powstałych w ten sposób linków nie da się już naprawić. Pozostałe pola z listy powyżej są opcjonalne, a walidowane są tylko wartości pól z tej listy (pola spoza niej nie są sprawdzane). Link powstaje tylko przy żywym katalogu i żywej bazie: inaczej 503 SHARE_SOURCE_UNAVAILABLE. To celowe — link wybity przeciw martwemu katalogowi byłby martwy od urodzenia.

Limit żądań (jedyny w tym API): 30 na godzinę z jednego adresu IP → 429 RATE_LIMITED. Adres odczytujemy z nagłówka ustawianego przez platformę, a nie z tego, co poda klient — dopisanie własnego wpisu do x-forwarded-for nie otwiera nowej puli. Licznik jest wspólny dla wszystkich instancji; gdy jego magazyn nie odpowiada, limit nadal obowiązuje, ale tylko w obrębie pojedynczej instancji. To zabezpieczenie przed nadużyciem, nie gwarantowana kwota. Pozostałe kody: 400 INVALID_SELECTION / SHARE_CONFIG_UNRENDERABLE / INVALID_CONFIG / SHARE_STATE_TOO_LARGE (całe state ponad 16 KB) / SHARE_UNKNOWN_METRICS / SHARE_TOO_MANY_SERIES; 503 SHARE_CREATE_FAILED (zapis się nie powiódł). Ciało niebędące poprawnym JSON-em dostaje 400 SHARE_CREATE_FAILED — zaobserwowane 3 sierpnia 2026, nie INVALID_JSON jak w /api/metrics/values.

Przykład zarejestrowany 3 sierpnia 2026, baza w stanie error — poprawne żądanie, odmowa zgodna z kontraktem:

HTTP/1.1 503 Service Unavailable
Content-Type: application/json

{"code":"SHARE_SOURCE_UNAVAILABLE","message":"Udostępnianie jest chwilowo niedostępne — źródło danych nie odpowiada."}

GET /api/charts/share/{id}

Zapisany stan udostępnionego wykresu: obiekt { selectedIds, config } — konfiguracja, nie dane liczbowe (te trzeba dobrać przez /api/metrics/values). Odczyt zwiększa licznik wyświetleń.

Oddajemy zapisany obiekt dosłownie i nie oceniamy, czy da się z niego narysować wykres. Zasady zapisu zaostrzyły się 7 sierpnia 2026 r., a starszych wpisów nie ruszamy — więc zapis może być nierysowalny, a odpowiedź i tak ma kod 200. To celowe: gdyby odczyt sądził konfigurację, każde przyszłe zaostrzenie zasad unieważniałoby działające linki. Ocena rysowalności żyje na stronie HTML /wykresy/s/{id}, która w takim wypadku mówi wprost, że link jest uszkodzony.

id to 8 znaków z alfabetu A–Z a–z 0–9 _ -; inny format → 400 INVALID_SHARE_ID. Nieistniejący link → 404 SHARE_NOT_FOUND. Uwaga: kod odpowiada 404 także wtedy, gdy zapytanie do bazy się nie powiedzie — przy niedostępnej bazie każdy odczyt kończy się 404 (zaobserwowane 3 sierpnia 2026). 503 SHARE_SOURCE_UNAVAILABLE pojawia się tylko, gdy klient bazy w ogóle nie jest skonfigurowany.

curl -s https://www.podziemieanalityczne.pl/api/charts/share/aaaaaaaa

Przykład zarejestrowany 3 sierpnia 2026 (id o poprawnym formacie, bez istniejącego wpisu; baza niedostępna):

HTTP/1.1 404 Not Found
Content-Type: application/json

{"code":"SHARE_NOT_FOUND","message":"Nie znaleziono takiego wykresu."}

CORS i użycie w przeglądarce

Publiczne endpointy odczytu pozwalają na wywołania z innej domeny: Access-Control-Allow-Origin: *. Endpointy GET obsługują też HEADOPTIONS; /api/metrics/values pozwala na POST i OPTIONS z nagłówkiem Content-Type. Preflight odpowiada kodem 204. Dane są publiczne, więc żądania nie wysyłają poświadczeń.

Przykład do wklejenia na statycznej stronie działającej pod inną domeną:

async function fetchPublicMetrics() {
  const catalogueResponse = await fetch('https://www.podziemieanalityczne.pl/api/metrics');
  const catalogue = await catalogueResponse.json();
  const metricIds = catalogue.data.slice(0, 3).map(({ id }) => id);

  const valuesResponse = await fetch('https://www.podziemieanalityczne.pl/api/metrics/values', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ metricIds }),
  });

  return valuesResponse.json();
}

fetchPublicMetrics().then(console.log).catch(console.error);

Inne maszynowe adresy

  • /analizy/feed.xml i /zmiany/feed.xml — kanały Atom 1.0: opublikowane artykuły z /analizy (wyłącznie opublikowane — szkice są niewidoczne, a kanał bez wpisów jest poprawnym, pustym Atomem) oraz dziennik zmian z /zmiany — te same wpisy co na stronie, słowo w słowo. Oba wysyłają Content-Type: application/atom+xml i Cache-Control: public, max-age=3600. Dawny adres /feed.xml (RSS 2.0) odpowiada przekierowaniem 301 na kanał artykułów.
  • /llms.txt — opis serwisu dla agentów i modeli językowych według konwencji llms.txt: filary, kluczowe adresy i zasady danych (Markdown serwowany jako text/plain).
  • /sitemap.xml i /robots.txt — standardowe. /status celowo nie figuruje w sitemap: strona raportuje stan z chwili sprawdzenia i sama prosi roboty o nieindeksowanie, a wpis w sitemap prosiłby o coś przeciwnego.
  • /status — raport z /api/status w wersji dla ludzi; obie strony powstają z tego samego obiektu, więc nie mogą się rozjechać.