API reference · updated 2026-09-20

One set of capabilities. Two ways to call them.

A capability is one thing this server does for you — search LinkedIn through an account you connected, read your inbox, install a product. There are 65. An AI client reaches them over MCP; everything else reaches them over plain HTTP. Same credential, same scopes, same limits, same executor.

One surface, two transports

Everything below is generated from the capability table this server dispatches on, so it cannot describe a capability the endpoint does not serve. The table's fingerprint today is 7b1c2fd4 — it changes whenever a capability's name, arguments, scope or routing change, and the generated client carries a copy so a client can tell whether it was built for the server it is talking to.

43 of the 65 reach LinkedIn through a sender account you connected. The rest read this server's own database — your campaigns, leads, inbox, the product catalogue, your library and projects. The split decides everything that follows: whether a call spends the LinkedIn budget, whether it lands in the execution ledger, and whether an unconfigured provider can make it fail.

Authenticate

One credential for both transports: a workspace API key, minted on the Developer page in the app. Send it as Authorization: Bearer tcz_live_…. The header X-Api-Key works too, and ?key= exists for clients that accept only a URL — it ends up in access logs, so prefer a header.

Scopes belong to the key, not to the workspace: two keys are how you give a third-party client read access to LinkedIn without also handing it the ability to install products. The scopes in use are credits:read, linkedin:propose, linkedin:read, products:install, products:read, projects:read, usage:read, workspace:read, workspace:write. A key minted before scopes existed carries none and is allowed everywhere; that is deliberate, so nobody's integration broke the day scopes shipped.

A key is shown once

Only its hash is stored, so it cannot be read back out of the database — by us or by anyone who gets a copy of it. Lost keys are replaced, not recovered.

Call it over HTTP

GET /api/v1/capabilities lists the surface and needs no credential — it describes the server, not a workspace.

shell
curl -s https://topclozer.com/api/v1/capabilities

POST /api/v1/capabilities/{name} runs one. The body is the arguments object — the same arguments the MCP tool takes — or no body at all.

shell
curl -s -X POST https://topclozer.com/api/v1/capabilities/search_linkedin_people \
  -H "Authorization: Bearer tcz_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keywords":"VP Engineering","industry":"Financial Services","limit":25}'

A success is the capability name and its payload, so a log line says what produced the result:

json
{
  "capability": "search_linkedin_people",
  "result": {
    "searchedAs": "Marcus Feld",
    "count": 25,
    "people": [ … ]
  }
}

Field naming

This surface is snake_case, including the error body. The older resource routes — /api/v1/campaigns, /api/v1/leads, /api/v1/accounts, /api/v1/inbox — return the workspace document as the app stores it, which is camelCase. That inconsistency is real; it is written down here rather than hidden by translating one of them at the edge, and it will be resolved by a v2, not by changing a response somebody is already parsing.

Call it from an AI client

The same capabilities, spoken as JSON-RPC at https://topclozer.com/api/mcp. This is the transport Claude, Cursor, VS Code, ChatGPT, n8n and Make understand.

shell
claude mcp add --transport http topclozer https://topclozer.com/api/mcp \
  --header "Authorization: Bearer tcz_live_YOUR_KEY"

Per-client setup, the tool reference and the response envelopes are in the MCP documentation; the endpoint itself is described at /hosted-mcp.

The CLI

Zero dependencies and no build step. It asks the server what exists rather than carrying its own copy, so it describes the server you point it at — a capability shipped this morning is callable from a CLI installed last year.

shell
export TOPCLOZER_API_KEY=tcz_live_YOUR_KEY

topclozer capabilities            # what this server can do
topclozer describe get_linkedin_profile
topclozer call get_stats
topclozer call search_linkedin_people --keywords "VP Engineering" --limit 25
topclozer usage                   # LinkedIn calls this month, and the limit

Exit codes are 0 success, 1 usage error, 2 the server refused or failed — so a shell script can branch without parsing output.

The TypeScript client

