Taxonomy namespace

Reference for category, skill, and place resolution.

Use board.taxonomy to list and resolve the categories, skills, and places used by published jobs, and to load the board’s location directory. Keyword typeahead lives on board.search.suggest. These reads are public.

Shared behavior

Every method returns the Board API wire body unchanged and throws BoardApiError for a non-2xx response. The final FetchOptions argument passes abort signals, headers, and framework cache directives through to fetch.

The three resolve methods share this result:

ts
type TaxonomyResolution = {
object: 'taxonomy_resolution';
type: 'category' | 'skill' | 'place' | 'market';
sourceSlug: string;
canonicalSlug: string;
displayName: string;
redirectTo: string | null;
geo: TaxonomyGeo;
};

sourceSlug is the immutable English taxonomy key. canonicalSlug and displayName reflect the board language. Emit a 308 to redirectTo when it is non-null. geo is null for categories and skills; place resolutions can include latitude/longitude, country/region codes, region, city, locality, and place type.

board.taxonomy.categories.resolve

Resolve a board-language or English category slug.

ts
board.taxonomy.categories.resolve(
slug: string,
options?: FetchOptions,
): Promise<TaxonomyResolution>
ts
const category = await board.taxonomy.categories.resolve('software-engineer');
if (category.redirectTo) {
console.log('Redirect permanently to:', category.redirectTo);
}
const jobs = await board.jobs.list({ category: category.sourceSlug });
console.log(category.displayName, jobs.count);

An unknown category or unavailable board returns categories_not_found with status 404.

Related methods: board.jobs.list and board.companies.salaries.category.

board.taxonomy.skills.resolve

Resolve a board-language or English skill slug.

ts
board.taxonomy.skills.resolve(
slug: string,
options?: FetchOptions,
): Promise<TaxonomyResolution>
ts
const skill = await board.taxonomy.skills.resolve('typescript');
if (skill.redirectTo) {
console.log('Redirect permanently to:', skill.redirectTo);
}
const jobs = await board.jobs.list({ skill: skill.sourceSlug });
console.log(skill.displayName, jobs.count);

An unknown skill or unavailable board returns skills_not_found with status 404.

Related method: board.jobs.list.

board.taxonomy.categories.list

List the board’s active job categories with localized names, canonical slugs, and live published-job counts.

ts
board.taxonomy.categories.list(
query?: TaxonomyListQuery,
options?: FetchOptions,
): Promise<ListEnvelope<PublicTaxonomyTerm>>
ts
const categories = await board.taxonomy.categories.list({ limit: 50 });
for (const category of categories.data) {
console.log(category.displayName, category.canonicalSlug, category.jobCount);
}
const top = await board.taxonomy.categories.list({
limit: 8,
sort: 'jobCount',
});

board.taxonomy.skills.list

List the board’s active job skills with localized names and canonical slugs. Category and skill lists accept the same query fields:

FieldTypeBehavior
qstringCase-insensitive substring match against the display name, source slug, or canonical slug. An empty value returns no terms.
cursorstringOpaque cursor from nextCursor. It is bound to the original q, limit, and sort.
limitnumberPage size from 1 to 100; defaults to 20.
sort'name' | 'jobCount'name (default) is locale-aware display-name order. jobCount is live published-job count, highest first, with name as the tie-break.
ts
let page = await board.taxonomy.skills.list({ q: 'script', limit: 20 });
while (page.hasMore && page.nextCursor) {
page = await board.taxonomy.skills.list({
q: 'script',
limit: 20,
cursor: page.nextCursor,
});
}

Each PublicTaxonomyTerm contains object, id, type, sourceSlug, canonicalSlug, displayName, and jobCount. jobCount is the board-wide published-job total for that term (the same maintained counter that decides whether the term is active) — not a tally of the current list page. Only terms backed by published jobs are returned. Canonical-slug collisions are de-duplicated. A missing or private board returns 404.

board.taxonomy.places.resolve

Resolve a board-language or English place slug and return its geo metadata.

ts
board.taxonomy.places.resolve(
slug: string,
options?: FetchOptions,
): Promise<TaxonomyResolution>
ts
const place = await board.taxonomy.places.resolve('london');
if (place.redirectTo) {
console.log('Redirect permanently to:', place.redirectTo);
}
console.log(place.displayName, place.geo?.countryCode);
const jobs = await board.jobs.list({
location: place.sourceSlug,
radius: 25,
});

An unknown place or unavailable board returns places_not_found with status 404. Do not fabricate coordinates when geo or individual geo fields are null.

Related methods: board.taxonomy.places.list and board.jobs.list.

board.taxonomy.places.list

Without q, return every place used by a published job, with live job counts. With q, return location-autocomplete matches.

ts
board.taxonomy.places.list(
query?: PlacesListQuery,
options?: FetchOptions,
): Promise<ListEnvelope<PublicPlace>>
FieldTypeBehavior
qstringAt least two characters returns ranked name matches. Fewer than two returns an empty list. Omit it for directory mode.
limitnumberAutocomplete result cap from 1 to 50; defaults to 10. Ignored in directory mode.
ts
const controller = new AbortController();
const matches = await board.taxonomy.places.list(
{ q: 'lon', limit: 5 },
{ signal: controller.signal },
);
for (const place of matches.data) {
console.log(place.name, place.slug, place.jobCount);
}

Each PublicPlace contains id, parentId, slug, name, placeType, countryCode, regionCode, and jobCount. In directory mode, use id and parentId to rebuild the place hierarchy. slug can be null, so do not create a public link for an unslugged row.

The directory is bounded and unpaginated; hasMore is false and nextCursor is null for the complete result. A short autocomplete query returning an empty data array is successful. A missing/private board returns 404.

Related methods: board.taxonomy.places.resolve and board.jobs.list.

board.taxonomy.remotePermits.list

List the canonical remote-permit option set — the {type, value, label} entries the API accepts wherever a job states where remote candidates must hold work authorization (remotePermits on v1 job writes, remoteWorkingPermits on public job postings).

ts
board.taxonomy.remotePermits.list(
options?: FetchOptions,
): Promise<ListEnvelope<RemotePermitTaxonomyEntry>>
ts
const permits = await board.taxonomy.remotePermits.list();
const countries = permits.data.filter((entry) => entry.type === 'country');
const blocs = permits.data.filter((entry) => entry.type === 'custom');

Each entry's type is one of worldwide, world_region, continent, region, subregion, custom (blocs such as EU), country (ISO 3166-1 alpha-2), or subdivision (ISO 3166-2). The data is platform reference — identical for every board — and served board-scoped so this client can reach it; responses are cacheable for six hours on public boards. The list is complete and unpaginated (hasMore is always false).

Submitting a {type, value} pair outside this set is rejected (jobs_invalid_permit), so build permit pickers from this list rather than a hardcoded one. For a countries-only picker with localized display names, COUNTRY_CODES and countryOptions from @cavuno/board/format mirror the country slice of this taxonomy without a network call.

Canonical route pattern

Resolve the inbound slug before loading the programmatic page. This keeps localized and English aliases on one URL:

ts
const taxonomy = await board.taxonomy.categories.resolve(inboundSlug);
if (taxonomy.redirectTo) {
return new Response(null, {
status: 308,
headers: { location: `/jobs/${taxonomy.redirectTo}` },
});
}
const jobs = await board.jobs.list({ category: taxonomy.sourceSlug });

Resolve and redirect before emitting canonical metadata. A taxonomy 404 should produce the route’s real not-found response.