Updated June 15, 2026
/api/v1/vacancies/facets
curl -X GET \
"https://app.recruitsome.com/api/v1/vacancies/facets" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
const response = await fetch('https://app.recruitsome.com/api/v1/vacancies/facets', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json',
},
});
const data = await response.json();
console.log(data);
use Illuminate\Support\Facades\Http;
$response = Http::withToken('YOUR_API_KEY')
->acceptJson()
->get('https://app.recruitsome.com/api/v1/vacancies/facets');
$data = $response->json();
The facets endpoint provides aggregated data about available filter options for vacancies. This is ideal for building dynamic search interfaces with faceted navigation, showing users what filters are available and how many results each filter would return.
This endpoint is more efficient than using ?include=facets on the main listing endpoint when you only need facet data without the actual vacancy listings — for example to render the filter sidebar before the first search.
Authentication
Requires an API key with the vacancies:read scope (included in the Career website preset). A key without this scope receives a 403 response.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
language | string | No | Filter by language code (exactly 2 characters, e.g. en, nl) |
location_id[] | array | No | Filter by location ID. Accepts array notation, but only the first element is applied — extra elements are ignored. |
company_location_id[] | array | No | Filter by company location ID (work site, for agency tenants). Accepts array notation, but only the first element is applied on this endpoint. |
department_id[] | array | No | Filter by department ID. Accepts array notation, but only the first element is applied. |
search | string | No | Search in title and summary |
experience_levels[] | array | No | Filter by experience level IDs (multiple values supported) |
job_types[] | array | No | Filter by job type IDs (multiple values supported) |
education_levels[] and tags[] are not accepted on this endpoint — passing them is silently ignored and does not change the counts. To get facet counts narrowed by education level or tag, use GET /vacancies?include=facets on the listing endpoint instead, which does apply those filters.
For genuinely multi-valued filtering on company_location_id[], use the listing endpoint (GET /vacancies), which applies all elements of the array.
Example
Get all available facets without any filters:
curl -X GET https://app.recruitsome.com/api/v1/vacancies/facets \
-H "Authorization: Bearer YOUR_API_KEY"const response = await fetch('https://app.recruitsome.com/api/v1/vacancies/facets', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const facets = await response.json();import requests
response = requests.get(
'https://app.recruitsome.com/api/v1/vacancies/facets',
headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
facets = response.json()Response 200
{
"data": {
"locations": [
{
"id": 1,
"name": "Amsterdam Office",
"city": "Amsterdam",
"country_code": "NL",
"country_name": "Netherlands",
"count": 45,
"selected": false
},
{
"id": 2,
"name": "Rotterdam Office",
"city": "Rotterdam",
"country_code": "NL",
"country_name": "Netherlands",
"count": 23,
"selected": false
}
],
"company_locations": [
{
"id": 10,
"name": "Shell Pernis Refinery",
"city": "Rotterdam",
"country_code": "NL",
"country_name": "Netherlands",
"count": 12,
"selected": false
},
{
"id": 11,
"name": "ASML Veldhoven HQ",
"city": "Veldhoven",
"country_code": "NL",
"country_name": "Netherlands",
"count": 8,
"selected": false
}
],
"departments": [
{
"id": 5,
"name": "Engineering",
"count": 32,
"selected": false
},
{
"id": 6,
"name": "Sales",
"count": 21,
"selected": false
}
],
"experience_levels": [
{
"id": 2,
"name": "Junior",
"count": 20,
"selected": false
},
{
"id": 3,
"name": "Medior",
"count": 35,
"selected": false
},
{
"id": 4,
"name": "Senior",
"count": 28,
"selected": false
}
],
"job_types": [
{
"id": 1,
"name": "Full-time",
"count": 68,
"selected": false
},
{
"id": 2,
"name": "Part-time",
"count": 12,
"selected": false
}
],
"education_levels": [
{
"id": 8,
"name": "Bachelor",
"count": 45,
"selected": false
},
{
"id": 9,
"name": "Master",
"count": 28,
"selected": false
}
],
"tags": [
{
"id": 1,
"name": "Engineering",
"count": 42,
"selected": false,
"sub_tags": [
{
"id": 5,
"name": "Maintenance Engineer",
"count": 25,
"selected": false
},
{
"id": 6,
"name": "Service Technician",
"count": 12,
"selected": false
}
]
},
{
"id": 2,
"name": "Sales",
"count": 28,
"selected": false,
"sub_tags": [
{
"id": 10,
"name": "Account Executive",
"count": 15,
"selected": false
}
]
}
],
"languages": [
{
"code": "en",
"name": "English",
"count": 52,
"selected": false
},
{
"code": "nl",
"name": "Nederlands",
"count": 34,
"selected": false
}
]
},
"meta": {
"generated_at": "2026-05-20T10:30:00+00:00",
"filters_applied": []
}
}Filtered example
Get facets with active filters to see how selections affect other options:
curl -X GET "https://app.recruitsome.com/api/v1/vacancies/facets?location_id[]=1&language=en" \
-H "Authorization: Bearer YOUR_API_KEY"const params = new URLSearchParams({
'location_id[]': 1,
'language': 'en'
});
const response = await fetch(
`https://app.recruitsome.com/api/v1/vacancies/facets?${params}`,
{
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const facets = await response.json();import requests
params = {
'location_id[]': 1,
'language': 'en'
}
response = requests.get(
'https://app.recruitsome.com/api/v1/vacancies/facets',
headers={'Authorization': 'Bearer YOUR_API_KEY'},
params=params
)
facets = response.json()When filters are applied:
- The
selectedfield showstruefor active filters - Counts in other facet categories are updated to reflect only the filtered results
- The
filters_appliedarray inmetashows which filters are active
Understanding facet counts
Without filters
When no filters are applied, each facet shows the total count of active vacancies with that attribute:
- Location "Amsterdam Office" count 45 = total active vacancies in Amsterdam
- Department "Engineering" count 32 = total active vacancies in Engineering
With active filters
When filters are applied, counts show how many results match both the facet AND the current filters:
- If filtering by Amsterdam: Department "Engineering" count 12 = vacancies in Amsterdam AND Engineering
- The Amsterdam location itself will show
"selected": true
Response structure
Facet object structure
Each facet type contains an array of options with:
| Field | Type | Description |
|---|---|---|
id | integer | Unique identifier for the option |
name | string | Human-readable name (translated) |
count | integer | Number of vacancies with this attribute |
selected | boolean | Whether this filter is currently active |
Location facets
Location facets include additional geographic information:
| Field | Type | Description |
|---|---|---|
id | integer | Unique location identifier |
name | string | Location name |
city | string|null | City name (from locality field) |
country_code | string|null | ISO 3166-1 alpha-2 country code |
country_name | string|null | Full country name in current locale |
count | integer | Number of vacancies at this location |
selected | boolean | Whether this filter is currently active |
For remote positions or locations without a specific city, the city field may be null.
Company location facets
Company location facets represent the actual work sites (client company locations) for agency tenants. This array will be empty for non-agency tenants or when no vacancies have a company location assigned. The fields are the same as the location facets above.
The company_locations facet does not include the company name to prevent accidental disclosure of client relationships. Use the include_company_name parameter on the vacancy list/detail endpoints if you need company names.
Tags facets (hierarchical)
Tags facets use a hierarchical structure with main categories containing nested sub-tags:
| Field | Type | Description |
|---|---|---|
id | integer | Unique tag identifier |
name | string | Tag name (translated) |
count | integer | Total vacancies in this category (including sub-tags) |
selected | boolean | Whether this filter is currently active |
sub_tags | array | Array of sub-category tags |
Each sub_tags item contains: id, name, count, and selected.
Zero-count entries are pruned from the tags facet: sub-tags with no matching vacancies are omitted from sub_tags, and a main tag whose entire branch (itself plus all sub-tags) has zero matches is omitted from tags altogether. Don't expect the full tag catalog here — only branches with at least one active vacancy.
Language facets
Language facets have a slightly different structure:
| Field | Type | Description |
|---|---|---|
code | string | ISO 639-1 language code |
name | string | Language name |
count | integer | Number of vacancies in this language |
selected | boolean | Whether this filter is currently active |
Use cases
Building dynamic filters
Use facet data to create filter interfaces that show available options and counts:
// Build location filter dropdown with enhanced geographic info
const locationFilter = facets.data.locations.map(location => ({
value: location.id,
label: `${location.name} (${location.count})`,
description: location.city && location.country_name
? `${location.city}, ${location.country_name}`
: location.country_name || '',
disabled: location.count === 0,
checked: location.selected
}));Smart filter updates
When a user selects a filter, fetch updated facets to show how it affects other options:
async function onFilterChange(filters) {
// Fetch new facets with current filters
const facets = await fetchFacets(filters);
// Update UI to show new counts
updateFilterCounts(facets);
// Disable options with zero results
disableEmptyFilters(facets);
}Pre-loading filter options
Load facets on page load to immediately show available filters:
// On page load
const [vacancies, facets] = await Promise.all([
fetchVacancies({ page: 1 }),
fetchFacets({})
]);
// Initialize filters with facet data
initializeFilters(facets);Performance considerations
- Caching: Facet data changes less frequently than vacancy listings, making it ideal for caching
- Parallel loading: Fetch facets in parallel with initial vacancy data for faster page loads
- Debouncing: When implementing live filter updates, debounce facet requests to avoid excessive API calls
- Conditional updates: Only refetch facets when filters actually change
Error responses
401 — missing or invalid API key
{
"message": "Unauthenticated."
}Send a valid key as Authorization: Bearer YOUR_API_KEY.
403 — key lacks the required scope
{
"message": "This API key does not have the required permission: vacancies:read",
"error": "INSUFFICIENT_SCOPE",
"required_scope": "vacancies:read"
}Add the vacancies:read scope to your key, or regenerate it with the Career website preset.
422 — invalid filter parameter
Returned when a filter value fails validation, e.g. a location_id[] that doesn't exist:
{
"message": "The selected location_id.0 is invalid.",
"errors": {
"location_id.0": [
"The selected location_id.0 is invalid."
]
}
}