Rand Stats

WebService::CareJobs

zef:carejobs

WebService::CareJobs

Raku client for the CareJobs.Now healthcare jobs API. Search open US healthcare jobs, see the hourly pay employers post, and look up employers and states.

Where the data comes from

CareJobs.Now is a free US healthcare job board. Its jobs are read daily from the career sites of US hospitals, health systems, clinics and care employers, and the pay is what each posting states (hourly rates before shift differentials and bonuses). Every job links to its page on CareJobs.Now, where the user applies. Data and docs: https://carejobs.now/data.

Data from CareJobs.Now (https://carejobs.now), CC BY 4.0.

Install

zef install WebService::CareJobs

Quick start

use WebService::CareJobs;

my $cj = WebService::CareJobs.new;

my $jobs = $cj.search-jobs(:query<ICU>, :kind(Nurses), :state<TX>, :limit(5));
say "$jobs<total> jobs match";
for $jobs<jobs>.list -> $job {
    say "$job<title> | $job<employer> | $job<location> | $job<url>";
}

my $pay = $cj.pay-by-kind(Nurses);
say "Median posted pay for nurses: \$$pay<pay><median>/hr";

# Every answer also carries the server's readable summary:
say $pay.text;

A runnable version is in examples/search.raku: raku -Ilib examples/search.raku OH.

Methods

Every method returns a CareJobs::Result: the tool's data as a hash ($result<jobs>, or $result.data) plus $result.text, a readable summary written by the server. The keys are the ones the API sends (typical_low, open_jobs, and so on). Where the API sends null, the value is an undefined Any. $result.to-json gives the data back as JSON.

Every argument is optional unless said otherwise, and leaving one out leaves that filter off. A kind may be a CareJobsKind value (Nurses) or its string ("nurses").

MethodWhat it answers
search-jobs(:query, :kind, :employer, :state, :remote, :min-hourly-usd, :sort, :limit)Open jobs. Data: total, pay, jobs, more. :limit is 1 to 25. :sort is mixed, newest or highest_pay.
pay(:kind, :employer)Posted hourly pay: every kind, one kind with the employers posting the most, one employer, or both.
pay-by-kind($kind)pay for one kind of job.
job-market()How many jobs are open, new in the last 7 days, and the open jobs by kind, state and employer.
jobs-by-state($state?, :kind, :limit)Every state, one state, or one kind of job in one state (a kind needs a state).
states($state?, :kind, :limit)Alias of jobs-by-state.
employers(:query, :type, :care, :state, :kind, :limit)The employers, AI-training programs and job boards the site reads. :limit is 1 to 100.
employer($slug)One employer by slug ("kaiser-permanente") or name.
kinds()The 17 kinds of job as slugs.
tools()The server's tools with their input schemas.
call-tool($name, %arguments)Any tool, for a tool this version does not know yet.
my $ohio = $cj.states('OH', :kind(NursingAssistants));
say "$ohio<open_jobs> CNA jobs in Ohio";

for $cj.employers(:query<Kaiser>, :limit(3))<entries>.list -> $entry {
    say "$entry<name> ($entry<open_jobs> open jobs)";
}

Options

my $cj = WebService::CareJobs.new(
    base-url   => 'https://carejobs.now',   # default
    timeout    => 60,                       # seconds, default 60
    user-agent => 'my-app/1.0',             # default "carejobs-raku/<version>"
);

The server answers a query it has not seen before in up to about 25 seconds, then repeats of it in a fraction of one, which is why the default timeout is a minute.

Errors

Every failure throws X::CareJobs. Its reason says which kind:

$cj.jobs-by-state(:kind(Nurses));   # a kind without a state
CATCH {
    when X::CareJobs {
        say "{.reason}: {.message}";
        # "tool": the server ran the tool and said no
        # "rpc": a JSON-RPC error (.rpc-code)
        # "http": a non-2xx status (.status)
        # "network", "timeout": no answer
        # "protocol": the answer was not what MCP promises
    }
}

Kinds

Nurses (nurses), NursePractitioners (nurse-practitioners), Physicians (physicians), NursingAssistants (nursing-assistants), Caregivers (caregivers), TherapyRehab (therapy-rehab), MentalHealth (mental-health), Pharmacy (pharmacy), Imaging (imaging), Lab (lab), SurgicalTechs (surgical-techs), EmtsParamedics (emts-paramedics), MedicalAssistants (medical-assistants), Dental (dental), Admin (admin), AlliedHealth (allied-health), AiTraining (ai-training).

Tests

prove6 -l t                      # offline, with a fake transport
CAREJOBS_LIVE=1 prove6 -l t      # also calls the live API

Plain JSON

Without this library the same data is plain JSON: https://carejobs.now/roles.json (open jobs by kind of job and employer) and https://carejobs.now/pay.json (posted hourly pay by kind of job).

License

Code: MIT, copyright CareJobs.Now. Data: CC BY 4.0, credit CareJobs.Now.