--- name: curviate-jobs description: "Create, publish and manage classic LinkedIn job postings with the Curviate CLI, and read their applicants. Covers `job` (get, list, create, update, budget, publish, close) and `job applicants` / `job applicant get|resume`, the draft-then-publish flow, the subscription gate that stops a publish even on the free mode, and the read-back delay after an update. Use when drafting or updating a posting, pricing or publishing one, closing one, or pulling applicants and résumés." version: 0.1.0 author: Curviate license: MIT tags: [LinkedIn, CLI, Agents, Sales, Recruiting, Outreach] repository: https://github.com/Curviate/curviate-plugin --- # Curviate: job postings and applicants Every posting starts as a draft. Drafts are free and reversible; publishing is neither. The two things that surprise agents here are that **a free publish is still subscription-gated** and that **an update's read-back lags the write**. Command surface established against CLI `0.33.0`. ## Before any command ```bash npm install -g @curviate/cli && curviate --version # needs Node 18 or newer curviate login --api-key # or export CURVIATE_API_KEY curviate account list --json # the acc_id for --account ``` - **Credentials resolve flag > environment > stored profile** (`CURVIATE_API_KEY`, `CURVIATE_BASE_URL`, `CURVIATE_ACCOUNT`). - **`--profile ` picks the stored credential set; `--account ` picks which connected LinkedIn account owns the posting.** - **`--preview` before every write**, and especially before a publish. On a read command it is refused with exit `2`. - **`--json` on anything you parse**; **`--fields a,b,c`** to project; **`--verbose`** when a slim response looks suspiciously empty. - **Put global flags at the end of the command.** - **Branch on the exit code, never on prose.** See the table at the end. - These are not retrieval-mode commands: `--mode`/`--max-age` are refused here with `unknown flag`, exit `2`. **`job publish --mode` is a different flag with the same name**: it selects a publishing publishing mode and can spend real money. See `curviate-profile` for retrieval mode. ## The commands | Command | What it does | Confidence | |---|---|---| | `curviate job get ` | One public posting's full detail. Takes a job URL or a bare numeric id. No seat needed. | proven | | `curviate job list --state ` | Your own postings by state. `--state` is required. | proven | | `curviate job create …` | Create a **draft**. Never publishes, never spends. | proven | | `curviate job update …` | Partial update to a posting you own; same flags as `create`. | proven | | `curviate job budget ` | Price a publish before committing any money. | proven | | `curviate job publish --mode FREE\|PROMOTED\|PROMOTED_PLUS` | Publish a draft. `PROMOTED` and `PROMOTED_PLUS` spend real money and additionally require `--budget-currency` (ISO-4217), `--budget-amount` and `--budget-scope` (`DAILY` or `TOTAL`). | wired, never live-fired | | `curviate job close ` | Stop a posting accepting applications. **Irreversible once listed.** | wired, never live-fired | | `curviate job applicants ` | Applicants to a posting you own. | proven (shape only, exercised on a draft with no applicants) | | `curviate job applicant get ` | One applicant's full detail, including contact information. | proven (shape only) | | `curviate job applicant resume ` | Download an applicant's résumé. Binary, write it with `-o `. | proven (shape only) | ### `job create`: the required set `--description` (minimum 200 characters, enforced server-side), `--workplace-type` (`ON_SITE|HYBRID|REMOTE`), `--location` (a `LOCATION` parameter id, resolved via `curviate search parameters --type LOCATION`), `--employment-status` (`FULL_TIME|PART_TIME|CONTRACT|TEMPORARY|OTHER|VOLUNTEER|INTERNSHIP`), `--apply-method` (`linkedin` or `external`), plus a title and a company. - Title: `--job-title` as free text, or `--job-title-id` for an existing LinkedIn title. - Company: `--company` as free text, or `--company-id`. - `--apply-method linkedin` requires `--notification-email`; `--apply-method external` requires `--website-url`. - `--skills` takes comma-separated skill parameter ids and is optional. Run `curviate job create --help` for the current required set before building a call. ### Traps - **A publish is subscription-gated in practice, not just on the paid modes.** `--mode FREE` (the money-free mode) still returns `LINKEDIN_FEATURE_NOT_SUBSCRIBED`, exit `5`, on an account with no LinkedIn job-posting subscription. On that error nothing goes public and the draft stays a draft. Expect exit `5` unless you have confirmed the subscription; do not read it as a malformed request. - **`publish` and `close` are wired, never live-fired.** Every test account run against this surface hit the subscription gate above before a posting ever went public, so a live `200` from either command has not been observed; `close`'s happy path is unreachable the same way, since nothing gets published to close. The response shapes are cross-checked against the substrate's own types rather than an observed call. The read and draft surface above `publish` (list/get/budget/create/update, plus the three applicant reads) does not depend on that subscription and is proven. - **An update lands before the read-back reflects it.** `job update` returning exit `0` was applied, but its own response and an immediate `job get` can both still show the old value, while `job list` (the owner view, checked minutes later) shows the update did land. Do not conclude a silent no-op from a stale-looking immediate read. - **`job list --state` filters client-side and says so on stderr.** LinkedIn's own state filter is best-effort, so the CLI re-fetches the unfiltered page walk and drops non-matching items itself, logging what it dropped. Read stdout for data and stderr for what was dropped, and note that `--all` may walk far more upstream pages than the filtered item count suggests. - **`job close` is irreversible once the posting is listed.** Preview it, and be sure. - **Price before you publish.** `job budget ` costs nothing and is the only way to know what a promoted publish will spend. ## A safe posting flow ```bash curviate job create --job-title "…" --company "…" --workplace-type REMOTE \ --location --employment-status FULL_TIME --description "$(cat jd.md)" \ --apply-method linkedin --notification-email --account --preview --json # re-run without --preview to create the draft, then: curviate job get --account --json # confirm the draft curviate job budget --account --json # price it curviate job publish --mode FREE --account --preview --json ``` ## Full command surface Read from the CLI's own `--help` at version 0.33.0. Descriptions, traps and confidence tags elsewhere in this skill are hand-written and carry the version they were established against. Every command below that takes flags at all also accepts `--account`, `--api-key`, `--base-url`, `--beta`, `--fields`, `--json`, `--preview`, `--profile`, `--timeout`, `--verbose`. | Command | Arguments | Flags | |---|---|---| | `curviate job get` | `ID` | *(none)* | | `curviate job list` | *(none)* | `--limit`, `--cursor`, `--all`, `--max-pages`, `--page-delay`, `--state` *(required)* | | `curviate job create` | *(none)* | `--job-title`, `--job-title-id`, `--company`, `--company-id`, `--workplace-type`, `--location`, `--employment-status`, `--description`, `--apply-method`, `--notification-email`, `--website-url`, `--skills` | | `curviate job update` | `ID` | `--job-title`, `--job-title-id`, `--company`, `--company-id`, `--workplace-type`, `--location`, `--employment-status`, `--description`, `--apply-method`, `--notification-email`, `--website-url`, `--skills` | | `curviate job budget` | `ID` | *(none)* | | `curviate job publish` | `ID` | `--mode` *(required)*, `--budget-currency`, `--budget-amount`, `--budget-scope` | | `curviate job close` | `ID` | *(none)* | | `curviate job applicants` | `ID` | `--limit`, `--cursor`, `--all`, `--max-pages`, `--page-delay`, `--ratings` | | `curviate job applicant get` | `ID` `APPLICANTID` | *(none)* | | `curviate job applicant resume` | `ID` `APPLICANTID` | `-o, --output` | ## Exit codes to branch on here | Code | Meaning | What to do | |---|---|---| | `1` | `INTERNAL` from the server itself: a genuine bug on the platform side. | Worth one retry; if it repeats it is a bug to report, not a state to work around. | | `2` | Usage or invalid input, often raised before any network call: a missing required flag, a description under 200 characters, a location passed as a name rather than an id. | Fix the invocation. Never retry unchanged. | | `4` | Not found: the posting does not exist, or is not yours. | Re-check the id and the acting account. | | `5` | Three causes, one code: read `error.code`. `NO_ACTIVE_SEAT`: the account is on no active seat. `LINKEDIN_FEATURE_NOT_SUBSCRIBED`: LinkedIn itself lacks the feature. `BETA_NOT_ENABLED`: the operation is beta-gated and this workspace has not opted in. | Branch on `error.code`: the three fixes have nothing in common, and none is fixed by retrying unchanged. | | `6` | `PLATFORM_RATE_LIMIT` and its siblings. Carries `retry_after` in whole seconds. A response naming `budgetRow` means only that row is paused; every other row on the account keeps working. | **Back off and retry** after that many seconds. On a named `budgetRow`, switch to other work on the account rather than backing off across the board. | | `7` | Transient platform fault: a request that got no response at all (network error, DNS failure, timeout) or one that came back as something other than a valid API answer. Carries `retryLikelyToSucceed: true`. | Retry with backoff. | | `11` | Billing: payment, a cancelled seat, or a subscription lock. `SUBSCRIPTION_BUSY` is retry-likely; check the envelope. | Resolve it in the dashboard. | | `13` | `BUDGET_EXHAUSTED`: a safety rule of your own refused the action, not LinkedIn. Read `error.safetyReason`: `ceiling` means the row named in `error.budgetRow` hit its configured limit; `activity_window` means the account is outside the hours it works in (no `budgetRow` on that one). **Nothing reached LinkedIn and nothing was spent.** `reset_at` can be weeks out, and may be `null` where no clock frees it. | **Do not back off and retry.** `error.safetyHint.parameter` names the exact setting to change. Read `quotas[]` via `curviate account get --json`, then wait for the named reset or change that setting. |