Skip to content
Automation
Skill

/apply

Apply to a single job (URL or pasted page) with fit review, or score and apply the job links pasted into an apply campaign when no argument is given.

From plugin
jobpilot
7331 skills2 agents2 MCP
Install
$ npx -y skills add suxrobGM/jobpilot --skill apply --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/apply

Context preview

The summary Claude sees to decide when to auto-load this skill.

Apply to a single job (URL or pasted page) with fit review, or score and apply the job links pasted into an apply campaign when no argument is given.

SKILL.md

apply.SKILL.md
name: apply
description: Apply to a single job (URL or pasted page) with fit review, or score and apply the job links pasted into an apply campaign when no argument is given.
argument-hint: "[job_url_or_pasted_job_page | campaign <id>] (omit to pick up your pasted links)"

Apply - Single Job or Pasted Links

Two modes, one shared apply loop:

  • **Single-job** (argument is a URL or pasted job page): fit review → user "yes" → apply one.
  • **Batch** (no argument, or a campaign that still holds `queued` links): score the campaign's

`queued` rows → ranked table approval → apply all.

User approves once up front. No per-job confirmation after that.

Setup

Follow `../_shared/setup.md` to load profile, resume, credentials. Shared campaign mechanics (applied-check, result writes, worker input, rules) live in `../_shared/campaign-flow.md`.

Read `autoApply` for config (defaults applied per field):

| Setting | Default | Notes | | ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------------ | | `minMatchScore` | 60 | Batch-mode threshold (0-100); the campaign's `config.minScore` wins when set. Ignored in single-job mode. | | `maxApplicationsPerCampaign` | `null` (unlimited) | Sent as `config.maxApplications` when set; omit for unlimited batch. Single-job mode forces `1`. | | `defaultStartDate` | `"2 weeks notice"` | Default start-date answer. |

For ATS portals (Greenhouse, Lever, Workday, etc.) the apply step lands on a domain that isn't in `/api/job-boards`; the `job-worker` handles login/registration there per `../_shared/auth.md`.

Phase 0: Dispatch

  • Argument is `campaign <campaign-id>` → set `CAMPAIGN_ID=<campaign-id>` and fetch it first (`GET /api/campaigns/$CAMPAIGN_ID`); the summary decides which of the two branches runs:
  • `summary.byStatus.queued > 0` → the campaign still holds pasted links nobody has visited: go to **Phase 2**, skipping 2.1 (the campaign is already resolved).
  • otherwise → **re-apply mode**: set `config.maxApplications = null` (unlimited - the user hand-selected these jobs), skip Phases 1-4, and run the Phase 5 loop over its current `approved` jobs. (The campaign viewer - or the `rescan-skipped` skill - promotes the chosen skipped/failed jobs to `approved` before injecting this.)
  • Any other argument present → **Phase 1** (single-job).
  • No argument → **Phase 2** (batch).

---

Phase 1: Single-Job Mode

If the argument is pasted content (HTML / text), extract description, Apply URL, company, title. If no Apply URL can be found, stop: **"I need either a job URL or content with a visible Apply link."**

1.1 Fit Review

**URL input** → delegate to the `job-worker` subagent with `mode:"review"` so the posting snapshot stays out of this conversation: `{ "mode":"review", "url":"<job-url>", "resumeId":"<primary-or-empty>" }`. Use its returned `matchScore`/`strongMatches`/`partialMatches`/`gaps`/`blockers`/`visaRisk`/`recommendation` to fill the review below; keep its `digest` as `DIGEST` for 1.4.

**Pasted input** → parse the fields yourself (the content is already in hand), build the digest (`../_shared/digest-schema.md`), and `POST /api/score-fit {digest, minScore:<minMatchScore>}` for the score. Its `fit.verdict` drives the recommendation below: `trust` → report the score as-is; `deliberate` → reason from `strongMatches`/`partialMatches`/`gaps` first. Keep the digest in `DIGEST=...` for 1.4.

## Job Fit Review: [Title] at [Company]

**Match Score: X/100**

**Strong Matches:** [skill - evidence]
**Partial Matches:** [skill - what's adjacent]
**Gaps:** [skill - what's missing]
**Visa/Sponsorship Risk:** [if mentioned]
**Verdict:** [1-2 sentence recommendation]

Ask: **"Want me to proceed with the application?"** - `yes`/`go` continue, anything else stop.

1.2 Dedupe Check

Run the applied-check (`../_shared/campaign-flow.md`) with url + title + company. If `.applied === true`, surface the match (title + company + appliedAt + `.match.kind`) and ask whether to proceed anyway. Stop on no.

1.3 Create Campaign-of-1

CAMPAIGN=$(curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/campaigns" \
  -H 'content-type: application/json' \
  -d "$(jq -n --arg query "<title> at <company>" \
    '{query:$query, source:"apply", config:{maxApplications:1}}')")
CAMPAIGN_ID=$(echo "$CAMPAIGN" | jq -r '.campaignId')

1.4 Add the Job

JOB_KEY=$(date -u +%s)-single
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/campaigns/$CAMPAIGN_ID/jobs" \
  -H 'content-type: application/json' \
  -d "$(jq -n --arg key "$JOB_KEY" --arg title "<title>" --arg company "<company>" \
    --arg location "<location>" --arg url "<job-url>" --arg board "<board>" \
    --arg matchReason "<one-line verdict>" --argjson score <0-100> \
    --arg digest "$DIGEST" --arg desc "<posting text>" \
    '{key:$key, title:$title, company:$company, location:$location, url:$url, board:$board, matchScore:$score, matchReason:$matchReason, status:"approved", digest:$digest, description:$desc}')"

Keep `$CAMPAIGN_ID` and `$JOB_KEY`. Live view: `$JOBPILOT_WEB/campaigns/<CAMPAIGN_ID>`. Jump to **Phase 5**.

---

Phase 2: Batch Mode

The campaign already exists (the new-campaign dialog creates it); its rows start `queued` - a bare URL with a hostname placeholder title. This skill never creates one. Phase 3 turns those rows into real, scored `pending` jobs.

2.1 Resolve the Campaign

Skip this when the `campaign <id>` dispatch already set `CAMPAIGN_ID`. Otherwise take the newest `apply` campaign still holding queued rows (`.items` is newest-first):

CAMPAIGN_ID=$(curl -fsS -
Read more
Ships withjobpilot

An AI agent that applies to jobs for you, on the Claude or Codex subscription you already have.

Get the whole plugin

Other skills on jobpilot.