Capability names and argument names are typed, generated from the server's own registry. Results are typed unknown: each payload is documented in prose and an example on its reference page, and inventing interfaces from an illustrative example would produce types that look authoritative and are not.

typescript
import { createClient, TopclozerError } from "@topclozer/sdk";

const topclozer = createClient({ apiKey: process.env.TOPCLOZER_API_KEY });

try {
  const people = await topclozer.call("search_linkedin_people", {
    keywords: "VP Engineering",
    limit: 25,
  });
} catch (e) {
  if (e instanceof TopclozerError && e.errorClass === "rate_limited") {
    // e.retryAfter is in seconds. Backing off is your decision, not the library's.
  }
}

Not on npm yet

The client and the CLI ship in the repository, not in a registry. Until they are published, run the CLI from a clone and build the library with npm --prefix packages/sdk run build.

Neither of them retries. A retry loop against the LinkedIn budget is the exact request pattern that gets a LinkedIn account restricted, and a library that hid a 429 behind three silent attempts would be doing that to your account on your behalf.

Budgets

Two limits, and conflating them is how a job gets planned against the wrong number.

LimitBudgetApplies to
requests120 per 60s per workspaceEvery call on the HTTP surface.
linkedin30 per 300s per workspaceThe 43 capabilities that reach LinkedIn, on either transport.

Both are per workspace, not per key: a second key does not buy a second allowance, and splitting traffic between the two transports does not either. The LinkedIn number is published because it is enforced.

Errors

Every failure carries a stable class, so a client can branch without parsing English: { "error": { "class": "rate_limited", "message": "…", "retry_after": 42 } }. On MCP the same classes become JSON-RPC codes, and the ones a model can act on come back as a readable tool result rather than a protocol fault.

ClassHTTPMeans
invalid_request400The body was not a JSON object.
unauthorized401No valid credential was presented.
forbidden_scope403A valid key that is not scoped for this capability.
unknown_capability404No capability by that name.
capability_refused422It ran and said no, for a reason you can act on.
rate_limited429A budget is spent. Carries retry_after.
upstream_failed502Anything else. A bug here, or a provider that failed in a way we did not model.
provider_unconfigured503Our problem: the provider behind it is not configured on this server.

Every capability

All 65, with the scope each one needs and what it costs. The name is both the MCP tool name and the HTTP path segment.

