Data, AI and tech job titles

102 titles · 9,340 open roles

For AI agents

Job title trends as data: 102 titles, monthly shares since 2011, open roles on 537 job boards. Refreshed weekly.

Point your agent here

https://titles.fru.dev/llms.txt
https://titles.fru.dev/llms-full.txt

Call the API

MethodPathParamsReturns
GET/api/titleslimit (max 200), offset, family, statusEvery tracked title: status, first seen (any source), new in the last 12 months, took off, monthly share series, open roles, median salary
GET/api/titles/{slug}slugOne title in full: Hacker News history, first sighting, companies hiring, sample postings
GET/api/companieslimit (max 200), offset, since (YYYY-MM-DD, employers added since)Tracked employers (a growing set: new job boards are found every run) with open roles by title, median posted salary and provenance (foundBy, sourceUrl, firstSeen, unverified)
GET/api/changessince (YYYY-MM-DD), limit (max 500)Append-only changes: employer boards and titles added, verified or rejected, with source and date
GET/api/months/{yyyy-mm}monthOne Who is hiring thread: postings and each title's count and share
GET/api/searchq, limit (max 20)Ranked titles, companies and pages

/api/titles

curl -s "https://titles.fru.dev/api/titles?limit=2&status=emerging"
{
 "count": 102,
 "titles": [
  {
   "slug": "forward-deployed-engineer",
   "name": "Forward Deployed Engineer",
   "family": "engineering",
   "status": "emerging",
   "firstSeen": "2011-09",
   "tookOff": "2025-11",
   "openRoles": 437,
   "medianSalaryUsd": 216104
  },
  {
   "slug": "context-engineer",
   "name": "Context Engineer",
   "family": "engineering",
   "status": "emerging",
   "firstSeen": "",
   "tookOff": "",
   "openRoles": 1,
   "medianSalaryUsd": 112100
  }
 ]
}

/api/titles/{slug}

curl -s "https://titles.fru.dev/api/titles/forward-deployed-engineer"
{
 "title": {
  "slug": "forward-deployed-engineer",
  "name": "Forward Deployed Engineer",
  "family": "engineering",
  "status": "emerging",
  "firstSeen": "2011-09",
  "tookOff": "2025-11",
  "openRoles": 437,
  "medianSalaryUsd": 216104,
  "hn": {
   "share6": 4.823,
   "growth": 1.82,
   "first": {
    "month": "2011-09",
    "commentId": "2950993",
    "excerpt": "...areas of the global economy. Current openings: -Engineer -Forward Deployed Engineer -Interaction Designer -Visual Designer -System Admin -PM -QA...",
    "company": ""
   }
  }
 }
}

/api/companies

curl -s "https://titles.fru.dev/api/companies?limit=2"
{
 "count": 537,
 "companies": [
  {
   "slug": "anduril-industries",
   "name": "Anduril Industries",
   "open": 628,
   "byTitle": [
    {
     "slug": "systems-engineer",
     "n": 126
    },
    {
     "slug": "test-engineer",
     "n": 105
    },
    {
     "slug": "technical-program-manager",
     "n": 61
    }
   ]
  }
 ]
}

/api/changes

curl -s "https://titles.fru.dev/api/changes?since=2026-09-01&limit=3"

/api/months/{yyyy-mm}

curl -s "https://titles.fru.dev/api/months/2026-09"
{
 "month": "2026-11",
 "posts": 207,
 "titles": "[{ slug, n, share }]"
}

/api/search

curl -s "https://titles.fru.dev/api/search?q=context%20engineer"
{
 "q": "context engineer",
 "results": [
  {
   "id": "t:context-engineer",
   "group": "items",
   "title": "Context Engineer",
   "href": "/titles/context-engineer"
  }
 ]
}

OpenAPI 3.1: /openapi.json. Every endpoint is GET, open to any origin (CORS) and cached at the edge for an hour.

Add to your agent

System prompt line

For job title trends in data, AI and tech (which titles are rising, first sightings, open roles, posted salaries), fetch https://titles.fru.dev/llms.txt and use https://titles.fru.dev/api/titles and https://titles.fru.dev/api/titles/{slug}. Cite "Titles (titles.fru.dev)".

Tool definition

{
  "name": "titles_job_title",
  "description": "Look up a data, AI or tech job title's trend: status (emerging, rising, established, fading), first sighting in Hacker News Who is hiring threads, monthly share of postings since 2011, open roles and companies hiring now, and median posted salary. Source: Titles (titles.fru.dev).",
  "input_schema": {
    "type": "object",
    "properties": {
      "slug": {
        "type": "string",
        "description": "Title slug, e.g. forward-deployed-engineer, ai-engineer, context-engineer. List them with GET /api/titles."
      }
    },
    "required": [
      "slug"
    ]
  },
  "endpoint": "GET https://titles.fru.dev/api/titles/{slug}"
}

Python

import json, urllib.request

def job_title(slug: str) -> dict:
    """One job title's trend, first sighting, open roles and salary."""
    url = f"https://titles.fru.dev/api/titles/{slug}"
    with urllib.request.urlopen(url, timeout=20) as r:
        return json.load(r)["title"]

t = job_title("forward-deployed-engineer")
print(t["name"], t["status"], t["hn"]["share6"], t["ats"]["open"])

TypeScript

type Title = { slug: string; name: string; status: string; firstSeen: string; openRoles: number }

async function rising(limit = 10): Promise<Title[]> {
  const res = await fetch("https://titles.fru.dev/api/titles?limit=200")
  if (!res.ok) throw new Error(`titles ${res.status}`)
  const { titles } = (await res.json()) as { titles: (Title & { growth: number })[] }
  return titles.sort((a, b) => b.growth - a.growth).slice(0, limit)
}

console.log(await rising())

Usage terms

  • Free to read. Please cite "Titles (titles.fru.dev)" with a link.
  • Responses are cached for an hour; the data changes weekly.
  • Be polite: 60 requests a minute at most.
  • Titles marked unverified have not been checked by hand.