adzuna-sdk
v0.1.2
Published
TypeScript SDK for the Adzuna API
Readme
adzuna-sdk
A fully type-safe TypeScript SDK for the Adzuna API, built on Ky and designed for Bun (also runs on Node 18+).
Features
- Type-safe everywhere. Request options, responses, and error bodies are all typed against the Adzuna OpenAPI spec.
- Ergonomic surface.
adzuna.jobs.search({ country: 'gb', page: 1, fullTime: true })instead of"1"flag strings and snake_case field names. - Auth handled once. Pass
appIdandappKeyto the constructor; every request gets them injected automatically. - Retries built in. Retries on 408, 429, 500, 502, 503, 504 via Ky, with configurable limits.
- Const-object enums.
Country.GBfor autocomplete,'gb'for terseness, both type-check. - ESM-only, tree-shakeable. No runtime overhead beyond Ky.
Install
bun add adzuna-sdk
# or
npm install adzuna-sdk
# or
pnpm add adzuna-sdkGetting API credentials
Sign up at developer.adzuna.com to get your app_id and app_key. The free tier allows 25 calls per minute and 250 per day.
Quick start
import { AdzunaClient, Country, SortBy } from 'adzuna-sdk';
const adzuna = new AdzunaClient({
appId: process.env.ADZUNA_APP_ID!,
appKey: process.env.ADZUNA_APP_KEY!,
});
const { count, results } = await adzuna.jobs.search({
country: Country.GB,
page: 1,
what: 'typescript',
where: 'London',
fullTime: true,
sortBy: SortBy.Date,
salaryMin: 60_000,
});
console.log(`${count} matching jobs`);
for (const job of results) {
console.log(`${job.title} at ${job.company?.display_name} (${job.salary_min}-${job.salary_max})`);
}API reference
The client exposes adzuna.jobs.* for all job-related endpoints and a top-level adzuna.version().
new AdzunaClient(options)
| Option | Type | Default | Description |
| ---------- | ------------- | ------------------------------------- | ------------------------------------------- |
| appId | string | required | Your Adzuna application ID. |
| appKey | string | required | Your Adzuna application key. |
| baseUrl | string | https://api.adzuna.com/v1/api | Override the API base URL. |
| retry | number | 2 | Retry attempts for transient failures. |
| timeout | number | 30_000 | Request timeout in milliseconds. |
| fetch | typeof fetch| globalThis.fetch | Custom fetch implementation (tests, proxies, etc.). |
adzuna.jobs.search(options)
Search the Adzuna job database.
const res = await adzuna.jobs.search({
country: 'gb',
page: 1,
resultsPerPage: 20,
what: 'react',
whatAnd: 'typescript',
whatOr: 'remote hybrid',
whatExclude: 'senior manager',
titleOnly: 'engineer',
where: 'Manchester',
distance: 25,
maxDaysOld: 7,
category: 'it-jobs',
sortBy: 'date',
sortDir: 'down',
salaryMin: 50_000,
salaryMax: 120_000,
salaryIncludeUnknown: true,
fullTime: true,
permanent: true,
company: 'acme-corp',
// location0..location7 also accepted
});Returns JobSearchResults.
adzuna.jobs.categories(options)
List available job categories for a country.
const { results } = await adzuna.jobs.categories({ country: 'gb' });
// results: Array<{ tag: string; label: string }>adzuna.jobs.histogram(options)
Distribution of live jobs by salary.
const { histogram } = await adzuna.jobs.histogram({
country: 'gb',
what: 'engineer',
category: 'it-jobs',
});
// histogram: Record<salary, jobCount>
// e.g. { '30000': 120, '40000': 80, '50000': 45 }adzuna.jobs.topCompanies(options)
Leaderboard of employers matching a query.
const { leaderboard } = await adzuna.jobs.topCompanies({
country: 'us',
what: 'python',
location0: 'US',
location1: 'California',
});
// leaderboard: Array<{ display_name, canonical_name, count, average_salary }>adzuna.jobs.geodata(options)
Live job counts broken down by sub-location.
const { locations } = await adzuna.jobs.geodata({
country: 'gb',
category: 'it-jobs',
location0: 'UK',
});
// locations: Array<{ count, location: { display_name, area } }>adzuna.jobs.history(options)
Historical average salary by month. Requires a location with 20+ jobs.
const { month } = await adzuna.jobs.history({
country: 'gb',
category: 'it-jobs',
months: 12,
});
// month: Record<'YYYY-MM', averageSalary>
// e.g. { '2025-08': 52000, '2025-09': 53100 }adzuna.version()
Returns the current API version.
const { api_version, software_version } = await adzuna.version();Constants
Each enum is exposed as a const object (for discoverability) and a type (for parameter checking). You can pass either the named constant or a raw string literal.
import { Country, SortBy, SortDir, ContractTime, ContractType } from 'adzuna-sdk';
Country.GB; // 'gb' — autocomplete friendly
Country.US; // 'us'
// ...all 19 supported countries
SortBy.Default | SortBy.Hybrid | SortBy.Date | SortBy.Salary | SortBy.Relevance;
SortDir.Up | SortDir.Down;
// Response-side unions (useful for narrowing):
ContractTime.FullTime | ContractTime.PartTime;
ContractType.Permanent | ContractType.Contract;Both of these type-check identically:
adzuna.jobs.search({ country: Country.GB, page: 1 });
adzuna.jobs.search({ country: 'gb', page: 1 });Boolean flags
The Adzuna API uses string "1" values for several filter flags. The SDK accepts boolean and handles translation:
| SDK option | Wire format |
| ----------------------- | ---------------------- |
| fullTime: true | full_time=1 |
| partTime: true | part_time=1 |
| contract: true | contract=1 |
| permanent: true | permanent=1 |
| salaryIncludeUnknown: true | salary_include_unknown=1 |
| <any flag>: false | omitted from request |
Error handling
The SDK throws AdzunaError for any 4xx/5xx response whose body matches the documented Exception schema. Other failures (network, parse errors) throw the underlying Ky HTTPError or fetch error.
import { AdzunaClient, AdzunaError } from 'adzuna-sdk';
try {
await adzuna.jobs.search({ country: 'gb', page: 1 });
} catch (err) {
if (err instanceof AdzunaError) {
console.error(`[${err.status}] ${err.exception}: ${err.display}`);
console.error(`See: ${err.doc}`);
} else {
throw err;
}
}AdzunaError shape:
class AdzunaError extends Error {
readonly status: number; // HTTP status
readonly exception: string; // e.g. 'AUTH_FAILED'
readonly display: string | undefined; // human-readable message
readonly doc: string | undefined; // link to Adzuna docs
}Retries and timeout
Retries are handled by Ky and default to 2 attempts on 408, 429, 500, 502, 503, 504.
new AdzunaClient({
appId,
appKey,
retry: 5, // more aggressive retries
timeout: 10_000, // fail fast
});To disable retries entirely, pass retry: 0.
Custom fetch (testing, proxies, instrumentation)
The fetch option accepts any fetch-compatible function. Useful for injecting a mock in tests or routing through a proxy.
const adzuna = new AdzunaClient({
appId,
appKey,
fetch: async (input, init) => {
console.log('→', input);
const res = await globalThis.fetch(input, init);
console.log('←', res.status);
return res;
},
});TypeScript types
All response types are exported from the package root:
import type {
Job,
JobSearchResults,
Category,
Categories,
SalaryHistogram,
TopCompanies,
JobGeoData,
HistoricalSalary,
Version,
Location,
Company,
// and option types:
SearchOptions,
CategoriesOptions,
HistogramOptions,
TopCompaniesOptions,
GeodataOptions,
HistoryOptions,
} from 'adzuna-sdk';If you need the raw OpenAPI types (e.g. to write your own wrapper), they're re-exported as components, operations, and paths.
Development
bun install # install deps
bun run generate # regenerate src/schema.ts from adzuna_swagger.json
bun run typecheck # tsc --noEmit
bun test # run the test suite
bun run build # emit dist/The schema is generated from adzuna_swagger.json via openapi-typescript. Regenerate whenever the swagger changes; the resulting src/schema.ts is checked in so consumers don't need the generator as a runtime dependency.
Releases
Releases are fully automated via GitHub Actions. See RELEASE_PROCESS.md for how to cut a release (spoiler: Actions → Release → Run workflow → pick patch/minor/major).
License
MIT.
