
Jobdanmark Search
- 49 installs
- 29.7k repo stars
- Updated August 4, 2026
- madslorentzen/ai-job-search
Searches live Danish job listings from the Jobdanmark.dk public API via CLI, with category, location, and job-type filters.
About
Queries Jobdanmark.dk's public API across 10 categories and all municipalities, supporting search, detail, categories, autocomplete, and location suggestions. A developer uses it to browse and inspect Danish job postings from the terminal.
- Commands: search, detail, categories, autocomplete, locations
- Filters by category, municipality, zip, region, and job type; no auth
Jobdanmark Search by the numbers
- 49 all-time installs (skills.sh)
- +14 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #316 of 550 CLI & Terminal skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/madslorentzen/ai-job-search --skill jobdanmark-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 49 |
|---|---|
| repo stars | ★ 29.7k |
| Last updated | August 4, 2026 |
| Repository | madslorentzen/ai-job-search ↗ |
What it does
Searches live Danish job listings from the Jobdanmark.dk public API via CLI, with category, location, and job-type filters.
Files
Jobdanmark Search Skill
Access live Danish job listings from the Jobdanmark.dk public API. No authentication needed. Covers ~15,000+ active job listings across 10 categories and all Danish municipalities.
When to use this skill
Invoke this skill when the user wants to:
- Search for job listings in Denmark (by keyword, location, category, or job type)
- Browse available jobs in a specific Danish city, municipality, zip code, or region
- Filter jobs by employment type (full-time, part-time, student job, apprentice, etc.)
- Look up the full details of a specific job posting including description and deadline
- List all job categories with current live job counts
- Resolve a job search term into a job title ID or category ID for precise filtering
- Find location suggestions (municipalities, zip codes, regions) for job search filters
Commands
Search job listings
bun run skills/jobdanmark-search/cli/src/cli.ts search [flags]Key flags:
--text <keyword>— free-text search, e.g.elektriker,sygeplejerske,softwareudvikler--category <id>— category ID (see Categories table below)--jobtitle-id <id>— job title ID fromautocompleteresults--municipality <name>— e.g.Odense,København,Aarhus--zip <code>— zip code, e.g.5000,8000,2100--region <name>— region name, e.g.Midtjylland,Sjælland--job-type <types>— comma-separated:fuldtid,deltid,fleksjob,elev,studiejob,praktik--page <n>— page number (30 items per page, server-enforced)--limit <n>— cap total results returned by CLI--format json|table|plain
Per-page is fixed at 30 by the API. Use --page to paginate.Full job detail
bun run skills/jobdanmark-search/cli/src/cli.ts detail <slug> [--format json|plain]slug is the URL path segment returned as slug in search results (e.g. it-chef-soeges-til-rah). Returns full structured job data from the job page's JSON-LD, including title, organization, location, employment type, deadline, and full HTML description.
List categories with live counts
bun run skills/jobdanmark-search/cli/src/cli.ts categories [--format json|table|plain]Returns all 10 job categories with current live job counts. Useful for giving the user an overview of the job market.
Autocomplete job titles and categories
bun run skills/jobdanmark-search/cli/src/cli.ts autocomplete --query "<text>" [--limit <n>]Use this to resolve search terms into precise job title IDs (--jobtitle-id) or category IDs (--category) for search. The value field in results is the ID to pass to search.
Suggest locations
bun run skills/jobdanmark-search/cli/src/cli.ts locations --query "<text>" [--limit <n>]Returns matching municipalities, zip codes, and regions. Use value from results as --municipality or --zip in search.
---
Categories
| ID | Danish | English |
|---|---|---|
| 227972 | Pædagogik, Uddannelse og Forskning | Education, Research |
| 227973 | Håndværk, Industri, Transport og Landbrug | Crafts, Industry, Transport, Agriculture |
| 227974 | Salg, Kommunikation, Marketing, og Design | Sales, Communication, Marketing, Design |
| 227975 | Pleje, Social og Sundhed | Care, Social, Health |
| 227976 | Hotel, Service, Restauration og Sikkerhed | Hotel, Service, Restaurant, Security |
| 227977 | Kontor, Finans og Økonomi | Office, Finance, Economy |
| 227978 | IT, Ingeniør og Energi | IT, Engineering, Energy |
| 227979 | Ledelse, HR og projektstyring | Management, HR, Project Management |
| 543415 | Kirke, Kultur og Underholdning | Church, Culture, Entertainment |
| 227980 | Øvrige job | Other jobs |
---
How to use effectively
Resolve locations first. Use locations to find the correct municipality name or zip code before passing them to search:
bun run skills/jobdanmark-search/cli/src/cli.ts locations --query "Aarhus" --format plainResolve job titles for precision. Use autocomplete to get the exact job title ID when the user wants a specific role:
bun run skills/jobdanmark-search/cli/src/cli.ts autocomplete --query "sygeplejerske" --format plainNatural workflow: `search` → `detail`. 1. Use search to get a list of matching jobs with their slug. 2. Call detail <slug> to get the full job posting with description, deadline, and organization details.
Use `--format table` for comparisons, --format json for data processing, and --format plain for single-record detail views.
Pagination: The API returns 30 items per page (server-enforced). Use --page to navigate through results. Use --limit to cap CLI output regardless of page size.
Combine filters for precise results — e.g. --text + --job-type + --municipality all work together in a single search request.
---
Usage examples
IT jobs in Copenhagen
bun run skills/jobdanmark-search/cli/src/cli.ts search \
--category 227978 \
--municipality "København" \
--job-type fuldtid \
--format tableNursing jobs anywhere in Denmark
bun run skills/jobdanmark-search/cli/src/cli.ts search \
--text "sygeplejerske" \
--category 227975 \
--format tableStudent jobs in Aarhus
bun run skills/jobdanmark-search/cli/src/cli.ts search \
--municipality "Aarhus" \
--job-type studiejob \
--format tableWhat job categories are most active right now?
bun run skills/jobdanmark-search/cli/src/cli.ts categories --format tableFull details for a specific job posting
bun run skills/jobdanmark-search/cli/src/cli.ts detail it-chef-soeges-til-rah --format plainFind jobs in zip code 8000
bun run skills/jobdanmark-search/cli/src/cli.ts search --zip 8000 --format tableWhat electrician jobs are available?
bun run skills/jobdanmark-search/cli/src/cli.ts search \
--text "elektriker" \
--category 227973 \
--job-type "fuldtid,deltid" \
--format tableApprentice positions in all of Denmark
bun run skills/jobdanmark-search/cli/src/cli.ts search \
--job-type elev \
--page 1 \
--format table---
Output formats
| Format | Best for |
|---|---|
json | Default — programmatic use, data processing, passing IDs between commands |
table | Quick human-readable overviews and comparisons |
plain | Single-record detail views (detail) |
All errors are written to stderr as { "error": "...", "code": "..." } and the process exits with code 1.
---
Notes
- All data is from the public Jobdanmark.dk API — no credentials required.
- Pagination is 1-indexed (
--page 1is the first page). 30 items per page, server-enforced. - The
detailcommand fetches the HTML job page and parses the embedded JSON-LD (schema.org/JobPosting). It does not use a separate JSON API. slugin search results is extracted from the API's relativeurlfield (the path after/job/).applicationDeadlinein search results can benull(no deadline set).- Job type values for filters:
fuldtid,deltid,fleksjob,elev,studiejob,praktik.
{
"name": "jobdanmark-cli",
"version": "1.0.0",
"description": "CLI for the Jobdanmark.dk public job search API",
"type": "module",
"main": "src/cli.ts",
"bin": {
"jobdanmark": "src/cli.ts"
},
"scripts": {
"start": "bun run src/cli.ts",
"test": "bun test --timeout 30000",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@bunli/core": "latest",
"@bunli/utils": "latest",
"node-html-parser": "^6.1.0",
"zod": "^3.23.0"
},
"devDependencies": {
"typescript": "^5.4.0",
"@types/bun": "latest"
}
}
jobdanmark-cli
CLI for the Jobdanmark.dk public job search API.
Base URL: https://jobdanmark.dk Authentication: None required. Format: All responses are JSON.
---
Installation
cd skills/jobdanmark-search/cli
bun install---
Commands
| Command | Description |
|---|---|
search | Search job listings with filters |
detail | Full detail for a single job posting (by slug) |
categories | List all job categories with live counts |
autocomplete | Suggest job titles and categories for a query |
locations | Suggest municipalities, zip codes, and regions for a query |
All commands accept --format json|table|plain (default: json). All errors are written to stderr as { "error": "...", "code": "..." } and the process exits with code 1.
---
Job Types
| Value | Danish | English |
|---|---|---|
fuldtid | Fuldtid | Full-time |
deltid | Deltid | Part-time |
fleksjob | Fleksjob | Flex job |
elev | Elev | Apprentice |
studiejob | Studiejob | Student job |
praktik | Praktik | Internship |
---
Categories
| ID | Danish Title |
|---|---|
| 227972 | Pædagogik, Uddannelse og Forskning |
| 227973 | Håndværk, Industri, Transport og Landbrug |
| 227974 | Salg, Kommunikation, Marketing, og Design |
| 227975 | Pleje, Social og Sundhed |
| 227976 | Hotel, Service, Restauration og Sikkerhed |
| 227977 | Kontor, Finans og Økonomi |
| 227978 | IT, Ingeniør og Energi |
| 227979 | Ledelse, HR og projektstyring |
| 543415 | Kirke, Kultur og Underholdning |
| 227980 | Øvrige job |
---
search — Search job listings
Endpoint: POST https://jobdanmark.dk/api/jobsearch/search/{page}
bun run src/cli.ts search [flags]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--text | string | — | Free-text keyword search (job title, keyword) |
--category | number | — | Category ID (see table above) |
--jobtitle-id | number | — | Job title ID from autocomplete results |
--municipality | string | — | Municipality name, e.g. Odense, København |
--zip | string | — | Zip code, e.g. 5000 |
--region | string | — | Region name |
--job-type | string | — | Comma-separated job types: fuldtid,deltid,fleksjob,elev,studiejob,praktik |
--page | number | 1 | Page number (30 items per page, server-enforced) |
--limit | number | — | Cap total results returned by CLI (client-side) |
--format | string | json | Output format: json, table, plain |
Note: Per-page is fixed at 30 by the API. There is no --per-page flag.Note: Multiple--job-typevalues are passed as a comma-separated string and split by the CLI into thejobTypesarray in the request body.
Example
bun run src/cli.ts search --text "elektriker" --job-type fuldtid --municipality "Odense"
bun run src/cli.ts search --category 227978 --job-type "fuldtid,deltid" --page 2 --format table
bun run src/cli.ts search --text "sygeplejerske" --zip 8000 --limit 10Request body sent to API
{
"jobTypes": ["fuldtid"],
"filters": [
{ "type": "freetext", "value": "elektriker", "displayText": "elektriker" },
{ "type": "municipality", "value": "Odense", "displayText": "Odense" }
],
"locationMode": "Text",
"distance": 50
}Important: All filter objects include adisplayTextfield (required by the server). Thevaluefield is an integer forcategoryandjobtitlefilter types; a string for all others.
Response shape
{
"meta": {
"currentPage": 1,
"totalItems": 15610,
"itemsPrPage": 30,
"totalPages": 521
},
"results": [
{
"title": "IT-chef søges til RAH",
"companyName": "Rah Service A/S",
"companyLogo": {
"key": "71f1c950-abcd-1234-efgh-000000000000",
"url": "https://jobdanmark.dk/media/k1epc2kk/rah-service-logo.jpg",
"focalPoint": null
},
"companyLogoSvgMarkup": null,
"overlayColor": "#FFFFFF1F",
"companyAddress": "Ndr Ringvej 4 6950 Ringkøbing",
"jobTypes": ["fuldtid"],
"boostJob": true,
"publishedDate": "12-03-2026",
"applicationDeadline": "10-04-2026",
"url": "https://jobdanmark.dk/job/it-chef-soeges-til-rah",
"slug": "it-chef-soeges-til-rah",
"coverImage": {
"key": "cf06eb46-abcd-1234-efgh-000000000000",
"url": "https://jobdanmark.dk/media/idvbnt4y/rah-service-as-billede.png",
"focalPoint": { "top": 0.488, "left": 0.499 }
},
"silhouetteLogo": false
}
]
}Notes:
-urlis normalized to a full URL (CLI prependshttps://jobdanmark.dkto the relative path from the API).
-slugis extracted from the relativeurlfield (the path segment after/job/).
-applicationDeadlinecan benull.
-companyLogocan benull.
-publishedDateformat:"DD-MM-YYYY".
-coverImagecan benull.
---
detail — Full job posting detail
Method: Fetch HTML from https://jobdanmark.dk/job/{slug}, extract <script type="application/ld+json"> block.
bun run src/cli.ts detail <slug> [--format json|plain]The slug is the URL path segment returned as slug in search results (e.g. it-chef-soeges-til-rah).
Example
bun run src/cli.ts detail it-chef-soeges-til-rah
bun run src/cli.ts detail it-chef-soeges-til-rah --format plainResponse shape
{
"slug": "it-chef-soeges-til-rah",
"url": "https://jobdanmark.dk/job/it-chef-soeges-til-rah",
"title": "IT-chef søges til RAH",
"datePosted": "2026-03-12",
"validThrough": "2026-04-10",
"employmentType": ["FULL_TIME"],
"hiringOrganization": {
"name": "Rah Service A/S",
"logo": "https://jobdanmark.dk/media/k1epc2kk/rah-service-logo.jpg?width=50"
},
"jobLocation": {
"streetAddress": "Ndr Ringvej 4 6950 Ringkøbing",
"addressLocality": "Ringkøbing",
"addressRegion": "Vestjylland",
"postalCode": "6950",
"addressCountry": "DK"
},
"description": "<p>Full HTML description...</p>"
}Note: ThehiringOrganization.logoandvalidThroughfields may benullif not present in the JSON-LD.jobLocationfields may benullif the location data is absent.
---
categories — List categories with live job counts
Endpoint: GET https://jobdanmark.dk/api/categorycount/getcounts
bun run src/cli.ts categories [flags]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--limit | number | — | Cap number of categories returned |
--format | string | json | Output format: json, table, plain |
Example
bun run src/cli.ts categories
bun run src/cli.ts categories --format tableResponse shape
[
{
"id": 227972,
"title": "Pædagogik, Uddannelse og Forskning",
"helpText": "Pædagog, lærer, forsker og meget mere...",
"count": 1856
},
{
"id": 227973,
"title": "Håndværk, Industri, Transport og Landbrug",
"helpText": "Elektriker, VVS, maler og meget mere...",
"count": 2816
},
{
"id": 227974,
"title": "Salg, Kommunikation, Marketing, og Design",
"helpText": "Sælger, marketingkoordinator og meget mere...",
"count": 1423
},
{
"id": 227975,
"title": "Pleje, Social og Sundhed",
"helpText": "Sygeplejerske, social- og sundhedsassistent og meget mere...",
"count": 2104
},
{
"id": 227976,
"title": "Hotel, Service, Restauration og Sikkerhed",
"helpText": "Tjener, kok, receptionist og meget mere...",
"count": 987
},
{
"id": 227977,
"title": "Kontor, Finans og Økonomi",
"helpText": "Bogholder, revisor, kontorassistent og meget mere...",
"count": 1234
},
{
"id": 227978,
"title": "IT, Ingeniør og Energi",
"helpText": "Programmør, datamatiker, ingeniør og meget mere...",
"count": 1567
},
{
"id": 227979,
"title": "Ledelse, HR og projektstyring",
"helpText": "Leder, HR-konsulent, projektleder og meget mere...",
"count": 876
},
{
"id": 543415,
"title": "Kirke, Kultur og Underholdning",
"helpText": "Præst, musiker, skuespiller og meget mere...",
"count": 234
},
{
"id": 227980,
"title": "Øvrige job",
"helpText": "Andre job der ikke passer i de øvrige kategorier...",
"count": 2329
}
]---
autocomplete — Suggest job titles and categories
Endpoint: GET https://jobdanmark.dk/api/search/autocomplete?q={query}
bun run src/cli.ts autocomplete --query <text> [flags]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--query | string | required | Search text to autocomplete |
--limit | number | — | Cap total suggestions returned |
--format | string | json | Output format: json, table, plain |
Example
bun run src/cli.ts autocomplete --query "it"
bun run src/cli.ts autocomplete --query "sygeplejerske" --format tableResponse shape
[
{
"title": "Jobtitler",
"items": [
{
"id": "title__454",
"text": "IT-konsulent",
"value": 454,
"category": "jobtitle",
"slug": "it-konsulent"
},
{
"id": "title__123",
"text": "IT-supporter",
"value": 123,
"category": "jobtitle",
"slug": "it-supporter"
}
]
},
{
"title": "Kategorier",
"items": [
{
"id": "category__227978",
"text": "IT, Ingeniør og Energi",
"value": 227978,
"category": "category",
"slug": "it-ingenioer-og-energi"
}
]
}
]Note: Returns an empty array[]when no suggestions match.valueis a numeric ID usable as--jobtitle-idor--categoryinsearch. Groups with no matching items are omitted from the response.
---
locations — Suggest location filters
Endpoint: GET https://jobdanmark.dk/api/search/locations?q={query}
bun run src/cli.ts locations --query <text> [flags]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--query | string | required | Location text to search (city, zip code, region) |
--limit | number | — | Cap total suggestions returned |
--format | string | json | Output format: json, table, plain |
Example
bun run src/cli.ts locations --query "Odense"
bun run src/cli.ts locations --query "8000" --format table
bun run src/cli.ts locations --query "Sjælland"Response shape
[
{
"title": "Kommune",
"items": [
{
"id": "municipality__0461",
"text": "Odense",
"value": "Odense",
"category": "municipality",
"slug": "odense"
}
]
},
{
"title": "Postnummer",
"items": [
{
"id": "zip__5000",
"text": "5000 Odense C",
"value": "5000",
"category": "zip",
"slug": "5000-odense-c"
},
{
"id": "zip__5200",
"text": "5200 Odense V",
"value": "5200",
"category": "zip",
"slug": "5200-odense-v"
}
]
}
]Note: Returns[]when no locations match. Thevaluefield for municipalities is the display name (string); for zip codes it is the numeric string. Usevaluedirectly as--municipalityor--zipinsearch.
---
Error handling
All errors are written to stderr in JSON format and exit with code 1:
{ "error": "Job not found", "code": "NOT_FOUND" }
{ "error": "API request failed: 400 Bad Request", "code": "API_ERROR" }
{ "error": "--query is required", "code": "MISSING_REQUIRED" }
{ "error": "Failed to parse JSON-LD from job page", "code": "PARSE_ERROR" }---
URL construction
- Job detail pages:
https://jobdanmark.dk/job/{slug} - Company logo images:
https://jobdanmark.dk{companyLogo.url}(prepend base URL to relative path) - Cover images:
https://jobdanmark.dk{coverImage.url}(prepend base URL to relative path)
import { createCLI } from "@bunli/core"
import { search } from "./commands/search.js"
import { detail } from "./commands/detail.js"
import { categories } from "./commands/categories.js"
import { autocomplete } from "./commands/autocomplete.js"
import { locations } from "./commands/locations.js"
const cli = await createCLI({
name: "jobdanmark-cli",
version: "1.0.0",
description: "CLI for the Jobdanmark.dk public job search API",
})
cli.command(search)
cli.command(detail)
cli.command(categories)
cli.command(autocomplete)
cli.command(locations)
await cli.run()
export const BASE_URL = "https://jobdanmark.dk"
export async function apiFetch<T>(path: string, params?: Record<string, string>): Promise<T> {
let url = `${BASE_URL}${path}`
if (params && Object.keys(params).length > 0) {
const qs = new URLSearchParams(params)
url += `?${qs.toString()}`
}
const maxRetries = 6
let delay = 500
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url)
if (response.status === 429 || response.status >= 500) {
if (attempt === maxRetries) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
}
const jitter = Math.floor(Math.random() * 500)
await new Promise((resolve) => setTimeout(resolve, delay + jitter))
delay = Math.min(delay * 2, 5000)
continue
}
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
}
return response.json() as Promise<T>
}
throw new Error("API request failed after max retries")
}
export async function apiPost<T>(path: string, body: unknown): Promise<T> {
const url = `${BASE_URL}${path}`
const maxRetries = 6
let delay = 500
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
if (response.status === 429 || response.status >= 500) {
if (attempt === maxRetries) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
}
const jitter = Math.floor(Math.random() * 500)
await new Promise((resolve) => setTimeout(resolve, delay + jitter))
delay = Math.min(delay * 2, 5000)
continue
}
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
}
return response.json() as Promise<T>
}
throw new Error("API request failed after max retries")
}
export function writeError(error: string, code: string): void {
process.stderr.write(JSON.stringify({ error, code }) + "\n")
}
export function stripHtml(html: string): string {
return html.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim()
}
import { join } from "path";
const CLI_PATH = join(import.meta.dir, "../src/cli.ts");
export interface CLIResult {
stdout: string;
stderr: string;
exitCode: number;
}
export async function runCLI(args: string[]): Promise<CLIResult> {
const proc = Bun.spawn(["bun", "run", CLI_PATH, ...args], {
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode };
}
export function parseJSON<T = unknown>(result: CLIResult): T {
if (result.exitCode !== 0) {
throw new Error(
`CLI exited with code ${result.exitCode}. stderr: ${result.stderr}`
);
}
try {
return JSON.parse(result.stdout) as T;
} catch {
throw new Error(
`Failed to parse JSON. stdout: ${result.stdout}\nstderr: ${result.stderr}`
);
}
}