Account

The account commands manage LinkedIn account connections: linking new accounts, re-authenticating an existing one in place, resolving the verification checkpoints LinkedIn issues during sign-in, and managing already-connected accounts. All account commands are root-scoped: they operate on your workspace and do not require --account.

Before you start. You need an API key (CURVIATE_API_KEY, or curviate login). This is the one command group that does not require an already-connected LinkedIn account; it is how you get one. Every other command group does, so start here.

Changed in 0.15.0: account connect-link, account reconnect-link, and account reconnect are removed; the hosted-link flow has no v2 endpoint. In-place re-authorization now lives on account link itself, behind --account-id (see Reconnecting an account). account connect-session poll is retained, but its --session value changed meaning: it is now the account_id returned by account link's checkpoint response (a plain status poll), not a separate hosted-link session id.

account list and get

# List all connected accounts (paginated)
curviate account list [--limit <n>] [--cursor <token>] [--all] [--max-pages <n>]
# Get a single account by ID (verbatim account ID, not a LinkedIn identifier)
curviate account get <account_id>

account get returns the account's current status, its quotas (one entry per tracked quota family, with remaining/total/recommended_throttle_hint), and, with --verbose, the cached enrichment fields (username, premium_id, public_identifier, signatures, groups). Those five enrichment fields are best-effort: for accounts connected going forward they may be null or empty, so treat them as optional. full_name and substrate_created_at remain populated.

Account status is one of active, reconnect_needed, restricted, connecting, or disconnected.

account link

account link does two jobs, decided by whether you pass --account-id:

  • omit --account-id: connect a new LinkedIn account into an empty seat.
  • pass --account-id <acc_...>: re-authenticate that existing account in place, keeping its id, its seat, its webhooks, and its history. See Reconnecting an account.

--seat-id and --auth-method are always required by the CLI. On a reconnect the server ignores --seat-id (the account keeps the seat it already holds), but the CLI still asks for it, so pass the account's current seat.

Where to get a seat ID. Open Billing (/billing) in the dashboard and look at the seat table. Every seat you own is listed under Seat ID as a seat_... chip; click a chip to copy the full ID. For a new connect, pick a seat whose Attached account column reads Empty; if none does, use Add Seats on the same page first.

# Link a new LinkedIn account (write)
curviate account link \
  --seat-id <seat_id> \
  --auth-method <credentials|cookie> \
  [--account-id <acc_...>] \
  [--email <email>] \
  [--password <password>] \
  [--password-stdin] \
  [--li-at <cookie>] \
  [--li-at-stdin] \
  [--li-a <cookie>] \
  [--user-agent <ua>] \
  [--country <code>] \
  [--ip <address>] \
  [--proxy-protocol <http|https|socks5>] \
  [--proxy-host <host>] \
  [--proxy-port <port>] \
  [--proxy-username <user>] \
  [--proxy-password <pass>] \
  [--recruiter-contract-id <id>] \
  [--no-interactive]

Cookie auth requires --user-agent. When --auth-method cookie, pass the exact browser User-Agent the li_at cookie was captured with; cookie sessions are pinned to it. Omitting --user-agent on a cookie link fails with INVALID_REQUEST. --user-agent stays optional for credentials auth.

Prefer --password-stdin / --li-at-stdin (or the CURVIATE_LINKEDIN_PASSWORD / CURVIATE_LINKEDIN_LI_AT environment variables) over passing a secret on the command line, where it is visible to other processes and saved in shell history.

On success the command prints the connected account:

{ "object": "account", "account_id": "acc_...", "status": "active" }

If LinkedIn issues a verification challenge, the response is a checkpoint instead of an account:

{
  "object": "checkpoint",
  "status": "checkpoint_required",
  "account_id": "acc_...",
  "challenge_type": "two_factor_app",
  "expires_at": "2026-06-08T10:10:00Z"
}

On an interactive terminal, account link then guides you through the checkpoint in-process, prompting for the code or waiting out a mobile-app approval, and exits once the account connects. Pass --no-interactive to always render the checkpoint envelope and exit instead of prompting, even on a TTY. On a non-interactive session it prints the checkpoint envelope and exits 12 so a script can resolve it later with the checkpoint commands below. The account_id in the checkpoint is the handle you pass to those commands.

If the account is already connected, account link returns ACCOUNT_ALREADY_LINKED (a LinkedIn identity maps to one account per workspace). When your workspace already owns that account, the error names your own account_id. A checkpoint resolution that turns out to reactivate an account you had previously disconnected returns that account with "recovered": true.

account connect-session poll

Poll the status of an in-progress connect. It is a pure status read that makes no external call and does not itself resolve a checkpoint. --session takes the account_id from account link's checkpoint (202) response, not a separately-minted session id.

# Single poll (prints the session status, exits 0 regardless of status)
curviate account connect-session poll --session <account_id>
 
# Block until the session resolves (or the wait window elapses)
curviate account connect-session poll --session <account_id> --wait [--timeout <ms>]

status is checkpoint_required, done, expired, or failed; account_id is populated only once status is done. Use this as a lightweight way to check on a link attempt without solving the checkpoint yourself. To actually resolve a pending checkpoint, use the checkpoint commands below.

account update and disconnect

# Update an account's metadata / proxy configuration (write, --preview accepted)
curviate account update <account_id> \
  [--metadata <json>] \
  [--proxy-protocol <http|https|socks5>] \
  [--proxy-host <host>] \
  [--proxy-port <port>] \
  [--proxy-username <user>] \
  [--proxy-password <pass>] \
  [--clear-proxy]

--metadata sets a flat key/value map of your own tags on the account and replaces the stored map wholesale (keys you omit are removed); pass it as a JSON object, e.g. --metadata '{"team":"growth","region":"eu"}'. The --proxy-* flags set a custom proxy; --clear-proxy removes a custom proxy and reverts to automatic proxy protection (mutually exclusive with --proxy-host). A managed location is set only at connect time (on account link); account update does not accept --country or --ip.

# Permanently disconnect an account (write, --preview accepted)
curviate account disconnect <account_id>

Checkpoint flow

When account link returns a checkpoint_required status, LinkedIn is waiting for a verification code or a mobile-app approval. The checkpoint commands are account-scoped: pass the account_id from the 202 response as a positional argument:

# Solve a code-based checkpoint (OTP / 2FA) (write)
curviate account checkpoint solve <account_id> --code <code>
# Re-request the verification notification (e.g. the user never got the code) (write)
curviate account checkpoint request <account_id>
# Poll a mobile-app-approval checkpoint (write)
curviate account checkpoint poll <account_id> [--wait] [--timeout <ms>]
  • solve submits a code and, on success, returns the connected account. A chained challenge (for example OTP then 2FA) returns another checkpoint_required; solve the next code at the same account. A wrong code returns CHECKPOINT_INVALID_CODE.
  • request re-issues the challenge notification. Its resent field is honest: resent: false means there was nothing to re-send (for example a two_factor_app code is generated on the device), not a failure. It does not extend the checkpoint's expiry.
  • poll is for mobile_app_approval checkpoints, where the user approves the sign-in on their phone. While pending it returns { "object": "checkpoint", "status": "pending", "expires_at": "..." }; once approved it returns the connected account. Add --wait to block until it resolves. A code-based checkpoint returns 422 here, so use solve instead.

The mobile-approval window is short (a few minutes; poll until the returned expires_at). If it elapses before the user approves, the checkpoint reaches a terminal expired state with a recovery_hint explaining the next step:

{
  "object": "checkpoint",
  "status": "expired",
  "challenge_type": "mobile_app_approval",
  "recovery_hint": "This sign-in wasn't completed in time. If you approved it in the LinkedIn app, this identity may already be connected; try Reconnect, sign in with the cookie method, or check your connected accounts."
}

Reconnecting an account

