The Swift SDK
Everything in the CareersOrbit Swift package for iOS, macOS, tvOS and watchOS apps and server-side Swift, from setup and every method to applications, the candidate portal, errors and tests.
Reference5 min read
The Swift SDK, CareersOrbit, shows live jobs from the delivery API, takes applications with a CV and runs the candidate portal in iOS, iPadOS, macOS, tvOS and watchOS apps, and in server-side Swift. It does what the client in the JavaScript Careers SDK does, with the same errors. It is open source under the MIT licence.
- async/await on
URLSession, with no dependencies - Safe under Swift 6 strict concurrency: the clients and every model are
Sendable Codablemodels for every answer, with aJSONValuefor a job's JobPosting data- Retries, timeouts and typed errors
It needs Swift 5.9 or later, and iOS 15, macOS 12, tvOS 15 or watchOS 8 or later.
Install
In Xcode, choose File › Add Package Dependencies… and enter https://github.com/bifrost-orbit/careers-sdk-swift, then add the CareersOrbit product.
In Package.swift:
dependencies: [ .package(url: "https://github.com/bifrost-orbit/careers-sdk-swift", from: "0.1.0"),],targets: [ .target(name: "App", dependencies: [.product(name: "CareersOrbit", package: "careers-sdk-swift")]),]Set up
Create a public key under API keys. It can only read live jobs and send applications, so it is safe to ship in an app.
import CareersOrbitlet careers = try CareersOrbitClient(publicKey: "careers_pk_...", locale: "de")let list = try await careers.jobs(.init(remote: "hybrid", query: "guide"))for job in list.items { print(job.title, job.locations.first?.city ?? "")}publicKeycareers_pk_…. Required. Anything else is refused with CareersOrbitError.invalidPublicKey.environment.production (https://api.bifrostorbit.com/careers/v1, the default), .staging (https://api-stage.bifrostorbit.com/careers/v1) or .custom(url)baseURLlocalede. Without it, the career site's main language answers.timeoutretries5xx answer. 2 by default. Applications are sent once.sessionURLSession to use| Option | What it does |
|---|---|
publicKey | A public key, careers_pk_…. Required. Anything else is refused with CareersOrbitError.invalidPublicKey. |
environment | .production (https://api.bifrostorbit.com/careers/v1, the default), .staging (https://api-stage.bifrostorbit.com/careers/v1) or .custom(url) |
baseURL | Overrides the environment's address |
locale | Used when a call names no language, such as de. Without it, the career site's main language answers. |
timeout | Seconds per attempt. 5 by default. |
retries | Extra attempts at a read after a failed connection or a 5xx answer. 2 by default. Applications are sent once. |
session | The URLSession to use |
Methods
jobs(CareersJobQuery)department, location, remote, type and query, with page and perPagejob(_:locale:).notFound when it is closed or gone.findJob(_:locale:)nilfilters(locale:)site(locale:)sitemap()apply(to:_:files:)joinTalentPool(_:)trackJob(_:kind:source:utmSource:)applicationURL(slug:)| Method | Answers with |
|---|---|
jobs(CareersJobQuery) | Live jobs, filtered by department, location, remote, type and query, with page and perPage |
job(_:locale:) | One job with its description, form, privacy notice and JobPosting data. Throws .notFound when it is closed or gone. |
findJob(_:locale:) | The same, or nil |
filters(locale:) | Departments, locations, job types and workplaces that have live jobs, with counts |
site(locale:) | The career site's name, introduction, logo, colours, languages and current privacy notice |
sitemap() | Every live job with its address in each language |
apply(to:_:files:) | Sends an application. A second one to the same job from the same email updates the first. |
joinTalentPool(_:) | Keeps someone's details for future jobs |
trackJob(_:kind:source:utmSource:) | Counts a view or an apply start for the reports. Never throws. |
applicationURL(slug:) | Where applications for the job are sent |
Applications
let job = try await careers.job("mountain-guide-lofoten")let application = CareersApplication( firstName: "Kari", lastName: "Nordmann", email: "kari@northwilds.com", phone: "+47 912 34 567", answers: ["q-years": 6, "q-areas": ["lofoten", "lyngen"], "q-first-aid": .yesNo(true)], privacyVersion: job.privacy.version, privacyAccepted: true, source: "northwilds-app")let files = CareersApplicationFiles(cv: CareersFile(name: "kari-nordmann.pdf", data: cvData))let result = try await careers.apply(to: job.slug, application, files: files)Send the standard fields job.form asks for (job.form.asks("phone")) and an answer for each question, keyed by its id. Choice answers are option ids. privacyAccepted must be true; the client refuses the application before sending it otherwise. Files are PDF or Word documents up to 10 MB.
Candidate portal
CareersPortalClient lets candidates sign in with an emailed code and follow their applications in your app. Keep the session token somewhere safe, such as the Keychain, by implementing CareersSessionStore; CareersMemorySession keeps it in memory.
let portal = try CareersPortalClient(publicKey: "careers_pk_...", locale: "de", session: KeychainSession())try await portal.requestCode(email: "solveig@northwilds.com")try await portal.signIn(email: "solveig@northwilds.com", code: "123456")if let me = try await portal.me(), let offer = me.offers.first(where: { $0.status == "open" }) { _ = try await portal.openOffers() _ = try await portal.acceptOffer(offer.id, acceptance: CareersOfferAcceptance(name: "Solveig Haugen", consent: true, documentHash: offer.documentHash ?? ""))}requestCode(email:), signIn(email:code:), signOut()me()nil when signed outwithdraw(applicationId:reason:), addDocument(applicationId:file:)openOffers(), acceptOffer(_:acceptance:), declineOffer(_:reason:), offerPDF(_:)exportData(), askToCorrect(_:), askToErase()stayInTalentPool(), leaveTalentPool()stopTexts(_:)sms or whatsapp| Method | What it does |
|---|---|
requestCode(email:), signIn(email:code:), signOut() | Sign-in with a six-digit code. The session lasts two hours. |
me() | Applications, offers, talent pool and requests, or nil when signed out |
withdraw(applicationId:reason:), addDocument(applicationId:file:) | Change an open application |
openOffers(), acceptOffer(_:acceptance:), declineOffer(_:reason:), offerPDF(_:) | Read, sign or decline an offer, and download it |
exportData(), askToCorrect(_:), askToErase() | The candidate's data and data requests |
stayInTalentPool(), leaveTalentPool() | Talent pool consent |
stopTexts(_:) | Stops text messages on sms or whatsapp |
Calls that change something answer with the whole CareersPortal again. A 401 throws .signedOut and clears the session store. Leave out publicKey when baseURL is your own server, which adds the key.
Errors
Everything throws CareersOrbitError, apart from CancellationError when the calling task is cancelled.
.notFound(message:)404: no such job, or it isn't live.unauthorized(message:)401: the key was refused, revoked or has expired.signedOut(message:)401 from the portal: the candidate's session is missing or has expired.rateLimited(retryAfter:message:)429: wait retryAfter seconds.subscriptionInactive402: the organisation's Careers Orbit trial or plan has ended, so the career site is paused. Not retried. Check error.isSubscriptionInactive and show a calm "temporarily unavailable" message instead of the jobs..http(status:code:message:)4xx, such as 422 invalid for a missing field.server(status:message:)5xx, after the retries.unavailable(message:).decoding(_:).invalidPublicKey, .invalidURL, .invalidInput| Case | When |
|---|---|
.notFound(message:) | 404: no such job, or it isn't live |
.unauthorized(message:) | 401: the key was refused, revoked or has expired |
.signedOut(message:) | 401 from the portal: the candidate's session is missing or has expired |
.rateLimited(retryAfter:message:) | 429: wait retryAfter seconds |
.subscriptionInactive | 402: the organisation's Careers Orbit trial or plan has ended, so the career site is paused. Not retried. Check error.isSubscriptionInactive and show a calm "temporarily unavailable" message instead of the jobs. |
.http(status:code:message:) | Any other 4xx, such as 422 invalid for a missing field |
.server(status:message:) | 5xx, after the retries |
.unavailable(message:) | Careers Orbit could not be reached or did not answer in time, after the retries |
.decoding(_:) | The answer did not match the type asked for |
.invalidPublicKey, .invalidURL, .invalidInput | The client was set up wrongly, or an application was refused before it was sent |
Messages are written for candidates, in the language of the request. See Troubleshooting.
Testing your app
Pass a URLSession whose configuration has your own URLProtocol in protocolClasses, and answer requests with JSON shaped like the delivery API. The SDK's own tests do this in Tests/CareersOrbitTests/StubProtocol.swift.
Was this page helpful?