Facebook Pixel
CLI

Not sending the wrong thing

Rehearse writes before they happen, catch bad arguments before they leave the machine, and make destructive actions impossible to run by accident.

A wrong git push is annoying. A wrong connection request is a message from you to a real person that you cannot take back. Automation on your own LinkedIn account deserves a different standard of care than automation on your own files, and this page is about the four things the CLI does to meet it.

Rehearse the write

--dry-run resolves everything — credentials, the account id, path parameters, the query string, the request body, the final URL — and then stops. Nothing is sent.

$ connectsafely send-connection-request \
    --profile-id ada-lovelace \
    --custom-message "Loved your talk on analytical engines." \
    --dry-run
{
  "dryRun": true,
  "command": "send-connection-request",
  "method": "POST",
  "path": "/linkedin/connect",
  "query": { "accountId": "acc_1" },
  "body": {
    "profileId": "ada-lovelace",
    "customMessage": "Loved your talk on analytical engines.",
    "accountId": "acc_1"
  },
  "accountId": "acc_1",
  "url": "https://api.connectsafely.ai/linkedin/connect?accountId=acc_1"
}

This is worth building into your habits, for three reasons.

It is free. No request is made, so nothing counts against your weekly connection allowance or daily action caps. You can rehearse the same call twenty times.

It shows the account. accountId in the output is the account that would actually act. If you manage several, this is the cheapest way to be certain a script is not about to post from the wrong one.

It works on destructive commands without confirmation. You never have to arm a dangerous command in order to see what it would do.

The API key never appears in the output. You can paste a dry-run result into a ticket or a chat thread safely.

Catch bad arguments before they leave

Everything the API specification declares is checked locally first — required fields, enum values, numeric ranges, string lengths:

$ connectsafely get-latest-posts --profile-id ada --count 500
{
  "code": "above_maximum",
  "error": "--count must be <= 20 (got 500)",
  "exitCode": 3,
  "remediation": "Lower the value to at most 20. To read more rows, page through with the cursor or start parameter instead of raising the limit."
}

No request was made. The remediation names the actual fix — page, do not inflate — rather than restating the constraint.

Missing values are reported together, so one correction fixes them all:

$ connectsafely withdraw-invitation
{
  "code": "missing_required_flag",
  "error": "withdraw-invitation is missing required flag: --profile-id",
  "remediation": "Add --profile-id, then re-run. `connectsafely schema withdraw-invitation` lists every parameter with its type."
}

Identifiers get extra scrutiny

A value that lands in a URL path is the one place a wrong argument silently addresses a different resource rather than failing. Those are checked hardest:

RejectedWhy
https://linkedin.com/in/adaA URL where an id belongs — the single most common mistake
../../adminPath traversal
a%2FbAn encoded path separator
Control charactersMalformed or injected input
EmptyWould silently address the collection instead of the item

Legitimate LinkedIn identifiers pass untouched, including URNs with colons, commas and parentheses — urn:li:msg_conversation:(urn:li:fsd_profile:ABC,2-xyz==) is fine.

Unknown keys are forwarded, not dropped

There is one deliberate exception to strictness. If you pass a key through --json that the specification does not declare, the CLI forwards it untouched.

This looks lax and is the opposite. The API owns the parameter list, and it rejects an unknown filter by name with a 400. If the CLI stripped the key instead, that same call would return 200 with results the filter never touched — a search that looks filtered and is not. A loud rejection beats a quiet wrong answer.

Undeclared flags, by contrast, are always rejected: --json is an explicit "I know what I'm sending", a misspelled flag is not.

Destructive commands cannot run by accident

A small set of commands remove or retract something a person can see — currently withdraw-invitation and conversations-delete-message. Without a terminal, they refuse:

$ connectsafely withdraw-invitation --profile-id ada
{
  "code": "confirmation_required",
  "error": "withdraw-invitation removes something and was run without a terminal",
  "exitCode": 4,
  "remediation": "Re-run the exact same command with --yes once you have confirmed the intent. Use --dry-run first to see precisely what would be affected."
}

The CLI refuses rather than prompts. A prompt in a script, a CI job or an agent sandbox either hangs forever waiting for input that will never arrive, or is answered by whatever byte happens to be on stdin. Neither is a confirmation. Exit code 4 is one, because it forces the decision back to whoever wrote the command.

At an interactive terminal these commands run normally — you are already the confirmation.

To find out what falls into this category:

connectsafely commands --kind write     # everything that changes your account
connectsafely schema withdraw-invitation | jq '.destructive'

Retries never duplicate an action

A failed write is not automatically retried. The CLI cannot know whether a request that timed out actually landed on LinkedIn, and a second connection request to the same person is a real-world mistake, not a wasted API call.

Writes are retried in exactly two situations:

  1. The server said it did nothing — a 429 or a 503. There is no action to duplicate.
  2. You supplied --idempotency-key — the API returns the first result for a repeated key, so the retry is safe by construction.
connectsafely send-connection-request \
  --profile-id ada-lovelace \
  --idempotency-key "invite-ada-2026-08-28"

Use a key that is stable for the intent, not the attempt. A key derived from the profile and the campaign means a re-run of the same script does not send twice.

Reads are retried freely — there is nothing to duplicate.

Putting it together

The pattern worth adopting, for a person and an agent alike:

# 1. what does this command take?
connectsafely schema send-connection-request

# 2. what exactly would it send?
connectsafely send-connection-request --json @invite.json --dry-run

# 3. send it, once, safely
connectsafely send-connection-request --json @invite.json \
  --idempotency-key "invite-ada-2026-08-28"

On this page