Wipperoz
Browse the docs

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.
  • updatedSince narrows 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’s expiresAt yourself, or ask for status=published and 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.

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}
Wipperoz Logo

Wipperoz is a video-first interactive virtual CV platform designed to replace traditional PDF resumes with dynamic, shareable profiles.

© 2026 Wipperoz. All rights reserved

Developed by epoqx.ai