Skip to content

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SOLID.Jobs - Public API Client

License: MIT PL

A sample client for fetching public job offers from the SOLID.Jobs portal.

The API is designed for external integrators, job aggregators, and anyone who wants to build custom interfaces or IT market analyses.

API Features

  • No classic authorization — The API does not require API Keys or OAuth tokens. Access is controlled via a mandatory campaign parameter used for traffic tracking (see below).
  • Advanced filtering — Search by cities, technologies, experience levels, and salary ranges.
  • Pagination and sorting — Full control over the data you retrieve.
  • Standardization — Clear and predictable data format (JSON).

Main Endpoint

GET https://solid.jobs/public-api/offers/{division}?campaign=my-awesome-aggregator

Available divisions: IT, Engineering, Marketing, Sales, HR, Logistics, Finances, Other.

API Versioning

The API is versioned. The current version is 1.0. You can specify the version in the request header:

X-Api-Version: 1.0

Omitting the header means the latest available version will be used.

Required Parameter: campaign

Every request must include the campaign query parameter. It is used solely for traffic analytics — it is not an authorization token.

  • Value: Your unique identifier (e.g. company name, bot name, integration id).
  • Format: Lowercase letters, digits, and hyphens only. Maximum 64 characters.
  • Example: ?campaign=my-awesome-aggregator

Rate Limits

The API enforces rate limiting. Exceeding the limit returns a 429 Too Many Requests status.

Limit Value
Requests per minute (per IP) 300 (fixed window)
Queue limit 10

We recommend handling the 429 status with a retry mechanism (e.g. exponential backoff).

Optional Search Parameters (Query Params)

For a detailed list of allowed values for each parameter, see DICTIONARIES.md.

Parameter Type Description Example
pageIndex int Page index (starting from 0, default 0). 0
pageSize int Page size (default 30, max 500). 50
sortActive string Sort field (validFrom, validTo, title, company, salaryFrom, salaryTo, experienceLevel). validFrom
sortDirection string Sort direction (asc or desc). desc
search.cities string Comma-separated cities. Poznań,Warszawa
search.categories string[] Main categories (e.g. Developer, Tester). Developer
search.subCategories string[] Subcategories (e.g. DotNet, Java). DotNet,Java
search.experiences string[] Experience level. Regular,Senior
search.searchTerm string[] Full-text search phrases. Angular
search.minimumSalary int Minimum salary — lower bound of salary range ≥ value. 20000

Example Response

A successful 200 OK response returns a JSON object with the following structure:

{
  "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": ["Private healthcare", "Sport card"],
      "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": "We are looking for a Senior .NET Developer...",
      "validFrom": "2026-05-01T00:00:00+00:00",
      "validTo": "2026-06-01T00:00:00+00:00",
      "updatedAt": "2026-05-15T12:30:00+00:00"
    }
  ]
}

Response fields

Field Description
pageIndex Current page index (starting from 0).
pageSize Number of offers per page.
totalCount Total number of offers matching the query.
totalPages Total number of pages.
jobs Array of job offer objects.

Job offer fields

Field Description
jobOfferKey Unique offer identifier.
title Job title.
division Division (e.g. IT, Engineering).
category Main category (e.g. Developer).
subCategory Subcategory (e.g. DotNet).
company Company name.
companyLogoUrl Company logo URL (nullable).
salary Primary salary object.
secondarySalary Secondary salary object, e.g. different contract type (nullable).
contractTime Contract time (e.g. full_time, part_time).
locations Array of city names.
benefits Array of benefit descriptions.
isRemote Whether the position is fully remote.
isHybrid Whether the position is hybrid.
url Direct link to the offer on SOLID.Jobs.
experienceLevel Required experience level.
skills Array of required skills (name + level).
languages Array of required languages (name + level).
description Job offer description.
validFrom Offer publication date.
validTo Offer expiration date.
updatedAt Last update timestamp (nullable).

Salary fields

Field Description
from Lower bound of salary range (nullable).
to Upper bound of salary range (nullable).
currency Currency code (e.g. PLN, EUR, USD).
period Payment period (e.g. Month, Hour).
employmentType Employment type (e.g. B2B, UoP).

Error Responses

400 Bad Request — invalid or missing campaign:

Make sure that campaign parameter exists and contains only lowercase letters, numbers and dashes (max 64 chars long).

