The queue is the product. This puts it where you already work.
Every morning Hellofew finds a handful of people who fit your ICP, writes each of them a note from their own profile, and waits for you to say yes. That wait is the whole design: nothing leaves your LinkedIn account that you have not read. But the place you read things is not necessarily a browser tab. It might be a terminal, a Slack channel, a CRM, or a Claude window where you are already thinking about this week's pipeline.
So the queue has an API, and an MCP server that speaks it to Claude, Cursor and anything else that takes a URL. Everything it can do, the buttons on the Today page can do, and nothing more. Approving through it is approving: a real invitation leaves from your account, scheduled by the same planner, under the same published limits. There is no way to send faster by being a script.
Keys are made on /integrations once you are signed in. Up to 5 active keys per workspace, shown once, stored as a hash.
Authentication
Every request carries the key as a bearer token. Keys look like hf_live_ followed by 32 characters. A key in a query string is not accepted; it would end up in logs.
curl https://hellofew.com/api/v1/queue \
-H "Authorization: Bearer hf_live_…"Errors are JSON with a status: { "error": "invalid_key", "message": "…" }. A missing or revoked key is 401, an unknown lead is 404, a lead that has already moved on is 409, a bad body is 400.
REST reference
GET /api/v1/queue
The leads waiting for a decision, best fit first.
curl https://hellofew.com/api/v1/queue -H "Authorization: Bearer hf_live_…"{
"count": 2,
"queue": [
{
"id": "0f3c…",
"name": "Priya Nair",
"headline": "Head of Sales, Clinic Software",
"profileUrl": "https://www.linkedin.com/in/…",
"foundBecause": "Commented on Jane Doe's post: …",
"note": "Hi Priya — saw your comment on …",
"dm": "Thanks for connecting, Priya. …",
"followUp": "…",
"score": 82
}
]
}foundBecause is null for a title search and a sentence for an intent signal — a comment on a watched profile's post, a recent post matching a watched phrase. note is what goes with the invitation; dm is sent after they accept.
POST /api/v1/leads/{id}/approve
Approve one lead. A real invitation, from your account, inside the caps. Not undoable once sent.
curl -X POST https://hellofew.com/api/v1/leads/0f3c…/approve -H "Authorization: Bearer hf_live_…"{
"ok": true,
"leadId": "0f3c…",
"wentLive": false,
"sending": "3 of today's 5 invitation slots are still free, so it goes out today if the window is open."
}On a new account the third approval is what switches sending on; wentLive says when that was this one. A lead that is not in the queue any more answers 409 rather than being approved twice.
POST /api/v1/leads/{id}/skip
Drop a lead from the queue. Nothing is sent.
curl -X POST https://hellofew.com/api/v1/leads/0f3c…/skip \
-H "Authorization: Bearer hf_live_…" \
-H "Content-Type: application/json" \
-d '{"reason": "wrong seniority"}'The reason is optional and recorded, so the targeting can be improved from what you skipped.
GET /api/v1/replies?unread=1
People who answered. One thread per person, newest first.
curl "https://hellofew.com/api/v1/replies?unread=1" -H "Authorization: Bearer hf_live_…"{
"count": 1,
"threads": [
{
"leadId": "0f3c…",
"name": "Priya Nair",
"headline": "…",
"profileUrl": "https://www.linkedin.com/in/…",
"intent": "interested",
"isComplaint": false,
"messages": 1,
"latest": { "id": "…", "text": "Sure, send it over", "receivedAt": "2026-10-14T08:12:00.000Z", "read": false },
"ours": { "kind": "message", "body": "Thanks for connecting, Priya. …", "sentAt": "2026-10-13T10:02:00.000Z" }
}
]
}Read-only on purpose. Replying happens on LinkedIn, where the thread already is; a second copy of a conversation is a conversation that gets out of sync. Every reply has already cancelled anything still queued for that person — including the DM, if they wrote first. intent is one of interested, later, not_interested, referral, complaint, other, and null until classified.
GET /api/v1/icp
The targeting the daily search uses.
curl https://hellofew.com/api/v1/icp -H "Authorization: Bearer hf_live_…"PATCH /api/v1/icp
Change titles, industries, geos, keywords, watchProfiles or watchKeywords. Anything else is ignored.
curl -X PATCH https://hellofew.com/api/v1/icp \
-H "Authorization: Bearer hf_live_…" \
-H "Content-Type: application/json" \
-d '{"titles": ["Practice Manager", "Clinic Owner"], "watchKeywords": ["no-shows"]}'Each field is a full list of up to 20 short strings and replaces the old one; a field you leave out is untouched. Positioning and value proposition are not editable here — they are regenerated from your product page — and sending them does nothing. The response is the ICP as it now stands, so you can see what changed. Tomorrow's search uses it.
GET /api/v1/stats
This week and all time, the account, and the caps.
curl https://hellofew.com/api/v1/stats -H "Authorization: Bearer hf_live_…"{
"week": { "found": 23, "approved": 11, "invited": 9, "accepted": 3, "replied": 2 },
"total": { "found": 140, "approved": 88, "invited": 80, "accepted": 27, "replied": 9 },
"acceptRate": 0.338,
"unreadReplies": 1,
"account": {
"connected": true,
"status": "ok",
"sending": { "state": "live", "detail": null },
"rampDay": 9, "rampLength": 21,
"dayCap": 15, "usedToday": 6, "sentToday": 4,
"nextSendAt": "2026-10-14T09:40:00.000Z",
"windowOpen": true
},
"caps": { "invitesPerDay": 20, "invitesPerWeek": 80, "messagesPerDay": 12, "leadsPerDay": 25 }
}The number to watch is replied. The rest are inputs. sending.state is live, approaching (a few more approvals and it starts), held (why, in detail) or blocked.
MCP
The same seven operations as tools, over the MCP Streamable HTTP transport, at https://hellofew.com/api/mcp, with the same bearer key. The tools are get_queue, approve_lead, skip_lead, get_replies, get_icp, update_icp and get_stats. Their descriptions tell the model what the product is and that approving sends a real invitation from your account under the published limits, so a model that has never heard of Hellofew still knows which tool is the irreversible one.
Cursor, Claude Code, and clients that take a URL
{
"mcpServers": {
"hellofew": {
"url": "https://hellofew.com/api/mcp",
"headers": {
"Authorization": "Bearer hf_live_…"
}
}
}
}Claude Code: claude mcp add --transport http hellofew https://hellofew.com/api/mcp --header "Authorization: Bearer hf_live_…"
Claude Desktop (via mcp-remote)
{
"mcpServers": {
"hellofew": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://hellofew.com/api/mcp",
"--header",
"Authorization: Bearer hf_live_…"
]
}
}
}Stateless: every call authenticates on its own, there is no session to resume, and GET returns 405. A client that only speaks the older SSE transport is not supported.
Webhook
Set a URL on /integrations and each reply is POSTed there as JSON shortly after it lands on LinkedIn. The inbox stays the source of truth: if your endpoint was down, GET /replies?unread=1 has everything the webhook would have told you.
POST https://example.com/hooks/hellofew
Content-Type: application/json
x-hellofew-signature: sha256=3f8a…
{
"event": "reply.received",
"at": "2026-10-14T08:12:04.000Z",
"workspaceId": "7b62d6e5-…",
"lead": {
"id": "0f3c…",
"name": "Priya Nair",
"headline": "Head of Sales, Clinic Software",
"profileUrl": "https://www.linkedin.com/in/…"
},
"message": {
"id": "a91e…",
"text": "Sure, send it over",
"intent": "interested",
"receivedAt": "2026-10-14T08:12:00.000Z"
}
}The signature is HMAC-SHA256 of the raw request body under the secret shown once when you set the URL, hex-encoded, in x-hellofew-signature as sha256=<hex>. Anyone can POST to a URL, so compare it before you trust the body — with a constant-time comparison, against the bytes you received, not a re-serialised copy.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, header: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Limits
Every write goes through the same caps as the UI. The API does not have its own numbers; it has the product's, which are constants in the source so that neither you nor we can raise them in a hurry.
- 20 invitations a day, 80 a week (50 a week on a free LinkedIn account, which cannot send notes). Approving a hundred leads through the API queues a hundred approvals; it sends 20, and the rest wait.
- A warm-up ramp. A connected account starts at 5 a day and reaches the ceiling on day 21, because the pattern LinkedIn most reliably restricts is a quiet account that suddenly is not. Reconnecting after a restriction restarts the ramp.
- An accept-rate breaker. LinkedIn watches how many of your invitations are ignored and restricts accounts where the answer is most. We watch the same number and stop first: below 15% the daily cap halves, below 8% sending stops, and a blunter check runs from day four because a proper accept rate needs invitations a week old.
GET /statsshowsdayCapafter the breaker. - Working hours only, in the account's own timezone, and never on a weekend or a public holiday there.
windowOpenandnextSendAtsay when. - Under 150 connections, nothing is sent at all. A near-empty profile sending invitations is the other pattern LinkedIn restricts. The account is held, and
sending.detailsays so. - 12 follow-on messages a day, to people who accepted, and 25 new people found a day, which is why a queue is rarely longer than that.
- 600 API requests per key per hour, answered
429with aretry-afterbeyond it. Counted per server process, not shared, so the true ceiling is a small multiple of that; it exists to catch a loop, not to meter you.
None of this makes a LinkedIn account impossible to restrict. LinkedIn's terms prohibit automation and their detection keys on things nobody outside LinkedIn controls. The honest claim is that these limits sit three to five times under what the category calls safe, and that they are published here so you can check them against what the API does.