Skip to main content
The LinkedIn Recruiter inbox is the third messenger, with its seat, its session clock and the 24-hour rule
273 operations: LinkedIn 179, ID and Teams 72, Orchestration 22.Recruiter conversations are their own surface. Threads and messages carry messenger_type: "recruiter" and the candidate’s talent_id; sync-my-recruiter-conversations runs the background sync (one run walks the INBOX tab and then UNRESOLVED, where a thread you opened sits until the candidate replies), get-my-latest-recruiter on conversations and on messages is the head refresh, and recruiter_conversations joins the sync, webhook and reset-sync vocabularies with the same 120-minute default cadence as Sales Navigator. The account carries recruiter_seat_id, recruiter_contract_id, recruiter_session_expires_at and last_recruiter_conversations_sync_at, all filterable; the two clocks sort.The seat and the session are facts on the account, not inputs. The premium check (checks: ["recruiter"]) stamps the seat number and its contract and reads the expiry of LinkedIn’s 30-day Recruiter session. Recruiter calls without a seat answer 422 recruiter_required or 422 recruiter_seat_unresolvable; past the session clock they answer 409 recruiter_reauth_required until the seat holder signs in to Recruiter in the account’s browser. No Recruiter password is ever stored. get-my-recruiter-seat and get-my-hiring-projects read the seat’s own entitlements and hiring projects.The Recruiter people search and its typeahead. search-recruiter-people runs the LinkedIn Recruiter search from a filters object (chips with exclude, required and a per-facet scope, the closed code sets, the three year sliders) or from a pasted Recruiter url, 25 rows a page, 40 pages at most, LinkedIn’s real total, and rows that carry the headline, current position, location, connection degree, talent_id, can_send_inmail and open_to_work. A Recruiter URL carries no filters, only the searchHistoryId of the search LinkedIn keeps on the seat, so the url half replays that stored search; every answer returns search_history_id and search_url, the handle to page by without re-sending the filters. recruiter-param-id-lookup resolves the eleven chip kinds into their ids. LinkedIn’s per-seat search throttle answers 429 recruiter_search_usage_limit with LinkedIn’s own sentence and a one-minute retry_after. On MCP both ride on /mcp/linkedin/scraping with every other live list.Sending is send-recruiter. One verb over two LinkedIn wires: a new thread by talent_id, ln_id or sn_id, or a reply by thread sid. LinkedIn’s one InMail per candidate per 24 hours is enforced before dispatch and read back from the wire: 429 rate_limited with reason: "recruiter_inmail_cooldown" and a retry_after, on a fresh InMail and on a reply the candidate has not answered yet.The first sync now tells you when it is done. The person who connected a LinkedIn account gets Your LinkedIn account is ready to use when its onboarding sync latches, with the connections and conversations it brought in and how long it took. A large account can spend its first-day read budget part-way: it then carries initial_sync_held_at and initial_sync_hold_reason (filterable), emits linkedin-accounts.initial-sync-held, resumes on its own after the daily reset, and the mail arrives a day later saying so. Sent once per account; a reset-sync re-latch sends nothing.In the app, the Recruiter Conversations row appears on the Account Sync tab once a seat is detected, with its own interval, Sync now and Reset sync; the initial-sync gate lists the surface; the Premium block shows the seat and the session’s expiry, and says so once it has expired.
Browser ownership is derived, deletes confirm the stop, and a key is not a person
262 operations: LinkedIn 168, ID and Teams 72, Orchestration 22.browser_owner is no longer an input. POST /api/antidetect-browsers derives it from the one fact that decides it: send vendor_profile_id and the profile is yours (customer), omit it and we mint one in our own vendor account (platform). The field stays on the row and in filters, but its vocabulary is now exactly those two values; mirror_profiles is gone from the enum. Sending browser_owner on create is a validation error naming the field.vendor_profile_id is validated as what it is: a GoLogin profile id, 24 hex characters, not a profile URL or a share link. A profile that is not shared with our vendor account answers 422 vendor_profile_not_found; an owning account over its GoLogin plan answers 402 vendor_profile_plan_limit, and retrying will not help until the plan changes.Deleting a running browser now stops it first and waits for the confirm. The call can take up to ~20 s; if the node does not confirm the teardown, the delete is refused 409 with context.reason: browser_stop_unconfirmed and nothing is deleted - retry in a minute rather than assuming success.A key is not a person. GET /api/users/current (get_current_user) on an API key answers 403 with context.reason: not_a_user_actor instead of a misleading validation error about a sid field you never sent. No permission on the key changes that: the endpoint returns a signed-in person’s profile, and a key has none. The same applies to PATCH /api/users/current.A freshly connected sender starts on business hours. Its sync_config.window is seeded Monday-Friday 09:00-18:00 in the workspace timezone (UTC when the workspace has none). An empty window still means round the clock, as it always has - there is no “off” through this field.
Person languages goes GA, short links resolve, and hand-off links carry a purpose
Person languages is live. POST /api/linkedin-enrichment/person-languages answers with the profile’s Languages card (raw name plus optional proficiency strings) instead of 501. Same shape and paging as certifications and recommendations.lnkd.in short links are a first-class input. LinkedIn’s own “Copy link to post” mints https://lnkd.in/p/..., so every endpoint that takes a post link now accepts one and expands it server-side: enrichment reads, get-activity-urn-by-url, reactions, reshares. A post page URL also resolves its backend share / ugcPost urn where only the activity urn resolved before.Cloud-browser links say what they are for. generate-cloud-browser-access-key takes purpose: relogin (the default, and what every key minted before the field carries) walks the visitor through a LinkedIn sign-in whose confirm re-binds the account; share just hands the browser over, with a Done button instead of a sign-in flow. The mass-action step forwards the same argument, so a batch of links minted to be handed out must say share explicitly.used_connects is gone from access-key entries. Nothing ever incremented it, so every reader saw 0 forever. max_connects caps CONCURRENT sessions and is enforced against live cloud-browser-sessions rows at connect time. On session rows, access_key is now masked to cb_ak_******** plus the last 4 characters - enough to attribute a session to a key you hold, and nothing anyone else could redeem.Workspace capacity in one read. POST /api/antidetect-browsers/seat-usage answers the seats and 5G proxy slots the whole workspace occupies against its plan ceilings - what the plan card prints, independent of any member’s account scope.Held profiles get a schedule. A workspace holding more browser profiles than its plan allows gets a warning at day 21 of the hold and a teardown of the over-cap profiles at day 28, oldest first. Growing the plan back at any point before day 28 keeps everything.Message attachments deliver again. send-message with attachments produced handler_failed: Invalid File URL on the wire since the wire expects a full data: URL; fixed server-side, no request change needed.
One operator field on a sender, and it is now searchable
display_name is gone from the LinkedIn account. It is off the account object in every response, off PATCH /api/linkedin-accounts/{sid}, and its column is dropped. The field was meant to override the LinkedIn name wherever an account was listed, and it never did: a few screens honoured it and the rest read the synced name, so one account could answer to two names depending on which page asked. The name a sender carries is LinkedIn’s own full_name, refreshed by the account snapshot, and no endpoint edits it.The update verb now sets label, and only label. In MCP the tool update_linkedin_account is renamed set_linkedin_account_label. label is a required key rather than an optional one: send the field on every call, with a string to set it or null to clear it. An empty body is now a validation error that names the field, instead of a nothing_to_update whose cause you had to guess.label is searchable and filterable. filter.label takes eq, ne, in, nin and is_null (is_null: true finds untagged senders), and the reserved q now runs its LIKE over label alongside full_name and nickname, so “find the account of Jane Roe” and “find the one we call burner #3” are the same search. It is deliberately not a sort axis: the tag is sparse, so a page ordered by it is a page of nulls.The label is private to the workspace that wrote it. LinkedIn never sees it and no recipient ever sees it, and sharing or transferring an account hands over the account, not your note about it, so the borrowing team’s copy starts empty.
Scheduled posts, media posts, mentions, and explicit browser states
263 operations: LinkedIn 167, ID and Teams 74, Orchestration 22.Scheduled posts. POST /api/linkedin-posting/create-post takes scheduled_at (ISO 8601, in the future) and LinkedIn queues the post instead of publishing it. A scheduled share answers with post_urn only - activity_urn and url stay null until it publishes - and that post_urn is the handle the new pair takes: POST /api/linkedin-posting/get-scheduled-posts lists the account’s queue (or a company page’s, with author_organization_id), and POST /api/linkedin-posting/delete-scheduled-post removes a draft by that backend urn. A refused delete is 409 scheduled_post_not_deleted, never a success body. All of it spends the posting bucket, which grew to fit: 20 a day in series of 3 before the 1200 s pause (4 a day on the free plan).Media posts. images takes up to 20 (array order is carousel order; with two or more the service supplies the author id itself, there is no profile_id to send), or one video instead. LinkedIn does not mix the two in a share, so sending both is a 422, and the decoded bytes across all media must stay under 35 MB. These calls are synchronous: a heavy upload can outlive your client timeout while the post still publishes, so do not retry a timeout blindly - read the queue or your own posts first.Posting as a page, into a group, as a partnership. author_organization_id (the bare numeric company id) publishes as a page the account administers; group_id posts into a group and is mutually exclusive with visibility; brand_partnership: true adds LinkedIn’s label.Mentions. create-comment takes mentions as {profile_id, name} pairs, name being an exact substring of text, matched left to right in list order. create-post takes ready positions instead, {profile_id, start, length} in UTF-16 code units - the way JavaScript counts string length, so an emoji is two. Both are validated before anything is dispatched.Reactions on comments, and a new reaction. entity_urn on react and unreact now takes a post urn in any family (activity, share, ugcPost, groupPost) or a comment urn in either form, so a reaction can land on a comment, and react returns reaction_urn. reaction_type gains interested, LinkedIn’s reaction on event posts; on an ordinary post LinkedIn silently ignores it and reaction_urn comes back null, which is the tell. POST /api/linkedin-scraping/get-post-reactors accepts a comment urn in post to read a comment’s reactors.Reactor rows changed vocabulary. reaction_type on get-post-reactors rows now uses the values you write with (like, celebrate, support, love, insightful, funny, interested). Until now the rows leaked LinkedIn’s internal names (interest, praise, empathy, appreciation, entertainment) while the schema promised ours; a caller matching on the old strings must switch.Resharers. post on get-post-resharers takes a post URL, an activity urn, or a backend share / ugcPost urn. A groupPost urn is refused 422 reshare_target_unsupported: group posts have no reshare feed.Undo verbs, and recalling a message (shipped 2026-08-20). delete-post, delete-comment and unreact retract what create-post, comment and react did, spending the same bucket as the action they undo. POST /api/linkedin-messages/{sid}/delete-on-linkedin recalls one of your own messages for every participant. LinkedIn allows that only within an hour of sending, so an older message answers 409 message_too_old_to_recall with nothing dispatched; the row stays in the thread marked is_deleted with an empty body.is_deleted on message rows. Every message row carries it, and the sync keeps it true to LinkedIn: a message recalled by either side is marked on the next read of its thread, with the body emptied. Sales Navigator threads carry no recall marker on LinkedIn’s side and are left alone.Post readers. person-posts, company-posts, post-details and the post-by-url read carry group, the group a post was published in (null elsewhere). On a group post LinkedIn shows the group as the card actor, so author there is a bare name with no profile url or ids; do not read that as an organization author.Auto-scrapes. A get-post-comments source accepts sort_order in source_input (RELEVANCE, CHRONOLOGICAL, REVERSE_CHRONOLOGICAL). It is frozen at create and used on every page of every run, because LinkedIn’s pagination cursor belongs to the order it was issued under.Browser states are explicit. A call that needs a browser which is down no longer times out or hides behind a generic conflict. 503 browser_unavailable says the session is gone; 503 browser_unreachable says the service tried once to start the browser inside the call and carries the error that attempt hit plus a retry_after; 503 browser_starting says a cold start is still warming up; 409 browser_logged_out says the account itself is signed out; 409 no_live_browsers says an enrichment or scrape with no pinned account found no live browser to run on; 503 infrastructure_maintenance says the browser fleet is being updated.Sender health. POST /api/linkedin-accounts/search counters carry signed_out_count, so a logged-out sender shows without opening each row.Billing. POST /api/billing-subscriptions/{sid}/undo-cancel reverses a scheduled cancellation before the period ends.Response meta. Every response meta block now carries team_sid and actor_type, across all three services.
5G Proxy reaches the antidetect-browser surface
256 operations: LinkedIn 159, ID and Teams 73, Orchestration 22, Support 2.proxy_5g on antidetect browsers. The 5G Proxy add-on is now on every surface of the resource: the browser row carries proxy_5g, both POST /api/antidetect-browsers and POST /api/antidetect-browsers/update-proxy accept it, and POST /api/antidetect-browsers/search filters on it (proxy_5g: {eq: true} is how slot usage is counted). Arming a browser with no add-on headroom left is refused 402 insufficient_proxy_5g_slots.
Scraping and enrichment pick an executor for you
Automatic executor selection. Every scraping and enrichment method runs on your own connected accounts. Pin linkedin_account_sid and the call runs on that account only, refusing 429 bucket_saturated when its daily budget is spent. Leave it out and the service picks a connected account with remaining budget for you, skipping accounts on hold or out of capacity; Sales Navigator methods narrow the pick to accounts holding a live seat. A call with no ready account answers 422 with a message starting no_connected_accounts: (or sales_nav_required:), and a team whose accounts are all at capacity gets 429 with retry_after.
The three own-dashboard reads
259 operations: LinkedIn 159, ID and Teams 77, Orchestration 21, Support 2.Three feeds off your own LinkedIn dashboard. All three are one-shot, cursor-paginated reads on an account you connected, and none of them writes anything.
  • POST /api/linkedin-accounts/{sid}/get-my-profile-views returns who viewed the profile over the last 90 days, newest first. The window and the sort are LinkedIn’s and there is no filter. Rows carry rendered text (“Viewed 1w ago”), never a parseable date, and an anonymized viewer arrives with is_anonymous true, no member ids and a people-search url where the profile would be, so branch on that flag before you key a row.
  • POST /api/linkedin-accounts/{sid}/get-my-catch-up returns the nurture cards on My Network (birthdays, job changes and work anniversaries among the account’s connections), each with the one-click message LinkedIn printed on the button. It lists prompts and sends nothing. There is no page_size here: LinkedIn’s own request carries no count knob and the page is server-fixed at 10. Key a card by card_urn, which identifies the prompt rather than the person.
  • POST /api/linkedin-accounts/{sid}/get-my-sales-nav-notifications returns the Sales Navigator alert bell, and needs a Sales Navigator seat on the account. The feed is ordered by LinkedIn’s relevance score rather than reverse-chronologically, so sort by published_at yourself. Alerts arriving without an id are dropped, which means a page can be shorter than page_size while the feed continues: do not read a short page as the end here.