400 Bad Request — invalid division:

Division not allowed: 'InvalidValue'. Avialable values are: IT, Engineering, Marketing, Sales, HR, Logistics, Finances, Other.

429 Too Many Requests — rate limit exceeded. Retry after a short delay.


Market Statistics Endpoint

Aggregated labour-market statistics for a single scope — a division, a main category, a specialization (subcategory), a subcategory group, or a city. Like the offers endpoint it needs no authorization (only the campaign parameter) and returns a flat, stable JSON contract. Responses are cacheable for up to 1 hour.

GET https://solid.jobs/public-api/market-statistics/{scopeKind}/{scopeKey}?campaign=my-awesome-aggregator

The same X-Api-Version: 1.0 header, the campaign rules, and the rate limits (300 req/min per IP, queue 10) described above apply here too.

Path Parameters

Parameter Type Description
scopeKind string Kind of scope (case-insensitive). One of division, mainCategory, subcategory, subcategoryGroup, city.
scopeKey string Concrete value within the kind — see the mapping below.

scopeKey allowed values (an unknown kind or key returns 404):

scopeKind scopeKey value Example Allowed values
division Division name IT DICTIONARIES §2
mainCategory Main category name Developer DICTIONARIES §3 (categories)
subcategory Subcategory name React DICTIONARIES §3 (subcategories)
subcategoryGroup Subcategory group Frontend Frontend, Mobile (DICTIONARIES §9)
city City slug (lowercased) warszawa Any city served by the portal

Query Parameters

Parameter Type Required Description
campaign string yes Traffic identifier — lowercase letters, digits and hyphens, max 64 chars.
fields string no Comma-separated subset of sections to return (case-insensitive): demand, salary, experience, topLocations, topSkills. When omitted, all sections available for the scope are returned.

Section Availability

topLocations is not available for the city scope (the scope is already a single city). Requesting it explicitly for a city returns 400; omitting fields simply skips it. All other sections are available for every scope kind.

Example Response

A successful 200 OK response for subcategory/React (all sections):

{
  "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 }
  ]
}

Top-level fields

Field Type Description
scopeKind string Scope kind the statistics describe (echoes the request, normalized).
scopeKey string Scope key within the kind. Enum-based kinds keep canonical casing; city is a lowercased slug.
generatedAt string Generation timestamp (UTC, ISO-8601 with a +00:00 offset).
includedSections string[] Section names actually present in this response — lets you confirm what came back when some were skipped.
demand object Demand & hiring metrics. Omitted when the section was not requested.
salary object Salary metrics. Omitted when the section was not requested.
experience object[] Experience-level distribution. Omitted when not requested.
topLocations object[] Top cities distribution. Omitted when not requested or unavailable (city scope).
topSkills object[] Top skills distribution. Omitted when not requested.

demand fields

Field Type Description
activeOffers int Number of active offers in the scope.
distinctEmployers int Number of unique employers publishing in the scope.
remoteOffers int Number of fully remote offers.
remotePercentage int Share of fully remote offers (0–100).
offerTrend object[] Quarterly offer-count trend (precomputed), up to the 8 most recent quarters, oldest first.

offerTrend[] fields

Field Type Description
period string Quarter label in YYYY-Qn format (e.g. 2026-Q1).
offerCount int Number of offers in that quarter.

salary fields

Field Type Description
currency string Currency of every amount below. Always PLN.
overall object | null Salary band computed live from active offers (percentiles). null when no offer in the scope declares a salary.
b2b object | null Precomputed B2B salary stat. null when there is no precomputed data.
permanent object | null Precomputed permanent-contract (UoP) salary stat. null when there is no precomputed data.

salary.overall fields (salary band)

Field Type Description
min number Minimum — lower edge of the range.
p25 number 25th percentile.
median number Median (50th percentile).
p75 number 75th percentile.
max number Maximum — upper edge of the range.

salary.b2b / salary.permanent fields (salary stat)

Field Type Description
median number Median salary for the contract type.
average number Average salary for the contract type.
offerCount int Number of offers behind the stat.

experience[], topLocations[], topSkills[] fields (bucket)

All three sections share the same bucket shape:

Field Type Description
label string Entry label — experience level (experience), city name (topLocations) or skill name (topSkills).
offerCount int Number of active offers in this entry.
percentage int Share against all active offers of the scope (0–100).

Error Responses

