Skip to content

The PHP SDK

Everything in bifrost-orbit/careers-sdk for PHP websites, from setup and every client method to applications with files, the candidate portal, refreshing on job changes, errors and tests.

Reference7 min read

The PHP SDK, bifrost-orbit/careers-sdk, puts your jobs on a PHP website: a client for the delivery API, applications with a CV and files, talent pool sign-ups, the candidate portal, a webhook check for refreshing cached pages and a fake transport for tests. It works with Laravel, Symfony, WordPress or plain PHP, and is open source under the MIT licence.

It reads the same endpoints and answers with the same shapes as the JavaScript Careers SDK.

Install

Shell
composer require bifrost-orbit/careers-sdk

It needs PHP 8.1 or later and ext-json. Requests go through ext-curl when it is installed and PHP streams when it is not, so there is nothing else to install. To use your own HTTP client, pass any PSR-18 client.

Set up

Create a public key under API keys.

.envShell
CAREERS_API_URL=https://api.bifrostorbit.com/careers/v1CAREERS_PUBLIC_KEY=careers_pk_...CAREERS_WEBHOOK_SECRET=whsec_...   # for the website webhook
PHP
use BifrostOrbit\Careers\Client;$careers = new Client(    publicKey: getenv('CAREERS_PUBLIC_KEY'),    apiUrl: getenv('CAREERS_API_URL'),    locale: 'de',);// or, reading CAREERS_API_URL, CAREERS_PUBLIC_KEY and CAREERS_LOCALE:$careers = Client::fromEnvironment();
publicKey
What it does
A public key (careers_pk_…). Required.
apiUrl
What it does
https://api.bifrostorbit.com/careers/v1 by default; https://api-stage.bifrostorbit.com/careers/v1 for staging
locale
What it does
Used when a call names no language, such as de. Without it, the career site's main language answers.
timeout
What it does
Per attempt, in milliseconds. 5000 by default.
retries
What it does
Extra attempts at a read after a network or server error. 2 by default. Applications and sign-ups are sent once.
httpClient
What it does
A Transport, or any PSR-18 client with requestFactory and streamFactory

Client::create([...]) takes the same options as an array. An unknown option is refused, so a typo never goes unnoticed.

Client methods

Every method answers with plain associative arrays, in the shapes the delivery API describes.

getJobs([department, location, remote, type, q, page, perPage, locale])
Answers with
Live jobs, newest first: items, locale, total, page, perPage
getJob($slug, [locale])
Answers with
One job with its description, form, privacy notice and JobPosting data. Throws NotFoundException when it is closed or gone.
findJob($slug, [locale])
Answers with
The same, or null
getFilters([locale])
Answers with
Departments, locations, job types and workplaces that have live jobs, with counts
getSite([locale])
Answers with
The career site's name, introduction, logo, colours, languages and current privacy notice
getSitemap()
Answers with
Every live job with its address in each language
apply($slug, $application, [cv, questions])
Answers with
Sends an application: applicationId and updated
joinTalentPool($signup)
Answers with
Keeps someone's details for future jobs
`trackJob($slug, 'view'\
Answers with
'start', [source, utmSource])`
Counts a view or an apply start for the reports. Never throws.
applicationUrl($slug)
Answers with
Where applications for the job are sent
PHP
$list = $careers->getJobs(['q' => 'guide', 'remote' => 'hybrid']);foreach ($list['items'] as $job) {    echo $job['title'], ' ', $job['url'], "\n";}

Text that isn't translated falls back to the main language field by field. $job['fallback'] lists those fields, and $job['locales'] the languages the job is written in, for hreflang links.

Applications

PHP
use BifrostOrbit\Careers\Upload;$job = $careers->getJob('mountain-guide-lofoten');$result = $careers->apply('mountain-guide-lofoten', [    'firstName' => 'Kari',    'lastName' => 'Nordmann',    'email' => 'kari@northwilds.com',    'phone' => '+47 912 34 567',    'answers' => ['q-years' => 6, 'q-areas' => ['lofoten', 'lyngen'], 'q-first-aid' => 'yes'],    'privacyVersion' => $job['privacy']['version'],    'privacyAccepted' => true,    'talentPool' => true,    'source' => 'northwilds.com',    'utm' => ['source' => $_GET['utm_source'] ?? null],    'referral' => $_GET['ref'] ?? null,], [    'cv' => Upload::fromPath($_FILES['cv']['tmp_name'], $_FILES['cv']['name']),    'questions' => ['q-certificate' => Upload::fromPath($_FILES['certificate']['tmp_name'], $_FILES['certificate']['name'])],]);
  • Send the standard fields $job['form']['fields'] asks for, and answers keyed by question id. Choice answers are option ids; yes or no answers are 'yes' or 'no'.
  • privacyAccepted must be true and privacyVersion the version the candidate read. If the notice changed since, the call fails with a request to reload.
  • Files are PDF or Word documents up to 10 MB. new Upload($name, $contents, $type) works for files you already hold in memory.
  • A second application to the same job from the same email updates the first, and answers 'updated' => true.

