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 installInstall
npm install -g @connectsafely/cliNode 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_herePrefer 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_123That writes ~/.connectsafely/config.json with owner-only (0600) permissions.
Check what is actually in effect:
connectsafely auth statusIt reports not just whether you are authenticated but where each value came
from — flag, 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-runThat 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 logoutA 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.
| Flag | Description |
|---|---|
--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-run | Resolve the whole request and print it. Send nothing |
--yes | Confirm 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 / --verbose | Silence 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 abatchfile.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
| Code | Meaning | What to do |
|---|---|---|
0 | Success | Continue |
1 | General or unexpected error | Read the envelope; do not retry blind |
2 | Auth — key missing, wrong, revoked, or not entitled | Stop. Retrying will not help |
3 | Validation — rejected before or by the API | Fix the named flag and retry |
4 | Confirmation required — destructive, no terminal, no --yes | Confirm intent, re-run with --yes |
5 | Rate limit or daily action cap | Stop this action for now. Not transient |
6 | Network — never reached the API | Retry a read; verify before retrying a write |
7 | Unknown command | Run connectsafely commands. Do not guess |
8 | Conflict — already sent, already exists | Re-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,--helpandskillneed 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.
--fieldsand--max-itemsshrink the response inside the CLI process, so what you did not ask for is never read or paid for. - Rehearse every write.
--dry-runresolves 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-keymakes the repeat safe. Reads are retried freely. - Batches are paced.
batchspaces 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
4until 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
Command reference
All 87 commands with flags, types and examples.
Agent skills
The eight skills that teach an AI agent to use this CLI well.
CLI or MCP server?
What each is better at, and how to migrate without rewriting.
Recipes
Sourcing, outreach, inbox triage and reporting, end to end.
Cutting context cost
Why this costs an agent a fraction of a tool server.
Sending at a human rhythm
Batch pacing, and why rhythm matters more than volume.