400 Bad Request — invalid or missing campaign:

Make sure that campaign parameter exists and contains only lowercase letters, numbers and dashes (max 64 chars long).

400 Bad Request — unknown section in fields:

Unknown section 'foo'. Available sections: Demand, Salary, Experience, TopLocations, TopSkills.

400 Bad Request — a requested section is not available for the scope (e.g. topLocations for a city):

Section(s) not available for scope kind 'City': TopLocations. Available for this scope: Demand, Salary, Experience, TopSkills.

404 Not Found — unknown scopeKind or scopeKey (empty body).

429 Too Many Requests — rate limit exceeded. Retry after a short delay.


Market Raport Endpoint

A year-by-year market report for a single role — offer volume, contract-type split, seniority split and salary levels, one entry per calendar year. Unlike the statistics endpoint above there is no scopeKind (the path segment is always raport) and no fields parameter — the report is always returned whole. Responses are cacheable for up to 1 hour.

GET https://solid.jobs/public-api/market-statistics/raport/{scopeKey}?campaign=my-awesome-aggregator

The same X-Api-Version: 1.0 header, the campaign rules, and the rate limits (300 req/min per IP, queue 10) described above apply here too.

Path Parameters

Parameter Type Description
scopeKey string Role (specialization) the report describes, e.g. ManualTester. Case-insensitive; an unknown value returns 404. Allowed values: DICTIONARIES §3 (subcategories).

Query Parameters

Parameter Type Required Description
campaign string yes Traffic identifier — lowercase letters, digits and hyphens, max 64 chars.

Coverage

The report spans up to 3 calendar years, oldest first: the current year plus the two before it. The current year is year-to-date, so its counts are naturally lower than a completed year's.

Example Response

A successful 200 OK response for Golang (trimmed here to two years, topSkills trimmed to a handful of entries):

{
  "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 }
      }
    }
  ]
}

Top-level fields

Field Type Description
scopeKey string Role the report describes (echoes the request, canonical casing).
generatedAt string Generation timestamp (UTC, ISO-8601 with a +00:00 offset).
years object[] One entry per calendar year, oldest first.

years[] fields

Field Type Description
year int Calendar year the entry describes.
topSkills object[] Most required skills that year, descending by count, up to 100 entries. Empty array (never omitted) when there's no data.
quarters object[] Per-quarter breakdown of the same year, oldest first. See quarters[] fields below — each entry has the same contractType/seniority/salaryB2B/salaryUoP shape as the year itself (no topSkills).
offerCount int All offers published in the role that year.
contractType object Split by the contract types an offer proposes.
seniority object Split by required experience level, with a contract-type split nested inside each level.
salaryB2B object B2B salary levels for the year, by seniority. Omitted when the year has no B2B salary data at all.
salaryUoP object Permanent-contract (UoP) salary levels, by seniority. Omitted when the year has no UoP salary data at all.

contractType fields

Field Type Description
b2bOnly object Offers proposing only B2B.
permanentOnly object Offers proposing only a permanent contract (UoP).
both object Offers proposing both B2B and UoP.
total int Sum of the three buckets — the denominator of their percentage. Read the note below: this is not offerCount.

contractType bucket fields (b2bOnly / permanentOnly / both)

Every bucket in a contractType breakdown — whether at year level or nested inside a seniority entry — shares the same shape:

Field Type Description
count int Number of offers in this bucket.
percentage int Share against the breakdown's own total (0–100).

seniority fields

Field Type Description
junior object Offers requiring a junior level — count, percentage, and their own contract-type split.
regular object Offers requiring a regular level — count, percentage, and their own contract-type split.
senior object Offers requiring a senior level — count, percentage, and their own contract-type split.
total int Sum of the three levels' count — the denominator of their percentage. Read the note below: this is not offerCount.

seniority level fields (junior / regular / senior)

Field Type Description
count int Number of offers at this seniority level.
percentage int Share against seniority.total (0–100).
contractType object Contract-type split within this seniority level only — same shape as the year-level contractType, with its own independent total.

salaryB2B / salaryUoP fields

When present, both objects are keyed by seniority (junior, regular, senior) rather than being a single flat stat — every present object carries all three keys, even for a seniority with no matching offers that year (see the note below).

Field Type Description
junior object Salary band for junior offers that year.
regular object Salary band for regular offers that year.
senior object Salary band for senior offers that year.

