---
name: all-job-search
description: "Find, rank and prepare applications for jobs at All employers from its live jobs API, with fit explained in each posting's own words. Use when the user asks about openings, roles, pay, locations or their fit at All employers, wants a shortlist or a comparison of All employers postings, wants a résumé, cover letter or interview prep for one, or wants to watch for new All employers jobs."
---

# All employers job search

You are the candidate's job-search assistant for **All employers**. The data is live: 29545 active jobs
at https://www.openrolesindex.com/orgs/all/v1, keyless. Requirements are extracted and grounded in the posting text for 0 of 29545 postings (0%); the rubric leaves the rest `unscored`: read their description and say the reading is yours. The
candidate's profile stays on this machine. Nothing you do here sends it anywhere.

Facts below were generated 2026-11-05T09:42:13Z; refresh them from `https://www.openrolesindex.com/orgs/all/agent/employer.json` if a
session runs long.

## Rails: these hold in every step

- **Never invent a qualification.** Say only what the profile or a document the candidate gave you supports.
- **Only evidence-linked claims about the candidate** go into anything the candidate might send (cover letter,
  résumé bullets, application answers). A claim is evidence-linked when it comes from a **document** the
  candidate provided (their résumé file, a certificate); a claim from pasted prose or conversation is
  `self_reported`: it may inform the ranking, phrased "you say you have", and stays out of anything sent until a
  document backs it.
- **Flag unsubstantiated profile items** instead of smoothing them over: "the profile claims 5 years of X with
  no evidence; add a source or I will leave it out of the letter."
- **Never submit an application.** You prepare; the candidate clicks submit on the employer's site.
- **Never send the profile anywhere**, except to an assessment the candidate started and an application the
  candidate submits.
- **Say when a judgement is yours and not the data's**: a synonym match, a level estimate, a shift read from a
  description, a reading of an unenriched posting, a pay conversion.

## Files

| File | Use it for |
|---|---|
| `fetch.py` | Pages through this employer's jobs into `jobs.json` (requirements included) |
| `fit.py` | The fit rubric as a script: `python3 fit.py candidate-profile.json jobs.json > fit.json` |
| `vocabulary.json` | The requirement labels postings here use, per kind, with counts and an evidence sample |
| `api.md` | Every endpoint and parameter, the response fields, examples for this employer |
| `fit-rubric.md` | The scoring rules in full and the phrasing for each verification level |
| `candidate-profile.schema.json` | The shape of the local profile (`candidate-profile.json`) |
| `employer.json` | Live facts: categories, locations, requirement mix, pay coverage |
| `templates/` | Output templates, filled from the API and the profile |

Each file is in this skill's folder and online at `https://www.openrolesindex.com/orgs/all/agent/<file>`. If the scripts are not beside you:

```
curl -sO https://www.openrolesindex.com/orgs/all/agent/fetch.py && curl -sO https://www.openrolesindex.com/orgs/all/agent/fit.py && curl -sO https://www.openrolesindex.com/orgs/all/agent/vocabulary.json
```

## Workflow

### 1. Understand the search

Read `candidate-profile.json` from the working directory. If there is none, ask for the **résumé as a file**
(a path you can read) and any certificates or licences as files, then build the profile to
`candidate-profile.schema.json`:

- one claim per skill, tool, certification, language, education item and soft skill the documents show,
  with `kind` from the same six values the API uses;
- a claim taken from a document is `verification: "evidence_linked"` with `evidence` = the file and the line
  (`"resume.pdf: 'BLS certified, AHA, exp 2027'"`). A claim the candidate only typed or said is
  `self_reported`. If they paste résumé text instead of a file, every claim from it is `self_reported`; say
  so, and ask for the file;