When an account's status turns reconnect_needed or disconnected, LinkedIn has invalidated the stored session. Re-authenticate it in place with account link --account-id. Do not disconnect and re-link: that destroys the account record, releases its seat, and orphans anything bound to the old id.

# 1. Find the account that needs attention
curviate account list --json --fields items.account_id,items.status
# => {"account_id":"acc_01J8Z3K9P0Q1R2S3T4V5W6X7Y8","status":"reconnect_needed"}
 
# 2. Read its current seat (the CLI still requires --seat-id; the API ignores it on a reconnect)
curviate account get acc_01J8Z3K9P0Q1R2S3T4V5W6X7Y8 --json
 
# 3. Re-authenticate that same account id in place
curviate account link \
  --account-id acc_01J8Z3K9P0Q1R2S3T4V5W6X7Y8 \
  --seat-id seat_abc123 \
  --auth-method cookie \
  --li-at-stdin <<< "$LI_AT" \
  --user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
# => { "object": "account", "account_id": "acc_01J8Z3K9P0Q1R2S3T4V5W6X7Y8", "status": "active" }

A reconnect can raise a checkpoint just like a fresh link. If it does, resolve it with the same account checkpoint commands above, using the account_id from the response.

The account keeps its id, its seat, its metadata, and its webhook subscriptions. Nothing downstream has to change.

--account-id is what makes it a reconnect. Omit it and the same command opens a brand-new connection into --seat-id instead, which is not what you want for an account you already have. If the identity you are linking is already connected to this workspace, account link returns ACCOUNT_ALREADY_LINKED naming your existing account_id; pass that id back as --account-id to reconnect it. A checkpoint resolution that reactivates an account you had previously disconnected returns "recovered": true.

Challenge types

The challenge_type on a 202 checkpoint is one of otp, two_factor_sms, two_factor_app, two_factor_whatsapp, mobile_app_approval, otp_or_mobile_app_approval (the user may either enter a code or approve in the app), or contract_selection (a Recruiter or Sales Navigator seat picker; the response also carries contracts with { id, name } entries, and you solve with the chosen id). Two challenges cannot be resolved automatically, a CAPTCHA and a phone-number registration; those return 422 with a machine-readable challenge_type of captcha or phone_register.

Examples

List connected accounts

curviate account list

Link an account with credentials

curviate account link \
  --seat-id seat_abc123 \
  --auth-method credentials \
  --email user@example.com \
  --password-stdin <<< "$LINKEDIN_PASSWORD"

Link an account with a cookie

# Cookie auth requires --user-agent (the browser the li_at was captured with)
curviate account link \
  --seat-id seat_abc123 \
  --auth-method cookie \
  --li-at-stdin <<< "$LI_AT" \
  --user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"

Complete an OTP checkpoint manually

# 1. A non-interactive link returns a checkpoint and the account_id to resolve it
curviate account link --seat-id seat_abc123 --auth-method credentials \
  --email user@example.com --password-stdin <<< "$LINKEDIN_PASSWORD" --no-interactive
# => { "object": "checkpoint", "status": "checkpoint_required", "account_id": "acc_X", "challenge_type": "otp", "expires_at": "..." }
 
# 2. Solve the code from the user's email or authenticator app
curviate account checkpoint solve acc_X --code 123456
# => { "object": "account", "account_id": "acc_X", "status": "active" }
 
# 3. If the user never received the code, re-request the notification
curviate account checkpoint request acc_X

Wait out a mobile-app-approval checkpoint

# The user approves the sign-in on their phone; --wait blocks until it resolves
curviate account checkpoint poll acc_X --wait

Poll a link attempt's status without solving it

curviate account connect-session poll --session acc_X --wait

Preview a disconnect

# See what would be sent without actually disconnecting
curviate account disconnect acc_YOUR_ACCOUNT_ID --preview

Errors you may hit