Salary band fields (junior / regular / senior under salaryB2B / salaryUoP)

Field Type Description
medianLower number Lower edge of the median salary range in PLN.
medianUpper number Upper edge of the median salary range in PLN.
averageLower number Lower edge of the average salary range in PLN.
averageUpper number Upper edge of the average salary range in PLN.
salaryRangeCount int Number of salary ranges behind the figures for this seniority — not a distinct offer count, see the note below. All fields are 0 when this seniority has no salary data that year.

topSkills[] fields

Field Type Description
name string Skill name.
count int Number of offers requiring this skill that year — not a count of occurrences.

quarters[] fields

One entry per calendar quarter within the year, oldest first. Every field below has the exact same meaning, shape, and omission rules as its year-level counterpart (see contractType, seniority, salaryB2B / salaryUoP above) — just scoped to that quarter instead of the whole year. There is no topSkills at this level.

Field Type Description
quarter int Quarter number, 14.
offerCount int All offers published in the role that quarter.
contractType object Same shape as the year-level contractType, with its own independent total.
seniority object Same shape as the year-level seniority, with its own independent total.
salaryB2B object Same shape as the year-level salaryB2B. Omitted when the quarter has no B2B salary data at all.
salaryUoP object Same shape as the year-level salaryUoP. Omitted when the quarter has no UoP salary data at all.

Reading the Numbers

Several things will trip up a naive integration:

  • total is not offerCount, at every level. An offer proposing neither B2B nor a permanent contract (a mandate contract, for instance) lands in no contractType bucket, and an offer with no declared experience level lands in no seniority bucket. The year's contractType.total, seniority.total, and the contractType.total nested inside each seniority level are all independent denominators — none of them equal offerCount or each other. Always divide a percentage by the total of the object it lives in.
  • Percentages are rounded independently, so the three values of a breakdown can add up to 99 or 101 rather than exactly 100.
  • Salary figures are a band, not a point estimate. medianLower/medianUpper and averageLower/averageUpper come from pooling both ends of every matching salary range, not a single number — read salaryB2B.regular.medianLowermedianUpper as the range the median falls in for regular B2B offers that year.
  • A present salaryB2B/salaryUoP always has all three seniority keys. A seniority with zero matching offers that year still appears with every field at 0 — only the whole salaryB2B/salaryUoP object is omitted (when there's no salary data at all for that contract type that year), never an individual seniority inside it.
  • salaryRangeCount counts salary ranges, not offers, per seniority level. An offer declaring both a primary and a secondary range of the same contract type contributes twice, so this number can exceed the year's offer count for that seniority.
  • topSkills is never omitted, only empty, when a year has no skill data.
  • quarters totals are independent of the year's. Each quarter's contractType.total and seniority.total are their own denominators, separate from the year-level totals and from every other quarter — don't sum quarters to get the year's numbers, read the year-level fields for that. salaryB2B / salaryUoP can be omitted per-quarter the same way they can be omitted per-year, independently of whether the year-level object is present.

Error Responses

400 Bad Request — invalid or missing campaign:

Make sure that campaign parameter exists and contains only lowercase letters, numbers and dashes (max 64 chars long).

404 Not Found — unknown scopeKey (empty body).

429 Too Many Requests — rate limit exceeded. Retry after a short delay.


Usage Examples

In the /examples directory you will find ready-to-run scripts showing how to integrate with the API. Each example works after cloning the repository — just navigate to the directory and run a single command.

Language Requirements Directory Command
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

Market Statistics Examples

Every language directory also ships a market-statistics example. It fetches all sections for the subcategory/React scope, prints every returned value, then makes a second call with fields=demand,salary to show the section filter in action.

Language Directory Command
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

Market Raport Examples

Every language directory also ships a market-raport example. It fetches the yearly report for the ManualTester role and prints, for each year, the offer count, the contract-type and seniority splits, and the B2B / UoP salary levels.

Language Directory Command
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

Contributions and Bug Reports

If you have an idea for extending the public API with new endpoints or found a bug in the documentation, please open an Issue.

License

This project is licensed under the MIT license.

About

:octocat: A sample client for fetching public job offers from the SOLID.Jobs (https://solid.jobs). The API is designed for external integrators, job aggregators, and anyone who wants to build custom interfaces or IT market analyses.

Resources

Stars

6 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors