---
name: candidate-sourcing
description: Find LinkedIn candidates matching a role spec, shortlist them, and enrich the shortlist with current role and experience. Use when the user wants to source, find, or search for candidates, talent, or people matching criteria like title, school, or current/past employer (e.g. "engineers at Shopify who used to work at Amazon").
---

# Candidate sourcing with Veezee

Turn a role spec (title, company, school, or keywords) into a ranked shortlist with enriched profiles. LinkedIn only; this skill covers no other platform. Veezee returns no emails or phone numbers; if the user needs contact details, tell them plainly that this skill cannot provide them.

## Setup

Every call needs a key. If a call fails with `KEY_REQUIRED` (401), get one right away: run `vz init` in a shell. It POSTs to `https://api.veezee.io/v1/keys/mint` (no auth, no body, no signup) and writes the key to `~/.veezee/config`, never to the chat transcript, so you never need to paste a key. Retry the call once it is set. The key itself carries no balance: the free tier is a per-IP, per-day allowance (200 credits/day, recent data, first page only). Two equivalent surfaces; pick whichever your environment has:

- MCP: add the server `https://mcp.veezee.io/linkedin` (streamable-http; `https://mcp.veezee.io/all` exposes every tool). Call the tools directly; there is no auth step.
- SDK (this package): `import { VeezeeClient } from "@veezee/sdk"`; `const client = new VeezeeClient();` then `await client.mint()` (reuses a key from `vz init` or mints one). Platform methods live on the namespace (`client.linkedin.getProfile/searchPeople/getCompany/getPosts`); `client.resolveUrl` and `client.getUsage` are top-level. The client sends retries and Idempotency-Keys for you. The `veezee` CLI (from `npm install -g @veezee/sdk`) exposes the same operations.

When the free tier runs out, the call fails with `TRIAL_CAP_EXCEEDED` carrying `upgrade_url` (https://veezee.io/upgrade) and a machine-readable `offer`. Hand that link to your human; the purchase credits the same key you already have configured. Note: one search plus a couple of profile fetches exhausts the free tier, so sourcing runs at any real volume need paid credits.

## The workflow

1. Check the budget first: `get_usage` is free, exempt from the rate limit, and works on every key, trial included. Do not start a search you cannot afford to enrich.
2. Turn the role spec into `search_people` filters: `keywords` (free text), plus `title`, `current_company`, `past_company`, `school` as the spec supplies them. "Engineers at Shopify who used to work at Amazon" becomes `title: "engineer"`, `current_company: "Shopify"`, `past_company: "Amazon"`.
3. Pick a `limit` once and prefer it over paginating: a single call at a larger limit is cheaper than several smaller pages. Do not combine a company NAME filter with the largest limit; keep the limit modest with a name filter, or pass the company's numeric id or URN (from `get_company`) to use the largest limit.
4. Review the returned candidates: name, position, location, and identifier. Drop any result marked `is_anonymous: true` from the enrichment list; it is a private profile that cannot be fetched further. Keep it in the shortlist as "match found, profile private" if the user wants a count.
5. Shortlist the candidates worth enriching, then call `get_profile` for each with `sections: ["experience"]`. The overview plus the first two sections are included in the base price.
6. If a candidate's identifier is a dirty or shortened URL rather than a clean slug or URN, run `resolve_url` first. Skip it for clean slugs or URNs; it only spends credits for nothing there.

## Rules that save credits and errors

- Never call `get_profile` on an `is_anonymous: true` search result; it cannot be dereferenced. Treat it as a confirmed match and move on.
- Set `max_credits` on each call in a large batch; a call whose quote exceeds it is rejected with nothing charged, so you can keep going instead of overspending.
- Default freshness is cached (usually a few hours old) and free. `freshness: "realtime"` costs extra and needs credits on your key: trial keys are cached-only and reject realtime; paying upgrades the same key. Reach for it only when the user needs today's data.
- On TRIAL_CAP_EXCEEDED, INSUFFICIENT_CREDITS, or BUDGET_EXHAUSTED, stop the batch and hand the error's `upgrade_url` to your human; with a key, purchases credit the account directly and the same key keeps working afterward.
- A 403 NOT_ENTITLED means the key's account is not enabled for this platform; grants are explicit, write to hello@veezee.io to enable more.
- Veezee has no contact-detail lookup (no email, no phone). Do not imply the shortlist includes a way to reach anyone directly.

## Output

Report a shortlist ranked by fit: name, current title, current company, and the two most recent experience entries per candidate. Note any candidates skipped because their profile was private. Include total credits spent (sum the `usage.credits_charged` fields) so the human can budget the next search.
