Curviate: search and discovery
SkillSearchcurviate-search is a skill that lets an AI agent search LinkedIn through the Curviate CLI. It covers people, companies, posts, jobs, services and groups, and can run a pasted LinkedIn search URL directly. It also handles pagination and the filter traps that silently return wrong results.
Available today. Use it from your connected AI after setup.
No other account needed.
Have the Curviate CLI available and configured.
Then ask your AI: use the Curviate: search and discovery skill
What your AI can do with it
- Search people, companies, posts, jobs, services and groups via `search people|companies|po
- Run a pasted LinkedIn search URL directly
- Use the `group` read commands
- Paginate through results with `--all`
- Avoid filter traps that silently return wrong results
Getting started
- Have the Curviate CLI available and configured.
- Add the curviate-search skill to your agent.
- Ask the agent to search LinkedIn using keyword queries, or paste a LinkedIn search URL for it to run.
What this skill tells your AI
The instructions your AI receives, as published by davila7/claude-code-templates in cli-tool/components/skills/curviate/curviate-search/SKILL.md and read by ahel’s review.
Search is where most workflows start: find the person, the company, the post or the posting, then act on it. Two rules decide whether a search is trustworthy, and both fail silently when broken: structured filters take opaque ids, never human text, and a single page is not the result set.
Command surface established against CLI 0.33.0.
Before any command
npm install -g @curviate/cli && curviate --version # needs Node 18 or newer
curviate login --api-key <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 <name>picks the stored credential set;--account <acc_id>picks which connected LinkedIn account performs this command. They answer different questions; pass either or both.--accounttakes an id, never an account name.--jsonon anything you parse;--fields a,b,cto keep result sets small;--verbosewhen a slim response looks suspiciously empty.- Put global flags at the end of the command. Trailing placement is unambiguous on every version.
- Branch on the exit code, never on prose. See the table at the end.
- Search is a read.
--mode/--max-ageare refused here withunknown flag, exit2. Retrieval mode exists on four commands only, and none of them is a search (seecurviate-profile).
Resolve filter terms first
Every id-taking filter (--location, --industry, --company, --school, --title on jobs,
--service-category) must be resolved before use:
curviate search parameters --type LOCATION --keywords "Germany" --limit 5 --json
curviate search people --keywords "AI engineer" --location <id> --limit 25 --json
A filter value that is a name rather than an id is silently dropped and the search runs
unfiltered, at exit 0. --location "Germany" has returned New York, Bengaluru and Toronto with
no warning. There is no error to branch on; the only defence is resolving first. Full parameter-type
list and the resolution quirks are in curviate-profile.
The search commands
| Command | What it does | Confidence |
|---|---|---|
curviate search people --keywords "…" | Member search. Filters: --industry, --location, --company, --past-company, --school, --network-distance (1-3), --connections-of, --followers-of, --title (free text), --profile-language. | proven |
curviate search companies --keywords "…" | Company search. Filters: --industry, --location, --has-job-offers, --headcount. | proven |
curviate search posts --keywords "…" | Post search. Filters: --sort-by, --date-posted (past_day, past_week, past_month), --content-type (videos, images, live_videos, collaborative_articles, documents), --posted-by-member, --posted-by-company, --posted-by-me, --mentioning-member, --mentioning-company, --author-industry, --author-company, --author-keywords. | proven |
curviate search jobs --keywords "…" | Job-posting search. Filters: --location (single id) or --region, --location-within-area <miles>, --industry, --seniority, --function, --job-type, --company, --title (ids), --presence (on_site, hybrid, remote), --benefits, --commitments, --date-posted (a number of days), --has-verifications, --under-10-applicants, --in-your-network, --fair-chance-employer, --sort-by. | proven |
curviate search services --keywords "…" | Services Marketplace providers. At least one of --keywords, --service-category or --location is required. Also --connections (1, 2, 3) and --language. | proven |
curviate search groups "<query>" | Keyword search for groups. A no-match search returns an empty list, not an error. | proven |
curviate search "<pasted URL>" | Runs a pasted LinkedIn search, saved-search or lead-list URL directly. Reach for it when you already built the filters in the LinkedIn UI. | proven |
--filters '<json>', --filters-file <path> and --filters - (stdin) submit a raw filter body on
people, companies, posts and jobs. Named flags win on conflict, and the server body schema
is strict: an unknown field is a 400, not an ignored key.
Traps
-
--industryis a company's registered LinkedIn industry, not a topic. Pairing it with a cross-cutting technology theme silently excludes nearly everything relevant: adding an AI-flavoured industry filter to an otherwise identical function-plus-keywords search dropped a result set from 6,360 to 18. Most companies hiring for an AI role are registered under "Software Development" or "IT Services". Use--keywordsand--functionfor themes; keep--industryfor genuine vertical targeting (healthcare, financial services, construction). -
search people --titleis free-text;search jobs --titleis id-based. Resolving a job-title id and passing it tosearch people --titlesilently has no effect: the id is treated as a substring that matches nothing useful. -
--network-distanceand--locationtogether have returned a400onsearch people. Pick one or the other however the values were resolved. -
A salary threshold in the raw filter body is unreliable in low-transparency markets. In Germany, three drastically different thresholds against the same search returned identical, unfiltered counts with no error; LinkedIn's salary data is too sparse there to filter against. Spot-check a filtered against an unfiltered count before qualifying leads by salary.
-
Job postings can carry
company: null, even under--verbose; agency and confidential listings are a legitimate LinkedIn state. Skip them rather than assuming every result has a company. -
Classic job search has no company-size filter. For "mid-size-or-larger companies hiring for X", qualify by size first and then scope the job search to those companies:
curviate search companies --headcount 201-500,501-1000 --location <loc_id> --json curviate search jobs --company <id1>,<id2>,<id3> --function eng --jsonThe reverse direction (search jobs, then fetch each company to check size) works but is an N+1 with no batch lookup. Use it only when the posting itself is the entry point.
-
--headcount 10001+is not yet supported. Omit that bucket.
Groups
| Command | What it does | Confidence |
|---|---|---|
curviate group list | Groups the connected account belongs to: a complete read. --target <slug|URL> enumerates another member's groups instead, which is a partial, interests-only read. | proven |
curviate group get <group_id> | One group's detail: name, member count, description, admin contact, and the write-feasibility gates. | proven |
curviate group members <group_id> | The member roster: id, profile URL, name, headline, relationship signal. --name filters by prefix or substring, case-insensitively. Requires that the connected account is a member of the group. | proven |
Pagination: one page is not the result set
There is no --page N.
| Flag | Effect |
|---|---|
--limit N | Items per page. |
--cursor <c> | Resume from a cursor returned by a previous response. |
--all | Stream every page as NDJSON, one object per line. |
--max-pages N | Cap how many pages --all fetches. |
--page-delay <ms> | Pause between pages (default 400; 0 disables). A modest delay keeps a long stream under the platform rate gate. |
Two output shapes, and they parse differently: a single page is an envelope
({"object": "…_list", "items": [...], "cursor": …}), while --all is NDJSON. Some searches add
paging.total_count.
--all can stop early. Its last stdout line is then
{"object": "stream_truncated", "pages_fetched": N, "has_more": true}. Check for that line before
treating a stream as exhaustive.
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 search | URL | --limit, --cursor, --all, --max-pages, --page-delay |
curviate search people | (none) | --limit, --cursor, --all, --max-pages, --page-delay, --keywords, --filters, --filters-file, --industry, --location, --company, --past-company, --school, --network-distance, --connections-of, --followers-of, --title, --profile-language |
curviate search companies | (none) | --limit, --cursor, --all, --max-pages, --page-delay, --keywords, --filters, --filters-file, --industry, --location, --has-job-offers, --headcount |
curviate search posts | (none) | --limit, --cursor, --all, --max-pages, --page-delay, --keywords, --filters, --filters-file, --sort-by, --date-posted, --content-type, --posted-by-member, --posted-by-company, --posted-by-me, --mentioning-member, --mentioning-company, --author-industry, --author-company, --author-keywords |
curviate search jobs | (none) | --limit, --cursor, --all, --max-pages, --page-delay, --keywords, --filters, --filters-file, --location, --industry, --seniority, --function, --job-type, --company, --sort-by, --date-posted, --region, --title, --presence, --benefits, --commitments, --has-verifications, --under-10-applicants, --in-your-network, --fair-chance-employer, --location-within-area |
curviate search groups | QUERY | --limit, --cursor, --all, --max-pages, --page-delay |
curviate search services | (none) | --limit, --cursor, --all, --max-pages, --page-delay, --keywords, --service-category, --location, --connections, --language |
curviate group list | (none) | --limit, --cursor, --all, --max-pages, --page-delay, --target |
curviate group get | GROUPID | (none) |
curviate group members | GROUPID | --limit, --cursor, --all, --max-pages, --page-delay, --name |
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, usually raised before any network call: an unknown flag, a malformed id, a filter body the strict schema rejected. | Fix the invocation. Never retry unchanged. |
4 | Not found. | Wrong identifier form, or the resource is gone. |
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. A long --all walk is the usual cause; raise --page-delay. On a named budgetRow, switch to other work on the account rather than backing off across the board. |
7 | Transient platform fault: a hiccup, or 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. |
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 <acc_id> --json, then wait for the named reset or change that setting. |
Signals
- GitHub stars
- 32k
- Forks
- 4k
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Questions
- What can it search on LinkedIn?
- People, companies, posts, jobs, service providers and groups, using the `search people|companies|posts|jobs|services|groups` commands or a pasted LinkedIn search URL.
- Can it get all results, not just the first page?
- Yes, pagination with `--all` retrieves further pages of results.
- What are the filter traps?
- The skill covers filter traps that silently return wrong results, so the agent can avoid them when building searches.
Advanced
- Item type
- skill
- Key
curviate-search- Source
- github.com/davila7/claude-code-templates