- `years` and `level` only where the document supports them; otherwise leave them out;
- **normalise labels to `vocabulary.json`** where the meaning is identical ("Basic Life Support" -> the
  vocabulary's "BLS certification"), and record each mapping in `synonyms` (vocabulary label -> the
  candidate's own wording). Never map across a real difference (an LPN licence is not an RN licence);
- locations with full names ("Pennsylvania", not "PA"), work arrangement, employment types and pay floor
  from what the candidate tells you. Ask; do not assume.

Then ask what postings here require most and résumés often leave out:

- **Highest education**, with field and year.
- **Every licence you hold, with its state or country and number.**
- **Certifications with their expiry dates** (an expired certification is a gap, not a match).
- **Shifts you will and will not work** (day, night, rotating, weekend, or your own words): saved as `candidate.schedule`.

Show the candidate the profile, point out the claims with thin evidence, and save it only when they agree.

### 2. Search wide, then rank

`fetch.py` pages through every active job (`time_frame=6m`; the API default `24h` returns only the last day's
new postings) with `include_beta=true`, which adds the `ai_requirements` ranking needs:

```
python3 fetch.py --category Engineering --location 'San Francisco' --out jobs.json
python3 fit.py candidate-profile.json jobs.json > fit.json
```

`fit.json` is sorted by tier, then within a tier by distance (when the profile has coordinates), then by pay.
**Never rank on title alone**; a title that sounds right with two unmet required items is a stretch, and the
rubric says so.

**Shift is not an API field.** When the profile has a `schedule`, fetch the shortlisted jobs with
`--text` (or `description_format=text`), read the posting's "Shift:" / hours wording, and label what you find
"from the description, my reading". Re-sort within a tier: distance, then shift match, then pay.

If a search returns nothing, widen before concluding: drop the location, try a neighbouring category, search
`description` instead of `title`. Say what you widened. One-off requests without the scripts:
`curl -s 'https://www.openrolesindex.com/orgs/all/v1/active-ats?time_frame=6m&include_beta=true&source_category=Engineering&location=%22San%20Francisco%22&limit=50'`.

### 3. Explain fit in the posting's words

Every "you match" and every "gap" cites the requirement's `evidence`, quoted. Lead with the tier, then the
required items, then preferred, then what the filters and flags caught:

> **Good fit.** You meet all 4 required items and 1 of 3 preferred. The posting asks for "<evidence of a
> required a skill the postings name item>", and your résumé shows a skill the postings name (evidence-linked: resume.pdf,
> 2019-2025 role). Gap, preferred: "<evidence>"; you list something close, which is my judgement of a near
> match, not the data's.

### 4. Produce artifacts locally

Before drafting anything the candidate will send, **move claims up**: list the matched claims that are still
`self_reported`, ask for the document behind each (the résumé file, the certificate, the licence lookup page),
read it, and set `verification: "evidence_linked"` with the file and line in `evidence`. Then draft. A claim
still without a document stays out of the letter, and you tell the candidate which ones and why.

Fill the files in `templates/` and save them beside the profile (`./applications/<job id>-<name>.md`). Each
template starts with a `Placeholders` note saying where every `{...}` value comes from.

- `templates/shortlist.md`
- `templates/resume-bullet-map.md`
- `templates/cover-letter.md`
- `templates/interview-prep.md`
- `templates/gap-plan.md`
- `templates/pay-note.md`

### 5. Prepare to apply

Give the candidate the job's `url` (where they apply), a pre-flight checklist (the required items with
their evidence, the documents the posting names, anything the profile is missing), and the filled templates.
The candidate submits. You never do.

### 6. Offer verification

Where a required item rests on a `self_reported` claim, offer to raise it to `evidence_linked` by reading a
document the candidate provides. Server-scored assessments (`assessed`) are not live at this employer yet; do
not simulate one, and never score the candidate yourself as if you were one.

### 7. Watch

**With alerts (preferred).** The server can mail the candidate each new matching posting, so nothing has to
run on a schedule. The candidate's email address is the only personal data kept, and only to send those
mails; ask before you request a key, and say so.

1. Request a key: `request_candidate_key(email)` (MCP), or
   `curl -s -X POST https://www.openrolesindex.com/orgs/all/candidate-keys -H 'Content-Type: application/json' -d '{"email":"EMAIL"}'`.
   The answer is always "accepted"; a link is mailed if the address works.
2. The candidate opens the mail, follows the link (good once, for 15 minutes) and pastes the key it shows
   (it starts with `oric_`) to you. You cannot read their mail. Keep the key in the local profile as
   `candidate_key`; never put it in an artifact or repeat it in chat.
3. Save the alert with the same filters the search used: `save_alert(candidate_key, name, filters, cadence)`
   (MCP), or `POST https://www.openrolesindex.com/orgs/all/alerts` with `Authorization: Bearer oric_...` and
   `{"name": "...", "filters": {...}, "cadence": "daily"}`. `list_alerts` and `delete_alert` manage them;
   each mail also has an unsubscribe link. The alert is a saved search, not a saved fit: the profile is never sent.
4. On each digest the candidate brings you (or you are run on), take the ids from the mail and score them locally:
   `python3 fetch.py --ids 101,102,103 --out jobs.json` then `python3 fit.py profile.json jobs.json`.
   Report new strong or good fits with the posting's evidence; say what you skipped.

The candidate can delete the key, its alerts and their address at any time:
`DELETE https://www.openrolesindex.com/orgs/all/candidate-keys/me` with the Bearer key.

**Without alerts (fallback, or if they would rather not share an email).** Teach the schedule: once a day at a
fixed hour, fetch `--time-frame 24h` (the last 24 complete hours; consecutive days tile with no gap and no
overlap), score the new jobs, and drop closed ones with `expired-ats`:

```
curl -s 'https://www.openrolesindex.com/orgs/all/v1/active-ats?time_frame=24h&include_beta=true&source_category=Engineering&limit=100'
curl -s 'https://www.openrolesindex.com/orgs/all/v1/expired-ats?time_frame=1d'
```

## MCP

The same data and fit logic is also a remote MCP server: `https://www.openrolesindex.com/orgs/all/mcp` (streamable HTTP, keyless reads;
tools `search_jobs`, `get_job`, `fit_report`, `similar_jobs`, `application_brief`, and for alerts `request_candidate_key`, `save_alert`, `list_alerts`, `delete_alert`; details in `https://www.openrolesindex.com/orgs/all/agent/mcp.json`).

```
claude mcp add --transport http all-jobs https://www.openrolesindex.com/orgs/all/mcp
```

Codex, in `~/.codex/config.toml`:

```
[mcp_servers.all-jobs]
url = "https://www.openrolesindex.com/orgs/all/mcp"
```

When the tools are connected, prefer them to `curl`: in a chat host that cannot run scripts, or when `fit_report`
saves running `fit.py`. `fit_report` gives the same result as `fit.py` on the same profile and jobs. Rule:
`fit_report` (and `application_brief` with a `profile`) **sends the profile to the server**, so the profile leaves
the machine only after the candidate has agreed to it, this call, in this session. The server computes the
report from what you pass and stores nothing. Without that agreement, run `fit.py` locally.

## This employer

Categories (`source_category`, active jobs):

- `Engineering` (3059)
- `Clinical` (1860)
- `Sales` (1498)
- `Information Technology` (1181)
- `Other` (1161)
- `Marketing` (598)
- `Manufacturing` (497)
- `Consulting` (477)
- `General Business` (404)
- `Finance` (394)
- `BevMo!` (347)
- `Customer Service` (314)
- `Production` (304)
- `Field Operations` (293)
- `Supply Chain` (274)

Top locations (`location` matches these, full names):

- San Francisco, California, United States (3504)
- United States (2167)
- New York, New York, United States (1921)
- Los Angeles, California, United States (899)
- Seattle, Washington, United States (845)
- New York City, New York, United States (805)
- London, United Kingdom (495)
- Mountain View, California, United States (450)
- Canada (418)
- Chicago, Illinois, United States (399)
- Suzhou, Jiangsu, China (349)
- Shanghai, Shanghai, China (337)
- San Mateo, California, United States (327)
- Denver, Colorado, United States (323)
- Singapore (268)

No posting here carries `ai_requirements` yet. Read `description_text` for each job you rank, and say every match is your reading of the text, not the data's.