CodeExitCauseFix
UNAUTHORIZED3Missing or invalid API key.Set CURVIATE_API_KEY, or run curviate login.
ACCOUNT_NOT_FOUND4The acc_... id is not owned by this API key.Re-read it from curviate account list.
SEAT_NOT_FOUND4The --seat-id does not exist, or is not yours.Copy it from the seat table on the dashboard's Billing page.
ACCOUNT_ALREADY_LINKED8That LinkedIn identity is already connected to this workspace.Pass the id it names as --account-id to reconnect it instead.
CONNECTION_IN_PROGRESS8A connection attempt for this LinkedIn account is already open.Wait for it to finish or expire before starting another.
CHECKPOINT_INVALID_CODE9Wrong verification code.Re-read the code, or run account checkpoint request <account_id>.
CHECKPOINT_EXPIRED9The checkpoint window elapsed.Start the link again.
PAYMENT_REQUIRED11No paid or trial seat is available.Use Add Seats on the dashboard's Billing page.
INVALID_REQUEST2A missing or malformed argument, most often --user-agent on a cookie link.Read the message on stderr, it names the field.

Full envelope shapes are in the error reference.

Next steps

  • Messaging: read an inbox and send a message with the account you just connected.
  • Profile: look up a member by URL or slug.
  • Search: find people, companies, and jobs.
  • Errors: the full error-code list and what to retry.
COMPANY · LEGAL

Privacy Policy

Redmer Holding GmbHLast updated August 4, 2026

Who we are

Curviate is operated by Redmer Holding GmbH ("Curviate", "we", "us"), a German GmbH registered at Amtsgericht Bonn, HRB 29957, registered address Hostertstraße 16, 53332 Bornheim, Germany. Full company details are on our Imprint. We haven't appointed a statutory Data Protection Officer, since our processing doesn't reach the scale or sensitivity that requires one. Privacy questions go to privacy@curviate.com.

The two roles we play

When you create an account and use Curviate, we process your own data (identity, billing, API keys, connector authorizations). For that data, we are the controller.

When you use Curviate to act on your own connected LinkedIn account, viewing profiles, sending messages, managing engagement, that content and those contacts belong to that account and its people. You are the controller of that data; we are the processor, acting only on your instructions, under a Data Processing Agreement available on request (see below). If one of your contacts has a question about being reached through Curviate, you're who they should contact first; email privacy@curviate.com if you need help routing it.

What we collect, and why

DataWhy
Account identity (name, email, sign-in method)Create and secure your account
Your LinkedIn credentialsOperate the actions you request
LinkedIn content returned by an API callFulfil that specific request, nothing more
API keys and connector (OAuth) authorizationsAuthenticate your API, CLI, MCP, or SDK requests
Billing detailsCharge you correctly and meet our tax obligations
Usage and security logsKeep the service reliable and abuse-free
Support messagesRespond to you
Website analytics, only if you opt inUnderstand how the site is used

We rely on our contract with you, our legitimate interest in running and securing the service, our legal obligations (tax law, for example), and, for analytics, your consent. We never sell your data or use it to train models.

Where it's processed, and who else touches it

Our infrastructure runs in the EU. Hosting: Railway. Database and auth: Supabase, Ireland. Email: Resend. Payments: Stripe. Network security: a DDoS-protection provider sits in front of our app and never sees or stores request content. LinkedIn connectivity: a third-party infrastructure provider that lets us execute LinkedIn actions on your behalf. Error tracking: Sentry, Frankfurt. Product analytics: PostHog, Frankfurt. Uptime monitoring: Better Stack.

We give the current, named list of every provider above to any customer who asks: security@curviate.com.

Data processing agreement

A data processing agreement under Article 28 of the GDPR is available to business customers on request. Email security@curviate.com and we will send you the current version.

Outside the EU

All customer LinkedIn data, account data, and telemetry are processed and stored exclusively in EU regions of our sub-processors. A few providers we rely on (Stripe and Sentry, for example) are headquartered outside the EU/EEA; where that applies, it's covered by their own GDPR safeguards, typically the EU Standard Contractual Clauses.

How long we keep it