Candidate portal

PortalClient lets candidates follow their applications on your own website. Keep the session token in something that lasts between requests, such as the PHP session, by implementing Portal\SessionStore; Portal\MemorySession keeps it in memory.

PHP
use BifrostOrbit\Careers\Portal\SessionStore;use BifrostOrbit\Careers\PortalClient;final class PhpSession implements SessionStore{    public function get(): ?string { return $_SESSION['careers_portal'] ?? null; }    public function set(?string $token): void { $_SESSION['careers_portal'] = $token; }}$portal = new PortalClient(publicKey: getenv('CAREERS_PUBLIC_KEY'), locale: 'de', session: new PhpSession());$portal->requestCode('solveig@northwilds.com');$portal->signIn('solveig@northwilds.com', '123456');$me = $portal->me(); // null when signed out
requestCode($email)
What it does
Emails a six-digit code if the address has applied
signIn($email, $code)
What it does
Keeps the session token, which lasts two hours, and answers with it
me()
What it does
Applications, offers, talent pool and requests, or null when signed out
withdraw($applicationId, $reason)
What it does
Withdraws an open application
addDocument($applicationId, Upload $file)
What it does
Adds a PDF or Word document
openOffers()
What it does
Notes that the candidate has seen their open offers
acceptOffer($offerId, ['name' => …, 'consent' => true, 'documentHash' => …])
What it does
Accepts an offer with a simple electronic signature
declineOffer($offerId, $reason)
What it does
Declines an offer
offerPdf($offerId)
What it does
The offer as a PDF, signed once accepted
exportData()
What it does
The candidate's data as JSON
askToCorrect($detail), askToErase()
What it does
Opens a data request
stayInTalentPool(), leaveTalentPool()
What it does
Renews or withdraws talent pool consent
`stopTexts('sms'\
What it does
'whatsapp')`
Stops text messages on one channel
signOut()
What it does
Ends the session

Calls that change something answer with the whole portal again. A 401 throws SignedOutException and clears the session store. Leave out publicKey when apiUrl is your own server, which adds the key.

Refresh on job changes

Point the website webhook at a route like this one:

PHP
use BifrostOrbit\Careers\Webhook;$body = file_get_contents('php://input');if (!Webhook::verify(getenv('CAREERS_WEBHOOK_SECRET'), $_SERVER['HTTP_X_ORBIT_TIMESTAMP'] ?? '', $body, $_SERVER['HTTP_X_ORBIT_SIGNATURE'] ?? '')) {    http_response_code(401);    exit;}$message = json_decode($body, true);foreach (Webhook::tags($message['event'] ?? '', $message['data'] ?? []) as $tag) {    $cache->invalidateTags([$tag]); // careers, careers:jobs or careers:job:<slug>}

Webhook::verify also refuses messages older than five minutes.

Errors

Failed calls throw BifrostOrbit\Careers\Exception\CareersException, or one of its kinds. Each has the HTTP status and the API's errorCode, and a message candidates can read in the language of the request.

NotFoundException
When
No such job, or it is closed, scheduled or unpublished (404)
AuthException
When
No key, or a key that is wrong, revoked or expired (401)
SignedOutException
When
The candidate's portal session is missing or has expired (401)
RateLimitException
When
Too many requests (429). retryAfter is the number of seconds to wait, 60 unless the answer says otherwise.
SubscriptionInactiveException
When
The organisation's Careers Orbit trial or plan has ended, so the career site is paused (402). Not retried. Show a calm "temporarily unavailable" page with your own name; jobs come back as soon as an owner chooses a plan.
UnavailableException
When
Careers Orbit could not be reached, did not answer in time, or failed on its side, after every retry
CareersException
When
Anything else, such as a missing field (422 invalid) or a website not on the allowed list (403)

See Troubleshooting for the messages and their fixes.

Tests

BifrostOrbit\Careers\Testing\FakeTransport answers from a queue and records each request, so your tests never call Careers Orbit:

PHP
use BifrostOrbit\Careers\Client;use BifrostOrbit\Careers\Testing\FakeTransport;$transport = (new FakeTransport())->json(['data' => [], 'meta' => ['total' => 0]]);$careers = new Client(publicKey: 'careers_pk_test', httpClient: $transport);$careers->getJobs(['remote' => 'hybrid']);$transport->lastRequest()->url; // https://api.bifrostorbit.com/careers/v1/jobs?remote=hybrid

Was this page helpful?

Read next