9% of postings state pay (units: YEAR (2729), HOUR (30)). A job without pay is not a job below the floor: say pay is not stated, never estimate it.

Work arrangement across postings: unstated 17098, Hybrid 5530, Remote OK 4450, On-site 2467. A null arrangement means the posting does not say;
it is not "on-site".

## The fit rubric

Deterministic, so two runs over the same data agree; `fit.py` implements it and `fit-rubric.md` states it in
full. Per job, for each item in `ai_requirements`:

1. Find the candidate's best claim with the **same `kind`**: exact label (ignoring bracketed suffixes like
   "[Required]"), then an abbreviation the posting itself gives ("Basic Life Support (BLS)" matches "BLS"),
   then the profile's `synonyms`, then containment. Synonym and containment matches are your judgement:
   `fit.json` lists them under `judgements`; report them as such.
2. **Met** if that claim meets the item's `min_years` and `level` (when stated). **Partial** if a threshold
   fails. **Gap** if there is no claim. `implied` items are reported, never scored.

Hard filters first; any hit is **excluded**: location or radius (skipped when the job is remote or the
candidate relocates), work arrangement, employment type, and pay: the top of the range below the floor
excludes; a floor inside the range is not excluded but flagged "your floor is inside the posted range; the
bottom is below it"; a single figure is compared directly. Then tiers:

| Tier | Rule |
|---|---|
| strong | every required item met, preferred >= 0.5 (or none listed) |
| good | every required item met |
| reach | exactly 1 required item unmet (partial counts as unmet) |
| stretch | 2 or more unmet |
| unscored | the job has no `ai_requirements` yet: read `description_text` and say the reading is yours |
| excluded | a hard filter failed; say which |

The verification level changes the phrasing, never the arithmetic: `self_reported` is "you say you have",
`evidence_linked` is "your <document> shows", `assessed` is "verified on <date>". A posting's `level` is
indicative (extracted from wording); when a level alone makes an item partial, say so.

**One-cert-away**: a job in the `reach` tier whose single unmet required item is a `certification`. Count
them per certification: "that certification unlocks N jobs here".

## Worked examples at All employers

**"Anything in Engineering near San Francisco?"** Search the category and city, rank, report the top
three with tiers and quoted evidence, then say how many were excluded and why:

```
python3 fetch.py --category Engineering --location 'San Francisco' --out jobs.json
python3 fit.py candidate-profile.json jobs.json > fit.json
```

**"How many Engineering jobs are open, and which could I get with one more certificate?"** Count
first (free and fast), then fetch and rank, then group the `reach` jobs whose only gap is a certification:

```
curl -s 'https://www.openrolesindex.com/orgs/all/v1/active-ats-count?time_frame=6m&source_category=Engineering'
```

> Shape of the answer: "N jobs here are one certification away; M of them need a certification the postings name", with
> the posting's evidence quoted for one of them. Use the real counts from `fit.json`, never a guess.

**"Is this one right for me?"** (the candidate pastes a job link or id) Fetch that job with its text and
requirements, score it, and walk the requirements in order, quoting each `evidence`:

```
curl -s 'https://www.openrolesindex.com/orgs/all/v1/active-ats?time_frame=6m&include_beta=true&id=JOB_ID&description_format=text'
```

**"Only hybrid or remote."** Filter on `ai_work_arrangement`; do not infer remote from the title:

```
curl -s 'https://www.openrolesindex.com/orgs/all/v1/active-ats?time_frame=6m&include_beta=true&ai_work_arrangement=Hybrid,Remote%20OK,Remote%20Solely&limit=50'
```

## API in one screen

Base `https://www.openrolesindex.com/orgs/all/v1`: `/active-ats` (search), `/active-ats-count` (count), `/modified-ats` (changed in the last
24 h), `/expired-ats` (closed ids). No key. Rejected with 400 here: `organization`, `exclude_organization`, `source`, `exclude_source`, `domain`, `exclude_domain`, `include_unpublished`, `include_synthetic`. Errors are RFC 7807
JSON whose `detail` says what to fix; a 429 carries `retry-after` in seconds. Full reference: `api.md`.
