Connect
The connect commands let you send connection invitations, list pending invitations, and accept or decline received requests. All commands are account-scoped and require --account (or a default set via curviate config set-account).
Before you start. You need an API key and a connected LinkedIn account. Run curviate account list to read your acc_... ids; if it returns an empty list, connect an account first (Authentication & Accounts). Without one, every command here exits 4 with ACCOUNT_NOT_FOUND.
Invitations are visible to a real person. Use
--previewto inspect a send before it goes out. A repeated send to the same member returnsCONNECTION_REQUEST_CONFLICT(409, exit 8); never retry it in a loop, and never send-then-cancel-then-resend, which is the pattern that gets an account restricted.
Each command maps to a single method on the invites resource of the @curviate/sdk:
| CLI command | SDK method |
|---|---|
curviate connect <id> | invites.send() |
curviate connect sent | invites.listSent() |
curviate connect received | invites.listReceived() |
curviate connect accept <id> | invites.accept() |
curviate connect decline <id> | invites.decline() |
curviate connect cancel <id> | invites.cancel() |
The CLI is a thin wrapper: it resolves identifiers, applies a slim output projection (see below), and forwards to the SDK. Reach for the SDK directly when you need the full, unprojected response shape in code.
Changed in 0.15.0:
connect respond <invitation_id> --action accept|decline --shared-secret <token>is removed. It is replaced by two bodyless commands,connect accept <id>andconnect decline <id>, no--shared-secretis required anymore.
Identifiers
The <id> argument for curviate connect <id> accepts the recipient's LinkedIn profile URL, public slug, or provider_id (the ACoAAA... value returned by curviate profile). URL and slug are the most common forms; a provider_id is accepted directly, so you can pipe it straight from a profile lookup without an intermediate slug.
A LinkedIn URN (urn:li:member:N) is also accepted, but the numeric member ID N is not exposed by any Curviate endpoint, so unless you already have a URN from an external source, prefer a slug or provider_id.
Invitation IDs (<id>) used in accept, decline, and cancel are verbatim identifiers returned by connect received / connect sent. They are passed as-is, they are not LinkedIn URLs or slugs and are not normalized.
Commands
# Send a connection invitation (write, --preview accepted)
# <id> accepts a LinkedIn URL, slug, or provider_id (ACoAAA... from `curviate profile`)
curviate connect <id> [--note <text>]--note is the personalized message shown to the recipient alongside the connection request (<=300 characters, a LinkedIn cap). Omit it to send a generic note; personalized notes measurably increase acceptance rates.
# List sent invitations (paginated read)
curviate connect sent [--verbose] [--limit <n>] [--cursor <token>] [--all] [--max-pages <n>]# List received invitations (paginated read)
curviate connect received [--verbose] [--limit <n>] [--cursor <token>] [--all] [--max-pages <n>]# Accept a received invitation (write, bodyless, --preview accepted)
# <id> is the `id` field from `connect received`, NOT a LinkedIn URL or slug
curviate connect accept <id># Decline a received invitation (write, bodyless, --preview accepted)
# <id> is the `id` field from `connect received`, NOT a LinkedIn URL or slug
curviate connect decline <id># Cancel a sent invitation (write, --preview accepted)
# <id> is the `id` field from `connect sent` (SENT_...), NOT a LinkedIn URL or slug
curviate connect cancel <id>The write commands (connect <id>, connect accept, connect decline, connect cancel) do not accept the pagination flags (--limit, --cursor, --all, --max-pages); those apply only to the sent and received list reads.
Response shape
Both list reads return an item with the same top-level keys. The other party lives in
a nested user object; there are no flat invited_user* or inviter* fields.
connect sent (object: "invitation_sent"):
{
"object": "invitation_sent",
"id": "SENT_1234567890",
"created_at": "2026-07-14T09:12:00.000Z",
"message": "Hi Jane, I'd love to connect.",
"user": {
"id": "urn:li:member:123456789",
"type": "member",
"display_name": "Jane Smith",
"first_name": "Jane",
"last_name": "Smith",
"public_picture_url": "https://media.licdn.com/..."
}
}connect received (object: "invitation_received") carries the same keys minus
message, and its user is richer, adding public_identifier, profile_url, and
description (the headline). That asymmetry is the reason to qualify inbound
invitations directly from the list and outbound ones with a follow-up profile read.
id is direction-prefixed (SENT_... or RECEIVED_...) and is what connect accept,
connect decline, and connect cancel take. created_at is a real ISO-8601 UTC
timestamp, and it is optional: it is omitted when LinkedIn does not supply one, so
handle its absence rather than assuming a value.
Use --fields to project the response down, for example
--fields items.id,items.user.display_name.
Semantics and known limitations
- Pending only.
connect sentandconnect receivedreturn only pending invitations. Accepted and declined invitations are not returned (a LinkedIn API limitation), so an empty list means "no pending invitations", not "none ever sent". created_atmay be absent. It is a genuine ISO-8601 timestamp when LinkedIn supplies one, and omitted otherwise. Treat a missing value as unknown rather than defaulting it.- No total count. The list response carries no total. To count all pending sent invitations, stream them and count client-side:
curviate connect sent --all | grep -c invitation_sent. - Propagation delay. After a successful send, the invitation typically takes 10-30 seconds to appear in the recipient's
connect receivedlist (LinkedIn propagation). Poll with a brief backoff before reading the received list.
Examples
Send an invitation
# By LinkedIn URL
curviate connect https://linkedin.com/in/janesmith --account acc_YOUR_ACCOUNT_ID
# By provider_id straight from a profile lookup
curviate connect ACoAAA... --note "Hi Jane, I'd love to connect." --account acc_YOUR_ACCOUNT_ID
# Preview the request without sending
curviate connect janesmith --preview --account acc_YOUR_ACCOUNT_IDList and respond to received invitations
# Step 1: list received invitations and capture the id
curviate connect received --json --account acc_YOUR_ACCOUNT_ID
# Step 2: accept, use the `id` from the response above
curviate connect accept INVITATION_ID --account acc_YOUR_ACCOUNT_ID
# Or decline
curviate connect decline INVITATION_ID --account acc_YOUR_ACCOUNT_IDLook up who is pending, then follow up or cancel
# Sent items carry a sparse user object (no headline, no slug)
curviate connect sent --json --fields items.id,items.user.display_name --account acc_YOUR_ACCOUNT_ID
# Resolve a pending contact's full profile from the member urn in user.id
curviate profile <user.id> --account acc_YOUR_ACCOUNT_ID
# Cancel a stale sent invitation using its id
curviate connect cancel INVITATION_ID --account acc_YOUR_ACCOUNT_IDErrors you may hit
| Code | Exit | Cause | Fix |
|---|---|---|---|
ACCOUNT_NOT_FOUND | 4 | --account names an id this API key does not own. | Run curviate account list. |
RESOURCE_NOT_FOUND | 4 | The invitation id does not exist, or is no longer pending. | Re-read it from connect sent or connect received. |
CONNECTION_REQUEST_CONFLICT | 8 | A request to this member is already pending, or you are already connected. | Do not retry. Check connect sent first. |
RECIPIENT_UNREACHABLE | 10 | The member cannot receive an invitation from this account. | Nothing to retry. |
ACCOUNT_RESTRICTED | 8 | LinkedIn is restricting this account from inviting. | Pause outreach on this account. |
RATE_LIMIT_ACCOUNT | 6 | Too many actions on this LinkedIn account in the window. | Back off. See rate limits. |
Full envelope shapes are in the error reference.