Back to Dashboard

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-market

Development: 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

ParameterTypeDescriptionExample
city_codestringComma-separated city codes or single citylon,nyc,den
job_familystringJob family filter (data or product)data, product
job_subfamilystringSpecific role type within familydata_engineer, core_pm
date_rangeintegerFilter to last N days (omit for all time)7, 30, 90

Endpoints

GET/last-updated

Returns 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"
  }
}
GET/count

Returns 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"
  }
}
GET/kpis

Returns key performance indicators: job count, unique employers, remote percentage, and growth metrics.

Query Parameters (Required):

  • job_family - Required: data or product
  • city_code - Optional filter
  • date_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
  }
}
GET/role-demand

Returns 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=lon

Example 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"
  }
}
GET/top-skills

Returns 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": { ... }
}
GET/working-arrangement

Returns 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": { ... }
}
GET/top-employers

Returns 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": { ... }
}
GET/seniority-distribution

Returns 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": { ... }
}
GET/job-sources

Returns 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"
}