CapabilityScopeProviderNotes
search_linkedin_peopleSearch LinkedIn for people matching an ICP, through a connected sender accountlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_linkedin_profileRead one LinkedIn profile: identity, current role, followers, job-change signal and latest postlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_linkedin_postsRecent posts by a LinkedIn member or company page, newest firstlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_linkedin_connectionsFirst-degree connections of a connected sender accountlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_account_seatWhat the connected LinkedIn seat includes: Premium, Sales Navigator, Recruiter, Open Profile, InMail credits left, company pages it can message aslinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_my_profileThe connected account's own LinkedIn profile: name, headline, location, public URL, emaillinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_profile_viewersWho viewed the connected account's profile recently, as LinkedIn lists itlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_ssiThe connected account's LinkedIn Social Selling Index: overall score and the four pillarslinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_profile_sectionsOne LinkedIn profile in depth: experience, education, skills (with endorsement ids), languages, certifications, projects, recommendations, contact infolinkedin:readunipileread-only · LinkedIn budget · recorded as a run
check_reachabilityBefore reaching out: degree of connection, pending invitation, whether a connection request, a message or an InMail can go to this person — and whether the do-not-contact list or another account of the workspace already covers themlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_companyA LinkedIn company page: description, industry, size, headquarters, followers, website, employee growthlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_postOne LinkedIn post: text, author, reactions, comments, reposts, and whether the account can react or commentlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_post_commentsComments on a LinkedIn post, or replies to one comment — with each commenter's headline and degreelinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_post_reactionsWho reacted to a LinkedIn post (or one of its comments), with each person's headline and degree — the warmest leads a post produceslinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_profile_activityWhat a member has been doing on LinkedIn: the comments they wrote or the posts they reacted tolinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_sent_invitationsConnection requests the account sent that are still pending — the ones to withdraw before they hurt the accept ratelinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_received_invitationsConnection requests the account received and has not answered, with each inviter's headline and notelinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_followersFollowers of the connected account, or of another member or companylinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_followingMembers and pages the connected account followslinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_conversationsThe account's LinkedIn conversations, newest first — classic, Sales Navigator, Recruiter and company-page inboxeslinkedin:readunipileread-only · LinkedIn budget · recorded as a run
get_conversationOne LinkedIn conversation: who is in it (headline, degree) and its messages, oldest firstlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_recent_messagesThe latest LinkedIn messages across all of the account's conversations, newest firstlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
search_linkedin_companiesSearch LinkedIn companies by keywords, location, industry and headcount — classic or Sales Navigatorlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
search_linkedin_postsSearch LinkedIn posts by keywords — the conversations a market is having this weeklinkedin:readunipileread-only · LinkedIn budget · recorded as a run
search_linkedin_jobsSearch LinkedIn job postings — the companies hiring for a role are the companies with the budgetlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
search_linkedin_urlRun a search a person built in LinkedIn or Sales Navigator and copied the URL of — people, companies, posts or jobs, filters intactlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
find_company_peopleThe people at one company who match a role — decision makers, a department, a title — through the account's searchlinkedin:readunipileread-only · LinkedIn budget · recorded as a run
lookup_search_filterThe ids LinkedIn's typed search filters take, for a name: a location, industry, company, school, job title, skill…linkedin:readunipileread-only · LinkedIn budget · recorded as a run
list_sender_accountsThe LinkedIn accounts this workspace can act as, with their status, warm-up stage and remaining daily budgetworkspace:readinternalread-only
connect_linkedin_accountConnect a LinkedIn account to this workspace. Returns a sign-in link, valid for 24 hours, for the PERSON to open and sign in to LinkedIn on the provider's hosted page; the account is added when they finish. Give the person the URL. Never ask them for their LinkedIn password, a verification code or a session cookie — they enter those only on that pageworkspace:writeinternalwrites
propose_connection_requestPropose a LinkedIn connection request. It is NOT sent: it waits in the workspace's Approvals queue until a person approves it, then goes out under the account's Smart Limits.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_messagePropose a LinkedIn message to a 1st-degree connection. It is NOT sent: it waits in Approvals until a person approves (and may edit) it.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_profile_visitPropose visiting a LinkedIn profile as the account — the person will see it in "Who viewed your profile". NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_inmailPropose an InMail to someone outside the account's network. NOT sent: it waits in Approvals. Spends an InMail credit when sent, unless the person has an Open Profile.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_replyPropose a reply in an existing LinkedIn conversation. NOT sent: it waits in Approvals.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_withdraw_invitationPropose withdrawing a pending connection request. NOT done until approved. LinkedIn then blocks re-inviting that person for about three weeks.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_accept_invitationPropose accepting a connection request the account received. NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_ignore_invitationPropose ignoring a connection request the account received. NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_followPropose following a LinkedIn member — a softer first touch than an invitation. NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_unfollowPropose unfollowing a LinkedIn member. NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_endorsementPropose endorsing one of a member's skills. NOT done until approved. The person is notified.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_post_reactionPropose reacting to a LinkedIn post or one of its comments. NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_commentPropose a comment on a LinkedIn post, or a reply to a comment on it. NOT posted until approved. Public and permanent.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_postPropose a post on the account's own feed, optionally scheduled. NOT published until approved — and not before publish_at.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
propose_message_reactionPropose reacting to a LinkedIn message with an emoji. NOT done until approved.linkedin:proposeunipilewrites · LinkedIn budget · recorded as a run
get_approvalThe status of a proposal made with any propose_* tool: pending, approved, sent, rejected, failed or expiredlinkedin:proposeinternalread-only
list_approvalsProposals in this workspace and what became of them: pending, approved, sent, rejected, failed or expiredlinkedin:proposeinternalread-only
list_do_not_contactThe workspace's do-not-contact list: people and companies no account may invite, message or InMailworkspace:readinternalread-only
add_do_not_contactAdd a person or a company to the do-not-contact list. It can only stop outreach, never start it.workspace:writeinternalwrites
get_usageHow many LinkedIn tool calls this workspace has made this month, and the rate limit in forceusage:readinternalread-only
list_campaignsList all campaigns with performance statsworkspace:readinternalread-only
get_statsGet workspace-wide outreach statsworkspace:readinternalread-only
list_leadsList lead lists and leadsworkspace:readinternalread-only
get_inboxGet unified inbox conversationsworkspace:readinternalread-only
list_tasksList drafts waiting for approvalworkspace:readinternalread-only
pause_campaignPause a campaign by idworkspace:writeinternalwrites
search_capabilitiesSearch what TopClozer products can actually do, and what performs each capability todayproducts:readinternalread-only
search_productsSearch the TopClozer catalogue by outcome, kind, tier, price, risk or capabilityproducts:readinternalread-only
get_productOne product in full: capabilities, risk, price, and whether this workspace has bought or installed itproducts:readinternalread-only
install_productRecord an installation of a product into an AI client or the hosted runner. Writes a row; runs nothing.products:installinternalwrites
list_installationsWhat this workspace has installed, where, and what it paid forproducts:readinternalread-only
list_projectsThe projects in this workspace, with how complete each one's context isprojects:readinternalread-only
get_project_contextThe reusable company / ICP / offer / voice record an agent should read before it writes anythingprojects:readinternalread-only
check_creditsThe workspace credit balance and the ledger entries behind itcredits:readinternalread-only
get_platform_usageWhat this workspace owns and has run: installs, purchases, the hosted seat, credits and the execution ledgerusage:readinternalread-only

