Przykładowy klient, który pozwala na pobranie publicznych ofert pracy z portalu SOLID.Jobs.
API zostało zaprojektowane z myślą o zewnętrznych integratorach, agregatorach ofert pracy oraz osobach chcących budować własne interfejsy i analizy rynku IT.
- Brak klasycznej autoryzacji — API nie wymaga generowania kluczy (API Keys) ani tokenów OAuth. Dostęp jest kontrolowany przez obowiązkowy parametr
campaignsłużący do śledzenia ruchu (patrz niżej). - Rozbudowane filtrowanie — Wyszukiwanie po miastach, technologiach, doświadczeniu i widełkach płacowych.
- Paginacja i sortowanie — Pełna kontrola nad pobieranymi danymi.
- Standaryzacja — Jasny i przewidywalny format danych (JSON).
GET https://solid.jobs/public-api/offers/{dzial}Dostępne działy (dział): IT, Engineering, Marketing, Sales, HR, Logistics, Finances, Other.
API jest wersjonowane. Aktualna wersja to 1.0. Wersję można podać w nagłówku żądania:
X-Api-Version: 1.0Brak nagłówka oznacza użycie najnowszej dostępnej wersji.
Każde żądanie musi zawierać parametr query campaign. Służy on wyłącznie do analityki ruchu — nie jest to token autoryzacyjny.
- Wartość: Twój unikalny identyfikator (np. nazwa firmy, nazwa bota, id integracji).
- Format: Tylko małe litery, cyfry i myślniki. Maksymalnie 64 znaki.
- Przykład:
?campaign=moj-super-agregator
API stosuje rate limiting. Przekroczenie limitu zwraca status 429 Too Many Requests.
| Limit | Wartość |
|---|---|
| Zapytań na minutę (na IP) | 300 (fixed window) |
| Limit kolejki | 10 |
Zalecamy obsługę statusu 429 z mechanizmem retry (np. exponential backoff).
Szczegółowy opis dozwolonych wartości dla poszczególnych parametrów znajdziesz w pliku DICTIONARIES.pl.md.
| Parametr | Typ | Opis | Przykład |
|---|---|---|---|
pageIndex |
int | Indeks strony (od 0, domyślnie 0). | 0 |
pageSize |
int | Rozmiar strony (domyślnie 30, max 500). | 50 |
sortActive |
string | Pole do sortowania (validFrom, validTo, title, company, salaryFrom, salaryTo, experienceLevel). |
validFrom |
sortDirection |
string | Kierunek sortowania (asc lub desc). |
desc |
search.cities |
string | Miasta po przecinku. | Poznań,Warszawa |
search.categories |
string[] | Kategorie główne (np. Developer, Tester). |
Developer |
search.subCategories |
string[] | Podkategorie (np. DotNet, Java). |
DotNet,Java |
search.experiences |
string[] | Poziom doświadczenia. | Regular,Senior |
search.searchTerm |
string[] | Frazy do wyszukiwania pełnotekstowego. | Angular |
search.minimumSalary |
int | Minimalne wynagrodzenie — dolna granica widełek ≥ wartość. | 20000 |
Poprawna odpowiedź 200 OK zwraca obiekt JSON o następującej strukturze:
{
"pageIndex": 0,
"pageSize": 30,
"totalCount": 142,
"totalPages": 5,
"jobs": [
{
"jobOfferKey": "abc-123-def",
"title": "Senior .NET Developer",
"division": "IT",
"category": "Developer",
"subCategory": "DotNet",
"company": "Acme Corp",
"companyLogoUrl": "https://solid.jobs/images/company/acme.png",
"salary": {
"from": 18000,
"to": 25000,
"currency": "PLN",
"period": "Month",
"employmentType": "B2B"
},
"secondarySalary": {
"from": 15000,
"to": 20000,
"currency": "PLN",
"period": "Month",
"employmentType": "UoP"
},
"contractTime": "full_time",
"locations": ["Warszawa", "Kraków"],
"benefits": ["Prywatna opieka zdrowotna", "Karta sportowa"],
"isRemote": true,
"isHybrid": false,
"url": "https://solid.jobs/offer/abc-123-def",
"experienceLevel": "Senior",
"skills": [
{ "name": ".NET", "level": "Advanced" },
{ "name": "Azure", "level": "NiceToHave" }
],
"languages": [
{ "name": "English", "level": "Advanced" }
],
"description": "Szukamy Senior .NET Developera...",
"validFrom": "2026-05-01T00:00:00+00:00",
"validTo": "2026-06-01T00:00:00+00:00",
"updatedAt": "2026-05-15T12:30:00+00:00"
}
]
}| Pole | Opis |
|---|---|
pageIndex |
Indeks bieżącej strony (od 0). |
pageSize |
Liczba ofert na stronie. |
totalCount |
Łączna liczba ofert spełniających kryteria. |
totalPages |
Łączna liczba stron. |
jobs |
Tablica obiektów ofert pracy. |
| Pole | Opis |
|---|---|
jobOfferKey |
Unikalny identyfikator oferty. |
title |
Tytuł stanowiska. |
division |
Dział (np. IT, Engineering). |
category |
Kategoria główna (np. Developer). |
subCategory |
Podkategoria (np. DotNet). |
company |
Nazwa firmy. |
companyLogoUrl |
URL logo firmy (nullable). |
salary |
Główny obiekt wynagrodzenia. |
secondarySalary |
Dodatkowy obiekt wynagrodzenia, np. inny typ umowy (nullable). |
contractTime |
Wymiar czasu pracy (np. full_time, part_time). |
locations |
Tablica nazw miast. |
benefits |
Tablica opisów benefitów. |
isRemote |
Czy stanowisko jest w pełni zdalne. |
isHybrid |
Czy stanowisko jest hybrydowe. |
url |
Bezpośredni link do oferty na SOLID.Jobs. |
experienceLevel |
Wymagany poziom doświadczenia. |
skills |
Tablica wymaganych umiejętności (name + level). |
languages |
Tablica wymaganych języków (name + level). |
description |
Opis oferty pracy. |
validFrom |
Data publikacji oferty. |
validTo |
Data wygaśnięcia oferty. |
updatedAt |
Data ostatniej aktualizacji (nullable). |
| Pole | Opis |
|---|---|
from |
Dolna granica widełek płacowych (nullable). |
to |
Górna granica widełek płacowych (nullable). |
currency |
Kod waluty (np. PLN, EUR, USD). |
period |
Okres rozliczeniowy (np. Month, Hour). |
employmentType |
Typ zatrudnienia (np. B2B, UoP). |
400 Bad Request — nieprawidłowy lub brakujący campaign:
Make sure that campaign parameter exists and contains only lowercase letters, numbers and dashes (max 64 chars long).400 Bad Request — nieprawidłowy dział:
Division not allowed: 'InvalidValue'. Avialable values are: IT, Engineering, Marketing, Sales, HR, Logistics, Finances, Other.429 Too Many Requests — przekroczono limit zapytań. Ponów żądanie po krótkiej przerwie.
Zagregowane statystyki rynku pracy dla pojedynczego zakresu — działu, kategorii głównej, specjalizacji (podkategorii), grupy podkategorii lub miasta. Podobnie jak endpoint ofert nie wymaga autoryzacji (tylko parametru campaign) i zwraca płaski, stabilny kontrakt JSON. Odpowiedzi są cache'owalne do 1 godziny.
GET https://solid.jobs/public-api/market-statistics/{scopeKind}/{scopeKey}?campaign=moj-super-agregatorObowiązują tu te same zasady co wyżej: nagłówek X-Api-Version: 1.0, reguły parametru campaign oraz limity zapytań (300 zapytań/min na IP, kolejka 10).
| Parametr | Typ | Opis |
|---|---|---|
scopeKind |
string | Rodzaj zakresu (case-insensitive). Jeden z: division, mainCategory, subcategory, subcategoryGroup, city. |
scopeKey |
string | Konkretna wartość w obrębie rodzaju — patrz mapowanie poniżej. |
Dozwolone wartości scopeKey (nieznany rodzaj lub klucz zwraca 404):
scopeKind |
Wartość scopeKey |
Przykład | Dozwolone wartości |
|---|---|---|---|
division |
Nazwa działu | IT |
DICTIONARIES §2 |
mainCategory |
Nazwa kategorii głównej | Developer |
DICTIONARIES §3 (kategorie) |
subcategory |
Nazwa podkategorii | React |
DICTIONARIES §3 (podkategorie) |
subcategoryGroup |
Grupa podkategorii | Frontend |
Frontend, Mobile (DICTIONARIES §9) |
city |
Slug miasta (małe litery) | warszawa |
Dowolne miasto obsługiwane przez portal |
| Parametr | Typ | Wymagany | Opis |
|---|---|---|---|
campaign |
string | tak | Identyfikator ruchu — małe litery, cyfry i myślniki, maks. 64 znaki. |
fields |
string | nie | Rozdzielony przecinkami podzbiór sekcji do zwrócenia (case-insensitive): demand, salary, experience, topLocations, topSkills. Pominięcie zwraca wszystkie sekcje dostępne dla zakresu. |
Sekcja topLocations jest niedostępna dla zakresu city (zakres to już pojedyncze miasto). Jawne zażądanie jej dla miasta zwraca 400; pominięcie fields po prostu ją pomija. Pozostałe sekcje są dostępne dla każdego rodzaju zakresu.
Poprawna odpowiedź 200 OK dla subcategory/React (wszystkie sekcje):
{
"scopeKind": "subcategory",
"scopeKey": "React",
"generatedAt": "2026-07-08T09:15:00.1234567+00:00",
"includedSections": ["demand", "salary", "experience", "topLocations", "topSkills"],
"demand": {
"activeOffers": 312,
"distinctEmployers": 148,
"remoteOffers": 121,
"remotePercentage": 39,
"offerTrend": [
{ "period": "2025-Q2", "offerCount": 268 },
{ "period": "2025-Q3", "offerCount": 274 },
{ "period": "2025-Q4", "offerCount": 289 },
{ "period": "2026-Q1", "offerCount": 312 }
]
},
"salary": {
"currency": "PLN",
"overall": { "min": 8000, "p25": 14000, "median": 18000, "p75": 23000, "max": 38000 },
"b2b": { "median": 20000, "average": 20450, "offerCount": 176 },
"permanent": { "median": 15000, "average": 15200, "offerCount": 92 }
},
"experience": [
{ "label": "Senior", "offerCount": 168, "percentage": 54 },
{ "label": "Regular", "offerCount": 108, "percentage": 35 },
{ "label": "Junior", "offerCount": 36, "percentage": 11 }
],
"topLocations": [
{ "label": "Warszawa", "offerCount": 98, "percentage": 31 },
{ "label": "Kraków", "offerCount": 54, "percentage": 17 },
{ "label": "Wrocław", "offerCount": 41, "percentage": 13 }
],
"topSkills": [
{ "label": "React", "offerCount": 312, "percentage": 100 },
{ "label": "TypeScript", "offerCount": 254, "percentage": 81 },
{ "label": "Redux", "offerCount": 120, "percentage": 38 }
]
}| Pole | Typ | Opis |
|---|---|---|
scopeKind |
string | Rodzaj zakresu, którego dotyczą statystyki (odzwierciedla żądanie, znormalizowany). |
scopeKey |
string | Klucz zakresu w obrębie rodzaju. Rodzaje enumowe zachowują kanoniczną wielkość liter; city to slug małymi literami. |
generatedAt |
string | Moment wygenerowania (UTC, ISO-8601 z przesunięciem +00:00). |
includedSections |
string[] | Nazwy sekcji faktycznie obecnych w odpowiedzi — pozwala sprawdzić, co wróciło, gdy część pominięto. |
demand |
object | Metryki popytu i zatrudnienia. Pominięte gdy sekcja nieżądana. |
salary |
object | Metryki płacowe. Pominięte gdy sekcja nieżądana. |
experience |
object[] | Rozkład wg poziomu doświadczenia. Pominięte gdy sekcja nieżądana. |
topLocations |
object[] | Rozkład top miast. Pominięte gdy sekcja nieżądana lub niedostępna (zakres city). |
topSkills |
object[] | Rozkład top umiejętności. Pominięte gdy sekcja nieżądana. |
| Pole | Typ | Opis |
|---|---|---|
activeOffers |
int | Liczba aktywnych ofert w zakresie. |
distinctEmployers |
int | Liczba unikalnych pracodawców publikujących w zakresie. |
remoteOffers |
int | Liczba ofert w pełni zdalnych. |
remotePercentage |
int | Udział ofert w pełni zdalnych (0–100). |
offerTrend |
object[] | Trend liczby ofert kwartalnie (precomputed), do 8 najnowszych kwartałów, od najstarszego. |
| Pole | Typ | Opis |
|---|---|---|
period |
string | Etykieta kwartału w formacie YYYY-Qn (np. 2026-Q1). |
offerCount |
int | Liczba ofert w danym kwartale. |
| Pole | Typ | Opis |
|---|---|---|
currency |
string | Waluta wszystkich kwot poniżej. Zawsze PLN. |
overall |
object | null | Widełki policzone na żywo z aktywnych ofert (percentyle). null gdy żadna oferta w zakresie nie podaje płacy. |
b2b |
object | null | Precomputed statystyka płac B2B. null gdy brak danych. |
permanent |
object | null | Precomputed statystyka płac dla umowy o pracę (UoP). null gdy brak danych. |
| Pole | Typ | Opis |
|---|---|---|
min |
number | Minimum — dolny kraniec widełek. |
p25 |
number | 25. percentyl. |
median |
number | Mediana (50. percentyl). |
p75 |
number | 75. percentyl. |
max |
number | Maksimum — górny kraniec widełek. |
| Pole | Typ | Opis |
|---|---|---|
median |
number | Mediana wynagrodzenia dla rodzaju umowy. |
average |
number | Średnie wynagrodzenie dla rodzaju umowy. |
offerCount |
int | Liczba ofert uwzględnionych w statystyce. |
Wszystkie trzy sekcje mają ten sam kształt pozycji:
| Pole | Typ | Opis |
|---|---|---|
label |
string | Etykieta pozycji — poziom doświadczenia (experience), nazwa miasta (topLocations) lub nazwa umiejętności (topSkills). |
offerCount |
int | Liczba aktywnych ofert w tej pozycji. |
percentage |
int | Udział względem wszystkich aktywnych ofert zakresu (0–100). |
400 Bad Request — nieprawidłowy lub brakujący campaign:
Make sure that campaign parameter exists and contains only lowercase letters, numbers and dashes (max 64 chars long).400 Bad Request — nieznana sekcja w fields:
Unknown section 'foo'. Available sections: Demand, Salary, Experience, TopLocations, TopSkills.400 Bad Request — żądana sekcja niedostępna dla zakresu (np. topLocations dla miasta):
Section(s) not available for scope kind 'City': TopLocations. Available for this scope: Demand, Salary, Experience, TopSkills.404 Not Found — nieznany scopeKind lub scopeKey (puste ciało odpowiedzi).
429 Too Many Requests — przekroczono limit zapytań. Ponów żądanie po krótkiej przerwie.
Raport rynkowy rok po roku dla pojedynczej roli — liczba ofert, podział na typy umów, podział na poziomy doświadczenia oraz poziomy wynagrodzeń, po jednym wpisie na rok kalendarzowy. W odróżnieniu od endpointu statystyk powyżej nie ma tu scopeKind (segment ścieżki to zawsze raport) ani parametru fields — raport zawsze zwracany jest w całości. Odpowiedzi można cachować do 1 godziny.
GET https://solid.jobs/public-api/market-statistics/raport/{scopeKey}?campaign=my-awesome-aggregatorObowiązuje ten sam nagłówek X-Api-Version: 1.0, te same zasady dla campaign oraz te same limity zapytań (300 zapytań/min na IP, kolejka 10), co opisano wyżej.
| Parametr | Typ | Opis |
|---|---|---|
scopeKey |
string | Rola (specjalizacja), której dotyczy raport, np. ManualTester. Wielkość liter nie ma znaczenia; nieznana wartość zwraca 404. Dozwolone wartości: DICTIONARIES §3 (podkategorie). |
| Parametr | Typ | Wymagany | Opis |
|---|---|---|---|
campaign |
string | tak | Identyfikator ruchu — małe litery, cyfry i myślniki, maks. 64 znaki. |
Raport obejmuje do 3 lat kalendarzowych, od najstarszego: rok bieżący i dwa poprzednie. Rok bieżący liczony jest od początku roku do dziś, więc jego wartości są naturalnie niższe niż roku zakończonego.
Poprawna odpowiedź 200 OK dla Golang (skrócona tutaj do dwóch lat, topSkills skrócone do kilku pozycji):
{
"scopeKey": "Golang",
"generatedAt": "2026-08-07T07:14:51.6665451+00:00",
"years": [
{
"year": 2024,
"topSkills": [
{ "name": "Golang", "count": 43 },
{ "name": "Kubernetes", "count": 19 },
{ "name": "Docker", "count": 16 },
{ "name": "AWS", "count": 12 },
{ "name": "MongoDB", "count": 10 },
{ "name": "React", "count": 6 }
],
"quarters": [
{
"quarter": 1,
"offerCount": 9,
"contractType": {
"b2bOnly": { "count": 8, "percentage": 89 },
"permanentOnly": { "count": 1, "percentage": 11 },
"both": { "count": 0, "percentage": 0 },
"total": 9
},
"seniority": {
"junior": {
"count": 0,
"percentage": 0,
"contractType": {
"b2bOnly": { "count": 0, "percentage": 0 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 0
}
},
"regular": {
"count": 5,
"percentage": 56,
"contractType": {
"b2bOnly": { "count": 4, "percentage": 80 },
"permanentOnly": { "count": 1, "percentage": 20 },
"both": { "count": 0, "percentage": 0 },
"total": 5
}
},
"senior": {
"count": 4,
"percentage": 44,
"contractType": {
"b2bOnly": { "count": 4, "percentage": 100 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 4
}
},
"total": 9
},
"salaryB2B": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 20500, "medianUpper": 25000, "averageLower": 20100, "averageUpper": 25400, "salaryRangeCount": 4 },
"senior": { "medianLower": 22800, "medianUpper": 26500, "averageLower": 22600, "averageUpper": 27100, "salaryRangeCount": 4 }
}
},
{
"quarter": 2,
"offerCount": 11,
"contractType": {
"b2bOnly": { "count": 10, "percentage": 91 },
"permanentOnly": { "count": 1, "percentage": 9 },
"both": { "count": 0, "percentage": 0 },
"total": 11
},
"seniority": {
"junior": {
"count": 0,
"percentage": 0,
"contractType": {
"b2bOnly": { "count": 0, "percentage": 0 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 0
}
},
"regular": {
"count": 6,
"percentage": 55,
"contractType": {
"b2bOnly": { "count": 5, "percentage": 83 },
"permanentOnly": { "count": 1, "percentage": 17 },
"both": { "count": 0, "percentage": 0 },
"total": 6
}
},
"senior": {
"count": 5,
"percentage": 45,
"contractType": {
"b2bOnly": { "count": 5, "percentage": 100 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 5
}
},
"total": 11
},
"salaryB2B": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 21200, "medianUpper": 25800, "averageLower": 20800, "averageUpper": 26100, "salaryRangeCount": 5 },
"senior": { "medianLower": 23000, "medianUpper": 27200, "averageLower": 23200, "averageUpper": 27800, "salaryRangeCount": 5 }
}
}
],
"offerCount": 43,
"contractType": {
"b2bOnly": { "count": 38, "percentage": 88 },
"permanentOnly": { "count": 4, "percentage": 9 },
"both": { "count": 1, "percentage": 2 },
"total": 43
},
"seniority": {
"junior": {
"count": 0,
"percentage": 0,
"contractType": {
"b2bOnly": { "count": 0, "percentage": 0 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 0
}
},
"regular": {
"count": 22,
"percentage": 51,
"contractType": {
"b2bOnly": { "count": 20, "percentage": 91 },
"permanentOnly": { "count": 2, "percentage": 9 },
"both": { "count": 0, "percentage": 0 },
"total": 22
}
},
"senior": {
"count": 21,
"percentage": 49,
"contractType": {
"b2bOnly": { "count": 18, "percentage": 86 },
"permanentOnly": { "count": 2, "percentage": 10 },
"both": { "count": 1, "percentage": 5 },
"total": 21
}
},
"total": 43
},
"salaryB2B": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 21800, "medianUpper": 26000, "averageLower": 20930, "averageUpper": 26325, "salaryRangeCount": 20 },
"senior": { "medianLower": 23500, "medianUpper": 26900, "averageLower": 23068, "averageUpper": 27847, "salaryRangeCount": 19 }
},
"salaryUoP": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 14500, "medianUpper": 18500, "averageLower": 14500, "averageUpper": 18500, "salaryRangeCount": 2 },
"senior": { "medianLower": 18000, "medianUpper": 22000, "averageLower": 19000, "averageUpper": 23333, "salaryRangeCount": 3 }
}
},
{
"year": 2025,
"topSkills": [
{ "name": "Golang", "count": 42 },
{ "name": "Kubernetes", "count": 20 },
{ "name": "Docker", "count": 13 },
{ "name": "SQL", "count": 10 },
{ "name": "REST", "count": 10 },
{ "name": "CI/CD", "count": 9 }
],
"quarters": [
{
"quarter": 1,
"offerCount": 12,
"contractType": {
"b2bOnly": { "count": 11, "percentage": 92 },
"permanentOnly": { "count": 1, "percentage": 8 },
"both": { "count": 0, "percentage": 0 },
"total": 12
},
"seniority": {
"junior": {
"count": 0,
"percentage": 0,
"contractType": {
"b2bOnly": { "count": 0, "percentage": 0 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 0
}
},
"regular": {
"count": 7,
"percentage": 58,
"contractType": {
"b2bOnly": { "count": 6, "percentage": 86 },
"permanentOnly": { "count": 1, "percentage": 14 },
"both": { "count": 0, "percentage": 0 },
"total": 7
}
},
"senior": {
"count": 5,
"percentage": 42,
"contractType": {
"b2bOnly": { "count": 5, "percentage": 100 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 5
}
},
"total": 12
},
"salaryB2B": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 20800, "medianUpper": 24900, "averageLower": 20950, "averageUpper": 24600, "salaryRangeCount": 6 },
"senior": { "medianLower": 23200, "medianUpper": 26700, "averageLower": 25100, "averageUpper": 29700, "salaryRangeCount": 4 }
},
"salaryUoP": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"senior": { "medianLower": 13650, "medianUpper": 22150, "averageLower": 13650, "averageUpper": 22150, "salaryRangeCount": 2 }
}
},
{
"quarter": 2,
"offerCount": 10,
"contractType": {
"b2bOnly": { "count": 9, "percentage": 90 },
"permanentOnly": { "count": 1, "percentage": 10 },
"both": { "count": 0, "percentage": 0 },
"total": 10
},
"seniority": {
"junior": {
"count": 0,
"percentage": 0,
"contractType": {
"b2bOnly": { "count": 0, "percentage": 0 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 0
}
},
"regular": {
"count": 5,
"percentage": 50,
"contractType": {
"b2bOnly": { "count": 5, "percentage": 100 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 5
}
},
"senior": {
"count": 5,
"percentage": 50,
"contractType": {
"b2bOnly": { "count": 4, "percentage": 80 },
"permanentOnly": { "count": 1, "percentage": 20 },
"both": { "count": 0, "percentage": 0 },
"total": 5
}
},
"total": 10
},
"salaryB2B": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 21400, "medianUpper": 25500, "averageLower": 21300, "averageUpper": 25100, "salaryRangeCount": 5 },
"senior": { "medianLower": 23800, "medianUpper": 27500, "averageLower": 25800, "averageUpper": 30300, "salaryRangeCount": 4 }
}
}
],
"offerCount": 43,
"contractType": {
"b2bOnly": { "count": 40, "percentage": 93 },
"permanentOnly": { "count": 3, "percentage": 7 },
"both": { "count": 0, "percentage": 0 },
"total": 43
},
"seniority": {
"junior": {
"count": 0,
"percentage": 0,
"contractType": {
"b2bOnly": { "count": 0, "percentage": 0 },
"permanentOnly": { "count": 0, "percentage": 0 },
"both": { "count": 0, "percentage": 0 },
"total": 0
}
},
"regular": {
"count": 23,
"percentage": 53,
"contractType": {
"b2bOnly": { "count": 22, "percentage": 96 },
"permanentOnly": { "count": 1, "percentage": 4 },
"both": { "count": 0, "percentage": 0 },
"total": 23
}
},
"senior": {
"count": 20,
"percentage": 47,
"contractType": {
"b2bOnly": { "count": 18, "percentage": 90 },
"permanentOnly": { "count": 2, "percentage": 10 },
"both": { "count": 0, "percentage": 0 },
"total": 20
}
},
"total": 43
},
"salaryB2B": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 21000, "medianUpper": 25200, "averageLower": 21118, "averageUpper": 24877, "salaryRangeCount": 22 },
"senior": { "medianLower": 23500, "medianUpper": 27100, "averageLower": 25428, "averageUpper": 30028, "salaryRangeCount": 18 }
},
"salaryUoP": {
"junior": { "medianLower": 0, "medianUpper": 0, "averageLower": 0, "averageUpper": 0, "salaryRangeCount": 0 },
"regular": { "medianLower": 19800, "medianUpper": 33400, "averageLower": 19800, "averageUpper": 33400, "salaryRangeCount": 1 },
"senior": { "medianLower": 13650, "medianUpper": 22150, "averageLower": 13650, "averageUpper": 22150, "salaryRangeCount": 2 }
}
}
]
}| Pole | Typ | Opis |
|---|---|---|
scopeKey |
string | Rola, której dotyczy raport (powtórzenie żądania, kanoniczna wielkość liter). |
generatedAt |
string | Moment wygenerowania (UTC, ISO-8601 z przesunięciem +00:00). |
years |
object[] | Po jednym wpisie na rok kalendarzowy, od najstarszego. |
| Pole | Typ | Opis |
|---|---|---|
year |
int | Rok kalendarzowy, którego dotyczy wpis. |
topSkills |
object[] | Najczęściej wymagane skille w tym roku, malejąco po count, maks. 100 pozycji. Pusta tablica (nigdy pomijana), gdy brak danych. |
quarters |
object[] | Podział tego samego roku na kwartały, od najstarszego. Zobacz pola quarters[] niżej — każdy wpis ma ten sam kształt contractType/seniority/salaryB2B/salaryUoP co rok (bez topSkills). |
offerCount |
int | Wszystkie oferty opublikowane w tej roli w danym roku. |
contractType |
object | Podział wg typów umów proponowanych w ofercie. |
seniority |
object | Podział wg wymaganego poziomu doświadczenia, z zagnieżdżonym podziałem na typ umowy w każdym poziomie. |
salaryB2B |
object | Poziomy wynagrodzeń B2B w danym roku, wg poziomu doświadczenia. Pomijane, gdy rok w ogóle nie ma danych B2B. |
salaryUoP |
object | Poziomy wynagrodzeń dla umowy o pracę (UoP), wg poziomu doświadczenia. Pomijane, gdy rok w ogóle nie ma danych UoP. |
| Pole | Typ | Opis |
|---|---|---|
b2bOnly |
object | Oferty proponujące wyłącznie B2B. |
permanentOnly |
object | Oferty proponujące wyłącznie umowę o pracę (UoP). |
both |
object | Oferty proponujące zarówno B2B, jak i UoP. |
total |
int | Suma trzech koszyków — mianownik dla ich percentage. Przeczytaj notę niżej: to nie jest offerCount. |
Każdy koszyk podziału contractType — zarówno na poziomie roku, jak i zagnieżdżony wewnątrz wpisu seniority — ma ten sam kształt:
| Pole | Typ | Opis |
|---|---|---|
count |
int | Liczba ofert w tym koszyku. |
percentage |
int | Udział względem total danego podziału (0–100). |
| Pole | Typ | Opis |
|---|---|---|
junior |
object | Oferty wymagające poziomu junior — liczba, udział procentowy i własny podział na typ umowy. |
regular |
object | Oferty wymagające poziomu regular — liczba, udział procentowy i własny podział na typ umowy. |
senior |
object | Oferty wymagające poziomu senior — liczba, udział procentowy i własny podział na typ umowy. |
total |
int | Suma count trzech poziomów — mianownik dla ich percentage. Przeczytaj notę niżej: to nie jest offerCount. |
| Pole | Typ | Opis |
|---|---|---|
count |
int | Liczba ofert na tym poziomie doświadczenia. |
percentage |
int | Udział względem seniority.total (0–100). |
contractType |
object | Podział na typ umowy wyłącznie w obrębie tego poziomu — ten sam kształt co contractType na poziomie roku, z własnym, niezależnym total. |
Gdy obecne, oba obiekty są kluczowane wg poziomu doświadczenia (junior, regular, senior), a nie stanowią jednej płaskiej statystyki — każdy obecny obiekt zawiera wszystkie trzy klucze, nawet dla poziomu bez pasujących ofert w danym roku (patrz nota niżej).
| Pole | Typ | Opis |
|---|---|---|
junior |
object | Widełki wynagrodzeń dla ofert junior w tym roku. |
regular |
object | Widełki wynagrodzeń dla ofert regular w tym roku. |
senior |
object | Widełki wynagrodzeń dla ofert senior w tym roku. |
| Pole | Typ | Opis |
|---|---|---|
medianLower |
number | Dolna granica przedziału mediany w PLN. |
medianUpper |
number | Górna granica przedziału mediany w PLN. |
averageLower |
number | Dolna granica przedziału średniej w PLN. |
averageUpper |
number | Górna granica przedziału średniej w PLN. |
salaryRangeCount |
int | Liczba widełek stojących za tymi wartościami dla tego poziomu doświadczenia — nie liczba unikalnych ofert, patrz nota niżej. Wszystkie pola równe 0, gdy ten poziom nie ma danych o wynagrodzeniu w danym roku. |
| Pole | Typ | Opis |
|---|---|---|
name |
string | Nazwa skilla. |
count |
int | Liczba ofert wymagających tego skilla w danym roku — nie liczba wystąpień. |
Po jednym wpisie na kwartał kalendarzowy w obrębie roku, od najstarszego. Każde pole poniżej ma dokładnie takie samo znaczenie, kształt i zasady pomijania jak jego odpowiednik na poziomie roku (patrz contractType, seniority, salaryB2B / salaryUoP wyżej) — tyle że dotyczy tego kwartału zamiast całego roku. Na tym poziomie nie ma topSkills.
| Pole | Typ | Opis |
|---|---|---|
quarter |
int | Numer kwartału, 1–4. |
offerCount |
int | Wszystkie oferty opublikowane w tej roli w danym kwartale. |
contractType |
object | Ten sam kształt co contractType na poziomie roku, z własnym, niezależnym total. |
seniority |
object | Ten sam kształt co seniority na poziomie roku, z własnym, niezależnym total. |
salaryB2B |
object | Ten sam kształt co salaryB2B na poziomie roku. Pomijane, gdy kwartał w ogóle nie ma danych B2B. |
salaryUoP |
object | Ten sam kształt co salaryUoP na poziomie roku. Pomijane, gdy kwartał w ogóle nie ma danych UoP. |
Kilka rzeczy, na których naiwna integracja się przewróci:
totalto nieofferCount, na każdym poziomie. Oferta nieproponująca ani B2B, ani umowy o pracę (np. umowa zlecenie) nie trafia do żadnego koszykacontractType, a oferta bez określonego poziomu doświadczenia nie trafia do żadnego koszykaseniority.contractType.totalna poziomie roku,seniority.totalorazcontractType.totalzagnieżdżony wewnątrz każdego poziomuseniorityto niezależne mianowniki — żaden z nich nie równa sięofferCountani pozostałym. Zawsze dzielpercentageprzeztotalobiektu, w którym się znajduje.- Procenty zaokrąglane są niezależnie, więc trzy wartości podziału mogą zsumować się do 99 albo 101 zamiast dokładnie 100.
- Wynagrodzenia to przedział, nie pojedyncza liczba.
medianLower/medianUpperorazaverageLower/averageUpperpowstają przez zsumowanie obu końców każdych pasujących widełek, nie z pojedynczej liczby —salaryB2B.regular.medianLower–medianUpperczytaj jako przedział, w którym mieści się mediana dla ofert regular B2B w danym roku. - Obecny
salaryB2B/salaryUoPzawsze ma wszystkie trzy klucze poziomu doświadczenia. Poziom bez pasujących ofert w danym roku nadal się pojawia, z wszystkimi polami równymi0— pomijany jest wyłącznie cały obiektsalaryB2B/salaryUoP(gdy dla danego typu umowy w danym roku w ogóle brak danych o wynagrodzeniu), nigdy pojedynczy poziom wewnątrz niego. salaryRangeCountliczy widełki, nie oferty, osobno dla każdego poziomu doświadczenia. Oferta deklarująca zarówno główne, jak i dodatkowe widełki tego samego typu umowy liczy się dwa razy, więc ta liczba może przewyższyć liczbę ofert danego poziomu w roku.topSkillsnigdy nie jest pomijane, co najwyżej puste, gdy rok nie ma danych o skillach.- Sumy w
quarterssą niezależne od tych na poziomie roku.contractType.totaliseniority.totalkażdego kwartału to własne, niezależne mianowniki — oddzielne od poziomu roku i od pozostałych kwartałów. Nie sumuj kwartałów, żeby otrzymać liczby roku — do tego służą pola na poziomie roku.salaryB2B/salaryUoPmogą być pomijane per-kwartał tak samo jak per-rok, niezależnie od tego, czy obiekt na poziomie roku jest obecny.
400 Bad Request — nieprawidłowy lub brakujący campaign:
Make sure that campaign parameter exists and contains only lowercase letters, numbers and dashes (max 64 chars long).404 Not Found — nieznany scopeKey (puste ciało odpowiedzi).
429 Too Many Requests — przekroczono limit zapytań. Ponów żądanie po krótkiej przerwie.
W katalogu /examples znajdziesz gotowe skrypty pokazujące, jak zintegrować się z API. Każdy przykład działa po sklonowaniu repozytorium — wystarczy przejść do katalogu i uruchomić jedną komendę.
| Język | Wymagania | Katalog | Komenda |
|---|---|---|---|
| JavaScript / Node.js | Node.js 18+ | examples/javascript |
node fetch_offers.mjs |
| C# / .NET | .NET 9 SDK | examples/csharp |
dotnet run |
| Python | Python 3.8+ | examples/python |
pip install -r requirements.txt && python fetch_offers.py |
| Go | Go 1.21+ | examples/go |
go run . |
| Java | Java 11+ | examples/java |
javac *.java && java FetchOffers |
| PHP | PHP 7.4+ | examples/php |
php fetch_offers.php |
| Ruby | Ruby 2.7+ | examples/ruby |
ruby fetch_offers.rb |
| Rust | Rust 1.70+ | examples/rust |
cargo run |
| Swift | Swift 5.9+ | examples/swift |
swift run |
Każdy katalog językowy zawiera także przykład statystyk rynku. Pobiera wszystkie sekcje dla zakresu subcategory/React, wypisuje każdą zwróconą wartość, a następnie wykonuje drugie zapytanie z fields=demand,salary, aby pokazać filtr sekcji w działaniu.
| Język | Katalog | Komenda |
|---|---|---|
| JavaScript / Node.js | examples/javascript |
node fetch_statistics.mjs |
| C# / .NET | examples/csharp |
dotnet run stats |
| Python | examples/python |
python fetch_statistics.py |
| Go | examples/go |
go run . stats |
| Java | examples/java |
javac *.java && java FetchStatistics |
| PHP | examples/php |
php fetch_statistics.php |
| Ruby | examples/ruby |
ruby fetch_statistics.rb |
| Rust | examples/rust |
cargo run -- stats |
| Swift | examples/swift |
swift run solidjobs-example stats |
Każdy katalog językowy zawiera także przykład raportu rynkowego. Pobiera roczny raport dla roli ManualTester i wypisuje dla każdego roku liczbę ofert, podziały na typ umowy i poziom doświadczenia oraz poziomy wynagrodzeń B2B / UoP.
| Język | Katalog | Komenda |
|---|---|---|
| JavaScript / Node.js | examples/javascript |
node fetch_raport.mjs |
| C# / .NET | examples/csharp |
dotnet run raport |
| Python | examples/python |
python fetch_raport.py |
| Go | examples/go |
go run . raport |
| Java | examples/java |
javac *.java && java FetchRaport |
| PHP | examples/php |
php fetch_raport.php |
| Ruby | examples/ruby |
ruby fetch_raport.rb |
| Rust | examples/rust |
cargo run -- raport |
| Swift | examples/swift |
swift run solidjobs-example raport |
Jeśli masz pomysł na rozszerzenie publicznego API o nowe endpointy lub znalazłeś błąd w dokumentacji, otwórz Issue.
Ten projekt jest udostępniany na licencji MIT.