LinkedIn product and school search
260 operations: LinkedIn 160, ID and Teams 77, Orchestration 21, Support 2.LinkedIn products search. POST /api/linkedin-scraping/search-products searches LinkedIn’s product catalogue and returns product rows (slug, url, name, category line, vendor name, tagline, top features, a connections counter and artwork). Send filters with a required keywords plus any of free_version, product_category and product_company, or send a https://www.linkedin.com/search/results/products/ url the search screen produced. Exactly one of the two.The two id filters take digit strings from two different places: product_category ids come from POST /api/linkedin-scraping/param-id-lookup with type=product_category, while product_company takes ordinary LinkedIn organization ids. Nothing checks one against the other, so a category id used as a company id returns an empty page rather than an error. The identity field on a row is product_slug, a slug rather than an id, and it cannot be fed back into either filter.LinkedIn schools search. POST /api/linkedin-scraping/search-schools searches LinkedIn school pages and returns school rows (slug, url, name, location line, a students-and-alumni counter and a blurb). keywords is the whole filter vocabulary here, so every other facet the schools screen offers is reachable only by pasting a https://www.linkedin.com/search/results/schools/ url into the same endpoint. There is no school-id filter: the ids param-id-lookup returns for type=school do not fit this endpoint, and sending one is refused rather than quietly ignored.The identity field is school_slug, a slug rather than an id, and it can arrive URL encoded, so pass it through as you got it. students_alumni_count is a rounded figure parsed from the text LinkedIn printed, counting students and alumni, not employees.With these two the LinkedIn search surface is complete: people, companies, both Sales Navigator searches, service providers, posts, jobs, events, groups, courses, products and schools.
LinkedIn course and group search
259 operations: LinkedIn 159, ID and Teams 77, Orchestration 21, Support 2.LinkedIn courses search. POST /api/linkedin-scraping/search-courses searches LinkedIn Learning and returns course rows (slug, url, title, author, duration, release and viewer text, thumbnail). Two filters beyond keywords: difficulty (beginner / intermediate / advanced) and time_to_complete (under_10_mins through 3_plus_hours). Send those readable values, not LinkedIn’s own labels: the translation happens server-side. The software and subject facets are not in the filter vocabulary and are reachable by pasting a https://www.linkedin.com/search/results/learning/ url into the same endpoint.LinkedIn groups search. POST /api/linkedin-scraping/search-groups returns group rows (id, url, name, privacy, member count, description, logo). Its filter vocabulary is keywords and nothing else, which is LinkedIn’s whole surface for that vertical rather than a subset we chose; every other groups facet is reachable by pasting the UI search url into the same endpoint.
Five new LinkedIn endpoints, seven more come out of stub
253 operations, up from 248: LinkedIn 155, ID and Teams 77, Orchestration 21.Company reads that stay cheap. POST /api/linkedin-enrichment/company-lite-profile returns id, vanity, name and logo, cached 7 days. POST /api/linkedin-enrichment/company-public-identifier resolves a numeric company id to its vanity slug, cached 30 days. The pattern the two are built for: resolve the id once, then address every later company read by vanity. A /company/{slug}/ url is one request on LinkedIn’s side, while a Sales Navigator /sales/company/{id} url costs two, because the vanity has to be resolved first (measured at 433 ms against 2479 ms).A middle person read. POST /api/linkedin-enrichment/person-basic-profile sits between the lite identity stub and the full dossier: first and last name as separate fields, headline, country and display location, profile imagery, and the premium, influencer, creator and verified flags. One request, cached 24 hours. Addressed by public_identifier only, so resolve the slug first if all you hold is a URN.Services search by pasted url. The service-providers search gained a url form, so the marketplace facets the filter vocabulary does not carry are reachable by pasting a /search/results/services/ url. A url from any other search screen is refused rather than silently scraping something else.Editable account display fields. PATCH /api/linkedin-accounts/{sid} sets display_name and label. They are team-authored, never synced from LinkedIn, and an explicit null clears one.Seven more endpoints answer for real, no longer placeholders: my analytics, my SSI, edit my profile, person certifications, person comment activity, person recommendations, and service-provider search by filters.Mass actions take nine more step verbs. The browser lifecycle (run, stop, create, delete, and minting or revoking a cloud-browser access key) plus sync-config and smart-limit edits can now be plan steps.
Initial public release of the reference
The public API contract is published. 248 operations across three services: LinkedIn (150), ID and Teams (77), Orchestration (21). The reference is generated from the MCP tool registry, one endpoint per tool, and regenerates with it.The MCP server is live at mcp.gtm-api.com/mcp, exposing the same contract as 160+ typed tools across 10 toolsets. Connect guides for Claude, Cursor and Docker-based clients are in the MCP tab.Webhooks are platform-wide. Subscriptions, signed deliveries (X-Webhook-Signature, HMAC-SHA256 with a timestamp), a queryable delivery log, and a test endpoint live on the Orchestration service.Mass actions ship with a two-phase consent flow. Preview validates the whole plan and mints a 15-minute commit token; commit consumes it. Canary mode, randomized pacing and per-item retry are part of the same surface.