API Documentation
REST API for querying job market data. All endpoints return JSON and support case-insensitive filtering. 9 endpoints available.
Base URL
https://richjacobs.me/api/hiring-marketDevelopment: http://localhost:3000/api/hiring-market
API Conventions
Parameter Naming
All query parameters match database column names exactly (e.g., city_code, job_family).
Case Insensitivity
String parameters are case-insensitive. These are equivalent:
- ?job_family=product
- ?job_family=Product
- ?job_family=PRODUCT
Response Format
All endpoints return a consistent JSON envelope:
{
"data": {...}, // Response payload
"meta": {
"last_updated": "2025-12-15T12:00:00Z",
"total_records": 42,
"source": "all" // "all" | "greenhouse" | "adzuna"
},
"error": "..." // Present only on errors
}Common Query Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
| city_code | string | Comma-separated city codes or single city | lon,nyc,den |
| job_family | string | Job family filter (data or product) | data, product |
| job_subfamily | string | Specific role type within family | data_engineer, core_pm |
| date_range | integer | Filter to last N days (omit for all time) | 7, 30, 90 |
Endpoints
/last-updatedReturns the most recent classification timestamp from the data pipeline.
Example Response:
{
"data": {
"last_updated": "2025-12-15T08:30:00Z"
},
"meta": {
"last_updated": "2025-12-15T08:30:00Z",
"total_records": 1,
"source": "all"
}
}/countReturns total job count with optional filtering by city, job family, and date range.
Query Parameters:
city_code- Filter by cities (optional)job_family- Filter by job family (optional)date_range- Last N days (optional)
Example Response:
{
"data": {
"total": 342
},
"meta": {
"last_updated": "2025-12-15T08:30:00Z",
"total_records": 342,
"source": "all"
}
}/kpisReturns key performance indicators: job count, unique employers, remote percentage, and growth metrics.
Query Parameters (Required):
job_family- Required: data or productcity_code- Optional filterdate_range- Optional, enables growth calculation
Example Response:
{
"data": {
"job_count": 450,
"unique_employers": 87,
"remote_percentage": 32.5,
"growth": {
"percentage": 15.2,
"absolute": 58,
"previous_period_count": 392,
"period_days": 30
}
},
"meta": {
"city_code": "lon",
"job_family": "data",
"date_range_days": 30
}
}/role-demandReturns job posting counts grouped by city and role subfamily.
Query Parameters:
city_code- Filter by cities (optional)job_family- Filter by job family (optional)date_range- Last N days (optional)
Example Request:
GET /api/hiring-market/role-demand?job_family=product&city_code=lonExample Response:
{
"data": [
{
"city_code": "lon",
"job_subfamily": "core_pm",
"job_family": "product",
"count": 145
},
{
"city_code": "lon",
"job_subfamily": "technical_pm",
"job_family": "product",
"count": 67
}
],
"meta": {
"last_updated": "2025-12-15T08:30:00Z",
"total_records": 2,
"source": "all"
}
}/top-skillsReturns 3-level skill hierarchy (domain → family → skill) with mention counts.
Query Parameters:
city_code- Filter by cities (optional)job_family- Filter by job family (optional)job_subfamily- Filter by specific role (optional)date_range- Last N days (optional)
Returns hierarchical structure for sunburst visualization
{
"data": {
"name": "Skills",
"children": [
{
"name": "AI & ML",
"children": [
{
"name": "Deep Learning",
"children": [
{ "name": "PyTorch", "value": 45 },
{ "name": "TensorFlow", "value": 38 }
]
}
]
}
]
},
"meta": { ... }
}/working-arrangementReturns remote/hybrid/onsite/flexible split by role subfamily.
Query Parameters:
city_code- Filter by city (optional)job_family- Filter by job family (optional)date_range- Last N days (optional)
Example Response:
{
"data": [
{
"job_subfamily": "data_engineer",
"working_arrangement": "remote",
"count": 89,
"percentage": 32.5
},
{
"job_subfamily": "data_engineer",
"working_arrangement": "hybrid",
"count": 124,
"percentage": 45.3
}
],
"meta": { ... }
}/top-employersReturns top hiring employers ranked by job posting count.
Query Parameters:
city_code- Filter by city (optional)job_family- Filter by job family (optional)job_subfamily- Filter by specific role (optional)date_range- Last N days (optional)limit- Max results (default: 10)
Example Response:
{
"data": [
{ "employer_name": "Google", "count": 24 },
{ "employer_name": "Meta", "count": 18 },
{ "employer_name": "Amazon", "count": 15 }
],
"meta": { ... }
}/seniority-distributionReturns seniority level distribution (junior/mid/senior/staff) by role.
Query Parameters:
city_code- Filter by city (optional)job_family- Filter by job family (optional)date_range- Last N days (optional)
Example Response:
{
"data": [
{
"seniority": "mid",
"count": 156,
"percentage": 42.3
},
{
"seniority": "senior",
"count": 128,
"percentage": 34.7
}
],
"meta": { ... }
}/job-sourcesReturns job counts by data source (Adzuna vs Greenhouse).
Example Response:
{
"data": {
"adzuna": 4676,
"greenhouse": 953,
"total": 5629
},
"meta": { ... }
}Error Handling
All endpoints return HTTP 400 for invalid parameters or HTTP 500 for server errors:
{
"data": null,
"meta": {
"last_updated": "2025-12-15T08:30:00Z",
"total_records": 0,
"source": "all"
},
"error": "Missing required parameter: job_family"
}