---
title: "Get a linked LinkedIn account"
method: GET
path: "/v1/accounts/{account_id}"
tags: ["Accounts"]
---

# Get a linked LinkedIn account

`GET /v1/accounts/{account_id}`

## Request

- Base URL: `https://api.curviate.com`
- URL: `https://api.curviate.com/v1/accounts/{account_id}`
- Auth: HTTP bearer

Return the metadata and current state for one connected account. Status is mapped to the stable whitelist: active | reconnect_needed | restricted | connecting | disconnected.

## Path parameters

- `account_id` string, required

## Response `200`

The account's metadata and current state, including the quotas[] budget view (one entry per counted action row for this account, for the current window of that row's grain) account_states[], the platform conditions the account is currently in, activity_window, the hours in which live writes are reported or refused, and event_log, where the account stands against the retained event log's row budget.

- object
  - `object` 'account': Response type discriminator.
  - `account_id` string
  - `status` 'active' | 'reconnect_needed' | 'restricted' | 'connecting' | 'disconnected'
  - `auth_method` string
  - `full_name` string, nullable
  - `headline` string, nullable
  - `connected_at` string, nullable: ISO-8601 UTC connection timestamp.
  - `last_checked_at` string: ISO-8601 UTC time the account state was last checked.
  - `seat_id` string, nullable: The seat this account occupies (null for an admin seatless account).
  - `requested_products` string[], nullable: The connection scope this account was last connected with (e.g. ["classic","company","sales_navigator","recruiter"]). A connect asks for every product and LinkedIn activates the ones the account actually has, so this is what was ASKED for, narrowed only by an explicit linkedin_premium on that connect. A linkedin_premium narrowing is not remembered between connects, so this shows what the LAST connect asked for and a reconnect that omits the field widens it again. Null for accounts connected before this was recorded; not attachment truth for Company Pages.
  - `external_id` string, nullable: Your own id for this account's end user, as set on connect or PATCH. Null when none. Not unique.
  - `metadata` object, nullable: Your own flat string map for this account, as last set by PATCH. Null when none.
  - `connection_location` object: Where LinkedIn sees this account connecting from. country: the configured country (ISO 3166-1 alpha-2), the one the account is set to use. current_country: the country of the connection in use right now; it differs from country only after a fallback, which can happen only when strict is false. mode: auto (a managed connection in country) or custom (your own proxy). strict: true when the account never connects from another country, null when unknown. Each field is null until known; an account connected through ip or your own proxy may show null until the next background refresh. Change it with PATCH /v1/accounts/{account_id} (country), or by reconnecting with country.
    - `country` string, nullable: Configured country (ISO 3166-1 alpha-2).
    - `current_country` string, nullable: Country of the connection in use right now.
    - `mode` 'auto' | 'custom' | 'null', nullable: auto: managed connection. custom: your own proxy.
    - `strict` boolean, nullable: true: never connects from another country.
  - `substrate_created_at` string, nullable: ISO-8601 UTC creation timestamp of the underlying LinkedIn account, distinct from connected_at. Null until the first background enrichment lands.
  - `quotas` object[]: Account-safety rows for this account: one entry per counted action for the current window of its grain, plus the derived total and the invitation backlog. Each row reports where the account stands, what it is allowed, which band that puts it in, what happens at the ceiling, whether the ceiling was raised above the Curviate default, and any active pause the platform caused. Whether a breach is refused or merely reported is `posture`, and the default is to report: nothing here refuses until you configure it to. Pacing is still the caller's responsibility, and nothing on this array paces for you. The per-minute REQUEST ceiling is a different thing and is deliberately not in this array: it protects Curviate's own servers rather than your LinkedIn account, it counts requests where these count platform operations, it is always enforced with HTTP 429 whatever your posture, and its envelope is returned on that refusal. Read this array to see where the account stands; read the safety events surface to see what has already been refused or flagged.
    - `budget_row` 'connection_requests_with_note' | 'connection_requests_no_note' | 'messages_first_degree' | 'inmail' | 'open_profile_inmail' | 'profile_views' | 'search' | 'comments' | 'post_likes' | 'follows' | 'content_poll' | 'job_poll' | 'posts' | 'endorsements' | 'invite_responses' | 'withdrawal_sweep' | 'total_actions' | 'pending_invites', required: The safety row. `search` counts result LINES, not queries issued; `content_poll` also debits `profile_views`. Two rows are not counters and say so in `kind`: `total_actions` is a tally of everything this account did in the window, and `pending_invites` is the outstanding invitation backlog read from the platform rather than something this account spends.
    - `kind` 'debit' | 'tally' | 'gauge', required: Which arithmetic produced `used`. `debit`: actions this account spent, counted as they happen. `tally`: a sum across the debit rows in the window, which is why it has a warn threshold and no ceiling. `gauge`: a level read from the platform rather than counted here, so it falls on its own as invitations are accepted or withdrawn.
    - `window` 'day' | 'week' | 'month' | 'null', nullable, required: Calendar grain this row is counted in. UTC, not account-local. `null` on the gauge, which has no window at all.
    - `window_start` string, nullable, required: ISO-8601 UTC start of the current window; `null` on the gauge.
    - `resets_at` string, nullable, required: ISO-8601 UTC instant the current window rolls over and `used` returns to 0. `null` on the gauge: a backlog falls when invitations are accepted or withdrawn, not at a window edge, so naming an instant would promise something nothing keeps.
    - `used` number, nullable, required: Where the account stands: actions counted in the current window for a debit row, the summed tally for `total_actions`, the observed backlog for the gauge. A debit row with no activity reports 0, never null. Only the gauge can be `null`, and only before anything has observed it: 0 there would be a backlog nobody looked at.
    - `ceiling` integer, nullable, required: What this account is allowed on this row in this window, as configured. `null` where the row deliberately has no ceiling: the tally is reported and never enforced, and an uncalibrated row has no figure at all. Change it on the safety policy; Curviate never clamps what you set.
    - `effective_ceiling` integer, nullable: What this row is ACTUALLY held to right now: `ceiling` with the account's warm-up ramp applied. Compare `used` against THIS one, and `band` is computed against it too. While an account is new, thinly connected, dormant or recovering from a restriction the two differ, and on a five-day-old account they differ by a factor of about seven. Equal to `ceiling` once the ramp is over, and `null` wherever `ceiling` is null. The account's ramp, its factor and the triggers behind it are on GET /v1/{account_id}/safety-policy as `warm_up`.
    - `interface_ceiling` integer, nullable: What a call through the matching ELEVATED interface is held to on this row: the `/v1/{account_id}/sales-navigator/...` and `/v1/{account_id}/recruiter/...` operations. Null when this row and limit profile have no such figure, which is every row on `basic` and `premium` and most rows everywhere. Everything else on this row (`ceiling`, `effective_ceiling`, `band` and `over_default`) is the STANDARD-interface figure, because one counter serves both interfaces and the standard side is the conservative one to band against. The consequence is worth reading: on a `sales_navigator` or `recruiter` account a row can report `band: "over"` with `posture: "enforce"` while a call through the elevated interface still succeeds; the refusal the band predicts is the one a standard call gets.
    - `effective_interface_ceiling` integer, nullable: `interface_ceiling` with the account's warm-up ramp applied, and null wherever `interface_ceiling` is null. Read THIS to know where an elevated-interface call stops, for the same reason `effective_ceiling` rather than `ceiling` answers that for a standard one.
    - `band` 'green' | 'amber' | 'hard' | 'over', required: Where `used` sits against this row's own bands. `green` routine, `amber` at or past the routine band, `hard` at or past the warn-louder band, `over` at or past the ceiling. `over` is what `posture` acts on: `warn` reports it and lets the action through, `enforce` refuses. A row with no figures reports `green` at every value, because there is nothing to be past.
    - `posture` 'warn' | 'enforce', required: What happens when this row is `over`. `warn`: the action goes through and the response carries the breach. `enforce`: the action is refused before it reaches the platform. Set per row, per account or workspace-wide on the safety policy; the default is `warn`. One exception worth knowing: on `pending_invites` this field is not the one that acts. That row blocks invitations rather than being spent itself, and the posture that decides whether a full backlog warns or refuses is the one on the invitation row, so setting `enforce` on invitations is enough and you never have to find a posture on a row nothing spends.
    - `over_default` boolean, required: Whether something on this row is configured above Curviate's own default. The safety policy carries the detail: which field, the value, the default and how well calibrated that default is.
    - `halt` object, nullable, required: An active pause on this row because the platform refused a recent request on it, or `null`. A halt is not a ceiling: no limit you raise lifts it, and it clears itself when it expires. To lift one before it expires, send this row in `clear_halts` on `PATCH /v1/{account_id}/safety-policy`; that is recorded, and the pause returns if the platform refuses the account again.
      - `reason` string: What the platform did: `rate_limited`, `platform_fault`, or `resend_cooldown` (the same invitation was sent too recently).
      - `retry_after_seconds` integer: Whole seconds until the pause expires.
    - `observed_at` string, date-time, nullable, required: When the gauge's level was read from the platform. `null` on every other row, whose numbers are read live from Curviate's own records. Read it before acting on the backlog: the level is refreshed in the background, so it can be up to an hour old.
    - `source` 'substrate' | 'linkedin_official' | 'practitioner' | 'inferred' | 'unseeded', required: How well calibrated this row is, in descending order of trust: `substrate` (the transport layer's own guidance), `linkedin_official` (platform-published; reliable for mechanics such as credit counts and reset dates, not for safe volume), `practitioner` (corroborated operator experience), `inferred` (Curviate's own reasoning; flagged, never hidden), `unseeded` (no figure at all for this row on this account's limit profile, reported so nothing is silently missing, never presented as a number).
  - `account_states` string[]: Platform conditions this LinkedIn account is currently in, additive to `status` and independent of it. `recruiter_session_evicted`: the account is signed in somewhere else and LinkedIn allows one session at a time for it, so a person has to close the other session; reconnecting will not help. `commercial_use_limited`: LinkedIn's commercial-use limit has tripped, so people search returns only a handful of results per query. It is a paywall, not a penalty, and nothing else about the account is affected. `profile_view_limited`: profile views outside this account's own network are being blocked; first-degree views still work. The two limits are detected from their symptom: LinkedIn publishes no threshold for either, exposes no counter, and lifts neither on request, so no number is reported. `commercial_use_limited` is a present-tense reading and a genuinely narrow search can also produce the shape it looks for, so treat it as a reason to check rather than a certainty; the next people search that returns a full page clears it. An account in none of these reports an empty array.
  - `activity_window` object: The account's activity window, the hours in which Curviate reports or refuses live writes to LinkedIn. Reads and anything served from Curviate's own store are never affected, at any hour. Configure it on the account's safety policy.
    - `timezone` string, nullable: The IANA zone the window is read in, or null when none is set.
    - `timezone_unset` boolean: True when no zone is set, which DISABLES the activity window. Curviate never guesses a zone: a guess eight hours wrong inverts the window, refusing every daytime write and permitting every nocturnal one. If you configured a window and it never fires, this is why.
  - `event_log` object: Where this account stands against the retained event log's row budget. Events are retained for 24 hours so you can read what the platform sent without running a listener; an account may hold at most row_cap of them at once. While at_row_cap is true, further events for this account are still delivered to any webhook you registered but are NOT retained, so the events resource stops being a complete record of what arrived. The budget is a backstop against a misbehaving upstream, sized so ordinary use never approaches it.
    - `retained` integer: Events currently held for this account. A level, not a total: retention is 24 hours, so this falls on its own as events age out.
    - `row_cap` integer: The maximum this account may hold at once.
    - `at_row_cap` boolean: True while the cap is biting and incoming events are not being retained. Delivery to a registered webhook is unaffected.
  - `safety_warning` SafetyWarning: Present on a successful response when this account crossed an account-safety ceiling while the affected budget row is on the default warn posture. The action still happened. Field-identical to the BUDGET_EXHAUSTED error body except that blocked is false and hint.message describes the outcome, so one branch handles both postures and switching the account to enforce is a configuration change rather than a client rewrite.
    - `row` string, nullable, required: The budget row that breached, for example profile_views or pending_invites. Null when the breach is not about a row: an activity_window warning on an action that spends no budget of its own (editing a message, dismissing a notification, a Recruiter or Sales Navigator admin action) is still governed by the hours the account works in. Read hint.parameter for what to change; whenever this is null it names posture, because the activity window edges are set per action type and this action has none, so the levers that reach it are the account timezone and posture.
    - `reset_at` string, nullable, required: The instant the refusal lifts: the window roll on a ceiling refusal, the next window open on an activity-window one. Null in the two cases no clock frees, the pending_invites gauge and InMail credits (row inmail).
    - `hint` object, required
      - `parameter` string, required: The settable parameter on PATCH /v1/{account_id}/safety-policy.
      - `message` string, required: What the condition is and what changing that parameter would do. This is the one field whose text differs from the BUDGET_EXHAUSTED refusal: here it says the action went through, and on a ceiling breach how far past the ceiling this row now stands, rather than telling you to set posture to warn when it already is. Branch on parameter, reason and blocked, never on this text.
    - `reason` 'ceiling' | 'activity_window', required: Which rule was crossed. ceiling means the row's configured limit is spent; activity_window means the account is outside the hours it works in, which is a different fact with a different remedy. hint.parameter names the right setting for each.
    - `blocked` boolean, required: Always false here. True on the BUDGET_EXHAUSTED refusal carrying the same payload.

## Other responses

- `401`: Missing or invalid API key.
- `404`: No such account for this tenant. An id whose connection was replaced or removed also stops resolving here, and it is already absent from the list, so re-read `GET /v1/accounts` for the current id.
- `429`: Rate limited, slow down and retry after the hinted delay.
- `500`: Internal error.
- `503`: Service unavailable.
- `504`: Gateway timeout.

---

[API](https://skmtc.dev/curviate/apis/curviate-api.md) · [All operations](https://skmtc.dev/curviate/apis/curviate-api/llms.txt) · [OpenAPI document](https://skmtc.dev/curviate/apis/curviate-api/revisions/eab848801d95?raw)