What this API does not do

Stated here because finding out from a 404 wastes a developer's afternoon, and because an API that only advertises its strengths is how integrations get built on assumptions.

  • No sending. Nothing here invites, messages or posts on LinkedIn, and there is no plan for a capability that does. Outbound actions are released by the product under the workspace sending policy, which by default waits for a person.
  • No webhooks. There is no way to be told that a reply arrived; you poll get_inbox.
  • No paging on the workspace reads. list_leads and get_inbox return everything in one payload. On a large workspace that response is big; filter on your side.
  • No creating campaigns, importing leads or approving drafts. Those are in the app. An agent can read the approval queue and summarise it; it cannot press send.
  • No idempotency keys. The two write capabilities are effectively idempotent — pausing a paused campaign changes nothing, and an install records a row — but there is no header that makes a retry safe in general, so do not build one that assumes it.

FAQ

Is the HTTP API a different product from the MCP endpoint?
No. They are two transports over one executor. The same 65 capabilities, the same API key, the same scopes, the same LinkedIn budget, the same execution ledger. Whichever one you call, the code that resolves the sender account and talks to LinkedIn is the same code — which is why the two cannot disagree about what a capability does or what it refuses.
Can an agent send a LinkedIn invite or a message through this API?
No, and that is a product decision rather than a gap. 19 of 65 capabilities write anything at all (connect_linkedin_account and propose_connection_request and propose_message and propose_profile_visit and propose_inmail and propose_reply and propose_withdraw_invitation and propose_accept_invitation and propose_ignore_invitation and propose_follow and propose_unfollow and propose_endorsement and propose_post_reaction and propose_comment and propose_post and propose_message_reaction and add_do_not_contact and pause_campaign and install_product), and neither writes to LinkedIn. Outbound actions are released by the product under the workspace sending policy, which by default waits for a person to approve them.
What happens if I hit the LinkedIn rate limit?
You get HTTP 429 with error class rate_limited and retry_after in seconds. Nothing is queued and nothing is retried for you. The limit — 30 calls per 5 minutes per workspace — exists because a retry loop against a live LinkedIn session is the request pattern that gets accounts restricted, and the account at risk is yours.
Do I need an API key to read the documentation endpoint?
No. GET /api/v1/capabilities and GET /api/v1/capabilities/{name} are unauthenticated: they describe the server, not a workspace. Only POST needs a credential.