Render roles on your careers site
Updated
The reason the API exists: your roles on your domain, with candidates still applying through the hosted form that records their consent. This page is the pattern, with the decisions that matter called out.
Fetch on the server, on a schedule
Read the API from your build step or your server, never from the visitor’s browser, and never on every page view. A careers page changes a few times a week; fetching it every fifteen minutes is generous.
curl "https://api.wipperoz.com/v1/jobs?status=published&limit=100" \
-H "Authorization: Bearer $ORBIT_API_KEY"
A minimal sync in Node:
const BASE = 'https://api.wipperoz.com';
export async function fetchOpenRoles(): Promise<Job[]> {
const jobs: Job[] = [];
let cursor: string | null = null;
do {
const url = new URL('/v1/jobs', BASE);
url.searchParams.set('status', 'published');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, {
headers: {Authorization: `Bearer ${process.env.ORBIT_API_KEY}`},
});
if (!response.ok) {
throw new Error(`Orbit API ${response.status}: ${await response.text()}`);
}
const page = (await response.json()) as {jobs: Job[]; nextCursor: string | null};
jobs.push(...page.jobs);
cursor = page.nextCursor;
} while (cursor);
return jobs;
}
Cache, and know when to refetch
- Cache the whole list and rebuild your page from the cache. Do not cache per visitor.
- Refetch on a timer, not on demand. If your site builds statically, trigger a build on the same timer.
updatedSincenarrows a refetch to roles that were edited, published or closed since your last sync. It does not catch a role that expired, because expiry is a clock passing a date and nothing is written when it happens. Either read each cached role’sexpiresAtyourself, or ask forstatus=publishedand treat anything missing from the answer as gone.
Render the list and the detail
Use the posting’s own vocabulary and map it once. employmentType is full_time or part_time; contractType is one of permanent, part_time, fixed_term, contract, casual; a skill’s level is required or nice_to_have. These values are the same on every Wipperoz surface, so a mapping you write today keeps working.
url is the role’s page on Wipperoz, when it has one. You may link to it, but the point of this guide is that you do not have to.
Send candidates to applyLink
Every job carries an applyLink. Make your “Apply” button go there.
The link’s src=careers&account=… query is how the apply page records the entry origin. Leave it intact; it is what lets you see, in Orbit, which applications came from your site.
Closed and expired roles
A role you have already linked to may close. The API keeps returning it with status: closed or status: expired so your page can:
- keep the URL alive and show “this role has closed”, or
- remove the role from the list and redirect its URL to your careers home.
Either is fine. Silently 404ing is the one outcome to avoid, and the status field is there so you never have to.
Structured data
If you emit JobPosting JSON-LD, the fields map directly:
| JSON-LD | From the job |
|---|---|
title |
title |
description |
description |
datePosted |
postedAt |
validThrough |
expiresAt |
hiringOrganization.name |
company.name |
jobLocation |
location.city, location.state, location.country |
employmentType |
employmentType, contractType |
baseSalary |
salary.min, salary.max, salary.currency, salary.period |
url |
your own page for the role |
Reference
List the account's jobsGET /v1/jobs
Read one jobGET /v1/jobs/{jobId}