Facebook Pixel
CLI

Getting started

Install the ConnectSafely CLI and run your own LinkedIn account from a terminal, a CI job or an AI agent — no server to host, no protocol to speak.

AI-agent-friendly CLI for controlling your own LinkedIn account and retrieving real-time data. 87 commands, one binary, no server to host.

Built for Claude Code, Cursor, Codex and any AI agent that can run a shell command — and it ships the skills those agents need, so they do not have to learn the tool from --help.

Quick start

# 1. Install
npm install -g @connectsafely/cli

# 2. Authenticate (get a key at https://connectsafely.ai/api-key)
export CONNECTSAFELY_API_KEY=your_key_here

# 3. Pick the LinkedIn account to act as
connectsafely list-linkedin-accounts
export CONNECTSAFELY_ACCOUNT_ID=your_account_id

# 4. Read something
connectsafely get-connections --limit 5

# 5. Teach your AI agent to use it
connectsafely skill install

Install

npm install -g @connectsafely/cli

Node 20 or newer. The binary is connectsafely, with csly as a short alias. It has no runtime dependencies, and the API specification is bundled inside it — so commands, schema, --help and skill all work offline and before any key exists.

Setup

Authenticate with an environment variable:

export CONNECTSAFELY_API_KEY=your_key_here

Prefer this over a stored file. It is what works unchanged in CI, in a container and inside an agent sandbox, where an interactive login cannot happen.

To store the key instead:

connectsafely auth login --api-key your_key_here
connectsafely auth login --api-key your_key_here --account acc_123

That writes ~/.connectsafely/config.json with owner-only (0600) permissions.

Check what is actually in effect:

connectsafely auth status

It reports not just whether you are authenticated but where each value came fromflag, env or config. Precedence is flag → environment variable → config file.

Multiple accounts

The CLI acts as one LinkedIn account at a time. Set a default, or name one per command:

# List the accounts your key can act as (searchable)
connectsafely list-linkedin-accounts
connectsafely list-linkedin-accounts --q "acme"

# Set the default for the session
export CONNECTSAFELY_ACCOUNT_ID=acc_123

# Or store it
connectsafely auth login --api-key your_key_here --account acc_123

# Override for one command
connectsafely get-connections --account acc_other

# Confirm which account would act, without sending anything
connectsafely send-connection-request --profile-id ada --dry-run

That last line is the fastest way to settle a "it used the wrong account" question: the dry run prints the accountId that would really be used.

Updating your key

Regenerating a key on the dashboard invalidates the old one. To pick up the new one:

# Environment variable — just re-export
export CONNECTSAFELY_API_KEY=new_key_here

# Stored config — overwrite it
connectsafely auth login --api-key new_key_here

# Remove a stored key entirely (other settings are kept)
connectsafely auth logout

A revoked key shows up as exit code 2 with code: unauthenticated. Do not retry it — a wrong key does not become a right one.

How it works

The CLI is a thin, careful client for the ConnectSafely REST API. Each invocation is one process: it resolves credentials, validates your arguments locally, makes the HTTP request, prunes the response and exits.

Commands are generated from the same OpenAPI specification that produces the API reference and the MCP server — and each command name is identical to the matching MCP tool name, so prompts and runbooks written against that server work here unchanged.

Rate limits, daily action caps and plan entitlements are enforced server-side and are identical across all three surfaces. The CLI adds the layer around the call: discovery, validation, response pruning, write pacing and rehearsal.

Global flags

Every command accepts these.

FlagDescription
--json <json|@file|->Send the whole request payload as JSON, from a string, a file or stdin
--fields <mask>Keep only these response fields, e.g. people.firstName,people.headline
--max-items <n>Cap every array in the response at n entries
--output <mode>json, pretty, ndjson or markdown
--dry-runResolve the whole request and print it. Send nothing
--yesConfirm a destructive command in a non-interactive shell
--account <id>LinkedIn account to act as
--api-key <key>API key, if you are not using the environment variable
--idempotency-key <k>Make a retried write safe — a repeated key returns the first result
--retries <n> / --timeout <ms>Attempts (default 3) and per-attempt timeout (default 60000)
--quiet / --verboseSilence stderr, or show retry decisions on it

--pace, --no-pace and --stop-on-error apply to batch. The full list, with environment variables and argument syntax, is in the reference.

Output format

JSON is the default whenever stdout is not a terminal — a pipe, a CI job, or a recognised agent runtime. A human at a terminal gets aligned tables instead. Neither side has to ask.

$ connectsafely get-connections --limit 2 --fields 'connections.firstName,connections.headline'
{
  "success": true,
  "connections": [
    { "firstName": "John", "headline": "CEO | Entrepreneur | Tech Founder" },
    { "firstName": "Jane", "headline": "VP of Engineering" }
  ]
}

Four modes, chosen with --output or CONNECTSAFELY_OUTPUT:

  • json — indented and complete. The default for machines.
  • pretty — aligned columns. The default for a terminal.
  • ndjson — one row per line. The cheapest way to feed a script or a batch file.
  • markdown — a table. Language models read it natively and it costs fewer tokens than JSON.

Data goes to stdout; everything else goes to stderr. Errors are JSON on stderr in every mode, with a stable code and an actionable remediation:

{
  "ok": false,
  "error": "Daily connection limit reached",
  "code": "rate_limited",
  "exitCode": 5,
  "exitName": "rate_limit",
  "remediation": "A daily action cap or pacing limit was reached. Stop sending this action today and resume tomorrow; retrying now will not succeed and risks the account."
}

Match on code. It is stable; the error text comes from the API and may be reworded.

Exit codes

CodeMeaningWhat to do
0SuccessContinue
1General or unexpected errorRead the envelope; do not retry blind
2Auth — key missing, wrong, revoked, or not entitledStop. Retrying will not help
3Validation — rejected before or by the APIFix the named flag and retry
4Confirmation required — destructive, no terminal, no --yesConfirm intent, re-run with --yes
5Rate limit or daily action capStop this action for now. Not transient
6Network — never reached the APIRetry a read; verify before retrying a write
7Unknown commandRun connectsafely commands. Do not guess
8Conflict — already sent, already existsRe-read state before retrying

These are a public contract. New codes may be added; an existing one will never be repurposed.

Important notes

  • Discovery costs nothing. commands, schema, --help and skill need no API key and no network. An agent can plan its work before credentials exist.
  • Unknown commands and flags fail hard. There is no "did you mean…?" and no autocorrect. A helpful guess is advice to a person and an instruction to an agent.
  • Prune every read. --fields and --max-items shrink the response inside the CLI process, so what you did not ask for is never read or paid for.
  • Rehearse every write. --dry-run resolves auth, ids, URL and body and sends nothing. It costs nothing against your caps and works on destructive commands without --yes.
  • Writes are not blindly retried. A write that timed out may have landed. It is retried only when the server said it did nothing (429, 503) or an --idempotency-key makes the repeat safe. Reads are retried freely.
  • Batches are paced. batch spaces writes 30–90 randomised seconds apart. LinkedIn flags accounts on rhythm as much as on volume.
  • Destructive commands refuse rather than prompt. Off a terminal they exit 4 until you pass --yes. A prompt in a script either hangs forever or is answered by whatever is on stdin.
  • Server-side limits still apply. Connection requests are capped at 90 per account per week (resets Monday 00:00 UTC); people search at 300 per account per month. Check with connectsafely get-account-quota.
  • Timestamps are UTC, and identifiers should be passed exactly as the API returned them — the CLI rejects a full URL where an id belongs.

Next

On this page