DataRetention
Account and workspace dataWhile your account is active
Closed accountDeleted immediately and irreversibly; see Deleting your account below
LinkedIn credentialsUntil you disconnect that account
LinkedIn contentNot stored; any transient cache clears within 1 hour, never indexed, never used for training
API keysUntil you revoke or rotate them
Connector (OAuth) authorizationsAccess token ~1 hour; refresh token up to ~12 months, or until you revoke it, whichever comes first
Billing recordsAs required by German tax law, currently up to 10 years
LogsA short operational window; metadata only, never message content

The 12-month figure above is a server-side credential for a connected AI agent or app. It is not a cookie and doesn't touch your browser session; see Cookies below for that. You can see and revoke every connector from Authorized applications in your dashboard at any time.

Cookies

We keep cookies to a minimum, and ask before anything beyond the essentials runs.

Strictly necessary, no consent needed:

NamePurposeExpiry
cc_cookieRemembers your cookie choice12 months
curviate-themeRemembers light/dark mode (local storage, not a cookie)Persistent
sb-*-auth-tokenKeeps you signed inWhile active; cleared on sign-out

Analytics, only if you accept:

NamePurposeExpiry
_gaGoogle Analytics: distinguishes visitors2 years
_gidGoogle Analytics: distinguishes visitors24 hours
_ga_<container id>Google Analytics: persists session state2 years

No advertising cookies, ever. Accept and reject are equally easy, and you can change your mind any time via Cookie Preferences in the footer; we won't ask again for 12 months unless something material changes. Our LinkedIn connect flow and OAuth authorization screen never set anything beyond the essentials, so no banner appears there.

Connecting an AI agent or app

Curviate is built for AI agents and automated clients as much as for people. If you connect an app like Claude, or your own code, via an API key or an OAuth connector, it can act on your workspace within the access you gave it. What it does with anything it receives back, including what it sends to its own AI model, is between you and that provider; review its practices before connecting it. Review and revoke any connection any time from your dashboard.

Deleting your account

You can delete your account yourself, from Settings in your dashboard. It takes effect immediately and it cannot be undone. There is no grace period and nothing to restore afterwards, so export anything you want to keep before you start.

Deleting removes your sign-in identity, which frees your email address for reuse straight away, along with your profile, your workspace membership and settings, your API keys, and your seats. For any connected LinkedIn account, we instruct our infrastructure provider to delete it, and your access ends immediately. Records of the connection itself can remain in our systems; email privacy@curviate.com if you need those removed as well. LinkedIn content was never stored in the first place, so there is none of it to delete.

A few things are kept on purpose. We would rather name them than claim a clean sweep:

  • Billing records, for as long as German tax law requires. They hold plan, seat count, amount, and payment references; no name, no email, no LinkedIn data.
  • A record that the deletion happened, so we can show you or a regulator that we did it.
  • A security log of which requests were made, kept for 90 days and then removed automatically. It records that a request happened, never what was in it.
  • A one-way fingerprint, if you used a free trial, that lets us recognise a repeat trial. It holds no readable identifier and cannot be read back into your name, your email, or your LinkedIn profile.

Internal workspace identifiers can also remain in operational records such as queue entries and rate-limit counters. Those carry no name, no email, and no content. If you want to know exactly what is left for your own account, ask us at privacy@curviate.com.

Your rights

You can access, correct, delete, restrict, or object to your data, port it elsewhere, and withdraw consent at any time: email privacy@curviate.com. A copy of your data in a machine-readable format is available on request. We don't make automated decisions about you that have a legal or similarly significant effect. You can also complain to a supervisory authority; ours is the Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen (LDI NRW), www.ldi.nrw.de, though you're free to complain to the one in your own country instead.

Keeping it secure

Credentials are encrypted and never logged, returned, or shared. LinkedIn actions run through native, humanized flows; full detail is on our Security & Compliance page. If a breach puts your rights at risk, we'll notify the authorities and you, as GDPR requires. Curviate isn't directed at, or offered to, anyone under 16.

Changes

We'll update this page when our practices change, and reset the cookie prompt if the change is material.

Contact