@curviate/sdk 0.19.0 β†’ 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,12 +3,195 @@
3
3
  All notable changes to `@curviate/sdk` are documented here.
4
4
 
5
5
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
- Versioning: semantic β€” minor for additive changes, patch for bug fixes; no stability promise before 1.0.
6
+ Versioning: semantic. Minor for additive changes, patch for bug fixes; no stability promise before 1.0.
7
7
 
8
8
  ---
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.20.1] - 2026-08-10
13
+
14
+ Published as a patch, not a minor: it satisfies the `^0.20.0` range already
15
+ declared by installed consumers, including `@curviate/cli`, so it reaches them
16
+ on the next install with no consumer-side version bump.
17
+
18
+ ### Fixed
19
+
20
+ - **Path parameters are percent-encoded, and a few unusable values are now
21
+ rejected.** Every id you pass to a method was interpolated into the request
22
+ path verbatim, so any character that means something to a URL parser changed
23
+ which endpoint the call reached. A share URL passed as a post id turned one
24
+ path segment into eight (and `https://` collapsed to `https:/` on the way),
25
+ producing a 404 on a value the API accepts; a `?` moved the rest of the id
26
+ into the query string; and a value containing `../` was resolved away by the
27
+ URL parser before the request was sent, retargeting the call at a different
28
+ endpoint entirely. That last one is a security defect: if any part of an id
29
+ reaches your code from model output, end-user input, or a scraped page, it
30
+ could redirect the request. All 117 path parameters across every namespace
31
+ are now encoded at the point they enter the path, including the account id
32
+ bound by `account(id)`.
33
+
34
+ Encoding closes that on its own for every value except one class. `.` and
35
+ `..` are not encoded by `encodeURIComponent`, and a URL parser decodes a
36
+ segment before deciding whether it is a dot segment, so percent-encoding
37
+ them changes nothing. A path parameter that is exactly `.`, `..`, or the
38
+ empty string is therefore **rejected** with `INVALID_REQUEST` before any
39
+ network call, as a rejected promise like every other error here. No id, URN,
40
+ or slug is ever one of those three values. A value that merely contains a
41
+ dot (`1.2.3`, `.hidden`, `...`, `a..b`) is untouched, and so is a literal
42
+ `%2e`, which encoding already makes inert.
43
+
44
+ What changes for you, concretely:
45
+
46
+ - Ids made only of letters, digits, `-`, `_`, `.`, and `~` produce a
47
+ byte-identical request. Numeric post ids, `acc_...` ids, and public slugs
48
+ are in this group, so most calls are unaffected.
49
+ - Ids containing reserved characters (URN colons and commas, base64 `+` and
50
+ `/`, chat-id `=`) now travel percent-encoded. The API decodes them back to
51
+ the same value, so the call behaves as before; only the bytes on the wire
52
+ differ. Snapshot tests that assert on a literal request path will need
53
+ updating.
54
+ - Ids that never worked start working: a share URL, or any id containing `/`,
55
+ `?`, or `#`, now reaches the API intact.
56
+ - **One breaking case.** If you were working around this by percent-encoding
57
+ an id yourself before passing it in, stop: pass the decoded value. The SDK
58
+ owns the encoding now, so a pre-encoded id is treated as a literal value
59
+ containing percent signs and is encoded again.
60
+ - Every call made through an `account(id)` scope changes on the wire if that
61
+ id contains a reserved character, not just calls to one method. The bound
62
+ selector is encoded like any other path parameter.
63
+ - A whitespace or control character inside an id is no longer silently
64
+ dropped. Tab, carriage return, and line feed were deleted outright by the
65
+ URL parser, so an id with a stray one could accidentally match the intended
66
+ resource; it is now sent encoded and will simply not be found. If you were
67
+ relying on that accidental cleanup, trim your ids.
68
+ - An id containing the literal text `{account_id}` is no longer substituted.
69
+ That placeholder is how an account-scoped path is built internally, and a
70
+ raw id containing it used to be rewritten mid-flight. It is now encoded and
71
+ passed through as the value you supplied.
72
+ - Values that are not strings are stringified before encoding, so a JS
73
+ consumer passing an array now sends `a%2Cb` where it previously sent `a,b`.
74
+ Both decode to the same thing; only the bytes differ.
75
+
76
+ One deployment note: the encoded form of `/` is `%2F`, and while the Curviate
77
+ API accepts it, some infrastructure in front of your own code may not. Apache
78
+ with `AllowEncodedSlashes Off`, certain reverse proxies, and some web
79
+ application firewalls reject `%2F` in a path outright. This only affects ids
80
+ that genuinely contain a slash, share URLs above all.
81
+
82
+ ## [0.20.0] - 2026-08-07
83
+
84
+ Generated types are rebuilt from the **served** OpenAPI document rather than a
85
+ hand-refreshed snapshot, and the snapshot itself now records where it came from.
86
+ Source of this release: `https://api.staging.curviate.com` at server commit
87
+ `1015999221a2dafc505cc565369a40b8ceca1888`, recorded in
88
+ `fixtures/PROVENANCE.json`.
89
+
90
+ ### Changed
91
+
92
+ - **BREAKING (type-level): `headers[].value` is gone from every webhook response,
93
+ replaced by `headers[].value_prefix`.** It affects `webhooks.create` (201),
94
+ `webhooks.get`, `webhooks.update`, and `webhooks.list`. The API stopped
95
+ returning the plaintext header value some time ago and returns a masked prefix
96
+ instead; the vendored snapshot had not caught up, so the SDK typed a field the
97
+ server no longer sends. `tsc` will point at every `h.value` read:
98
+
99
+ ```diff
100
+ - const configured = webhook.headers?.[0]?.value;
101
+ + const configured = webhook.headers?.[0]?.value_prefix; // masked, never the full value
102
+ ```
103
+
104
+ This is the only breaking change in the release. Read the label honestly: the
105
+ old type was already wrong at runtime, so any code relying on it was reading
106
+ `undefined`; the break is `tsc` finally saying so.
107
+
108
+ - `POST /v1/auth/checkpoint/poll` reports three further `status` values
109
+ (`reconnect_needed`, `restricted`, `disconnected`) on top of the five it
110
+ already had. Purely additive, but an exhaustive `switch` over `status` with a
111
+ `never` fallthrough will need the new arms.
112
+
113
+ - `GET /v1/{account_id}/search/jobs`, `POST /v1/{account_id}/search`, and the
114
+ company jobs listing now mark `id` required on a job item alongside
115
+ `job_urn` (same value, shorter name, chains into `get_job`).
116
+
117
+ - **Punctuation swept out of the published copy.** The README, this changelog,
118
+ and every JSDoc comment that ships inside `dist/index.d.ts` used typographic
119
+ characters with no plain-ASCII equivalent on the keyboard: em and en dashes,
120
+ the single-glyph ellipsis, arrows. They are gone, replaced with commas,
121
+ semicolons, colons, `...` and `->`. No release note changed what it claims;
122
+ the word stream is byte-identical apart from one inserted "and". Version
123
+ headers now use the ` - ` separator that Keep a Changelog specifies.
124
+
125
+ ### Added
126
+
127
+ - **`account.restricted` is now a member of the `CurviateEvent` union.** The
128
+ server publishes it and delivers it by default on the `account_status` source,
129
+ but the SDK union did not carry it, so an exhaustive `switch (event.event)`
130
+ could not compile a handler for the one event that means "stop sending on this
131
+ account". The union is pinned at compile time to the generated create-events
132
+ enum, so this addition was produced by the regeneration rather than typed by
133
+ hand.
134
+
135
+ - **`EventPayloadBase`**, exported: the `account_id`, `event`, and `occurred_at`
136
+ every webhook payload carries. `occurred_at` was previously reachable only
137
+ through the payload index signature, typed `unknown`, which meant the
138
+ de-duplication key documented below could not be built without a cast.
139
+
140
+ - Response fields surfaced by the regeneration: `notices[]` on every search and
141
+ company-employees listing, `visibility` on people / services / company-employee
142
+ results, `id` on job results, `recovered` on a checkpoint poll,
143
+ `trial_premium` on an auth intent, `next_action` / `notices` / `unresolved` on
144
+ the shared `Error` schema, a `409` on checkpoint poll and a `403` on the user
145
+ read.
146
+
147
+ - `pnpm gen:fixture` (`scripts/refresh-fixture.mjs`): fetches the live document,
148
+ refuses to write it if it carries a forbidden name, canonicalizes the
149
+ environment-dependent `servers` order so a refresh from staging and one from
150
+ production produce identical bytes, and writes `fixtures/PROVENANCE.json`. The
151
+ snapshot previously had no writer at all, which is why nobody could tell how
152
+ old it was.
153
+
154
+ - **`pnpm check:copy`**, a pre-publish copy-quality gate, chained into
155
+ `prepack` alongside the existing internal-reference scan. It fails the pack
156
+ on any non-ASCII typographic character in the published copy and reports
157
+ (without failing) emoji and wording from the generic-marketing register. See
158
+ the header of `scripts/check-copy.mjs` for why only the first tier blocks.
159
+ `test/check-copy.test.ts` exercises each pattern on its own, because a
160
+ pattern set that silently stops matching still reports zero.
161
+
162
+ ### Fixed
163
+
164
+ - **The documented webhook de-duplication key did not work.** The README said
165
+ the delivered `event.id` was something you "can de-duplicate retries on". Every
166
+ delivery attempt mints a fresh `wdl_` id, so a retry arrives with a different
167
+ one and a consumer following that advice stored the same logical event twice,
168
+ double-processing exactly the retries the guidance claimed to cover. The stable
169
+ key is the payload composite `event.event` plus `event.data.account_id` plus
170
+ `event.data.occurred_at`, which the platform re-sends identically on every
171
+ attempt. The README, the `CurviateEventEnvelope.id` JSDoc, and the 0.19.0
172
+ entry below all carried the wrong claim; all three are corrected, and the
173
+ 0.19.0 entry was edited in place rather than left standing as advice someone
174
+ would still follow.
175
+
176
+ - Doc comments no longer hardcode the size of a set that grows. "27 events" on
177
+ `webhooks.listEvents`, "24 canonical events" on the `CurviateEvent` union,
178
+ "22 more event types" and "34 error codes" in the README were each hand-typed
179
+ and each stale. The served catalogue description counts itself and the union is
180
+ pinned to the generated enum, so the numbers were redundant as well as wrong.
181
+
182
+ - `test/docs-contract.test.ts`: the two claim families above now go red in the
183
+ suite. Prose was the one representation of the contract that nothing checked,
184
+ which is why both defects shipped.
185
+
186
+ - The banner on `src/generated/types.ts` carried an em dash. The file ships (it
187
+ is in `files[]` and is inlined into `dist/index.d.ts`), so the banner is
188
+ published copy and the dash was the same tell the sweep removed everywhere
189
+ else. It is fixed where it is emitted rather than in the output, and the
190
+ string now has one home: `gen-types.mjs` wrote it and `check-types.mjs`
191
+ reconstructed it to diff against, so the two hand-written copies could
192
+ disagree and red the drift gate over a file nobody had touched. Both now
193
+ import `GENERATED_HEADER` from `scripts/openapi-sanitize.mjs`.
194
+
12
195
  ## [0.19.0] - 2026-08-03
13
196
 
14
197
  ### Fixed
@@ -53,9 +236,9 @@ Versioning: semantic β€” minor for additive changes, patch for bug fixes; no sta
53
236
  ### Added
54
237
 
55
238
  - **`CurviateEventEnvelope`**: the per-delivery metadata now carried on every
56
- event. `event.id` is the `wdl_…` delivery id (de-duplicate retries on it),
57
- `event.webhook_id` is the registration, `event.delivered_at` is the attempt
58
- timestamp. These are typed optional and are **not** required by the parser:
239
+ event. `event.id` is the `wdl_...` delivery id, `event.webhook_id` is the
240
+ registration, `event.delivered_at` is the attempt timestamp. These are typed
241
+ optional and are **not** required by the parser:
59
242
  requiring an envelope field is what caused this defect, so nothing beyond the
60
243
  discriminant can fail a verification again.
61
244
 
@@ -67,39 +250,39 @@ Versioning: semantic β€” minor for additive changes, patch for bug fixes; no sta
67
250
  `test/fixtures/webhook-delivery.capture.json`: the regression test replays
68
251
  bytes and a signature header **captured off the wire from the real dispatch
69
252
  worker**, rather than constructing its own payload. The pre-existing suite was
70
- green throughout this defect because it built its own `{ type: … }` fixtures;
253
+ green throughout this defect because it built its own `{ type: ... }` fixtures;
71
254
  a suite that constructs its own input cannot detect an emitter/parser
72
255
  divergence, and that is what let this ship.
73
256
 
74
- ## [0.18.1] β€” 2026-07-18
257
+ ## [0.18.1] - 2026-07-18
75
258
 
76
259
  ### Changed
77
260
 
78
- - **`companies.sendMessage(identifier, chatId, body)`** now takes the normal `2-…`
261
+ - **`companies.sendMessage(identifier, chatId, body)`** now takes the normal `2-...`
79
262
  chat id that `companies.chats()` / `chat()` / `searchChats()` return; the endpoint
80
263
  resolves the page mailbox internally from `identifier`. The previous send-ready
81
264
  `COMPANY_` chat-id requirement is gone: pass the id straight from the company chat
82
265
  reads. Method signature and response shape are unchanged (documentation/behavior
83
266
  only).
84
267
 
85
- ## [0.18.0] β€” 2026-07-17
268
+ ## [0.18.0] - 2026-07-17
86
269
 
87
270
  ### Added
88
271
 
89
272
  - **`companies.sendMessage(identifier, chatId, body)`**: reply to a company-inbox
90
273
  conversation as the page. `POST /v1/{account_id}/companies/{identifier}/chats/{chat_id}/messages`.
91
274
  `chatId` must be the send-ready `COMPANY_` chat id from `inboxes.listChats()`, not the
92
- `2-…` id `companies.chats()`/`chat()`/`searchChats()` return; a non-`COMPANY_` id is
275
+ `2-...` id `companies.chats()`/`chat()`/`searchChats()` return; a non-`COMPANY_` id is
93
276
  rejected with a guiding 400. Reply-only. The response echoes `sent_as`, the acting
94
277
  identity actually used.
95
278
 
96
- ## [0.17.0] β€” 2026-07-17
279
+ ## [0.17.0] - 2026-07-17
97
280
 
98
281
  ### Added
99
282
 
100
283
  - **`companies.followInvite(identifier, body)`**: invite the connected
101
284
  account's 1st-degree connections to follow a company page it administers with the
102
- invite-to-follow entitlement. Pass the `AC…` member ids from an `invitableFollowers()`
285
+ invite-to-follow entitlement. Pass the `AC...` member ids from an `invitableFollowers()`
103
286
  read. `POST /v1/{account_id}/companies/{identifier}/follow-invite`. All-or-nothing:
104
287
  for an all-valid request, resolves to one outcome per requested invitee, in request
105
288
  order (`status: "invited" | "already_invited" | "ineligible" | "not_found"`); if any
@@ -107,7 +290,7 @@ Versioning: semantic β€” minor for additive changes, patch for bug fixes; no sta
107
290
  Re-inviting an already-invited member is a safe no-op (the same `invitation_id`,
108
291
  never a duplicate).
109
292
 
110
- ## [0.16.0] β€” 2026-07-17
293
+ ## [0.16.0] - 2026-07-17
111
294
 
112
295
  Mostly additive, plus one breaking removal on the connect/reconnect body.
113
296
 
@@ -154,25 +337,25 @@ Mostly additive, plus one breaking removal on the connect/reconnect body.
154
337
  profile; `user_fixable`, never retryable) and `REAUTH_REQUIRED` (a
155
338
  scope-changing reconnect attempted with a cookie instead of credentials;
156
339
  `user_fixable`, never retryable).
157
- - **New account-scoped `profile` namespace (4 methods)** β€” the connected
340
+ - **New account-scoped `profile` namespace (4 methods)**, the connected
158
341
  account's own insight surface: `profile.subscription()`, `profile.analytics()`,
159
342
  `profile.visitors(query?)`, `profile.ssi()`. Distinct from the retired
160
343
  `profiles` namespace (renamed to `users` in 0.15.0).
161
- - **New account-scoped `groups` namespace (3 methods)** β€” `groups.list(query?)`
344
+ - **New account-scoped `groups` namespace (3 methods)**, `groups.list(query?)`
162
345
  (own groups by default, or another member's via `{ profile }`),
163
346
  `groups.get(group)`, and `groups.members(group, query?)` (with the folded-in
164
347
  `{ name }` member search).
165
- - **New account-scoped `feed` namespace (1 method)** β€” `feed.home(query?)` reads
348
+ - **New account-scoped `feed` namespace (1 method)**, `feed.home(query?)` reads
166
349
  the connected account's home feed as agent-actionable posts, with `relevant`
167
350
  or `recent` sort orders.
168
- - **`companies` gains 3 company-insights methods** β€” `companies.managed(query?)`
351
+ - **`companies` gains 3 company-insights methods**, `companies.managed(query?)`
169
352
  lists the pages the connected account administers; `companies.followers(identifier,
170
- query?)` lists a page's followers (**re-added** under a different item shape β€”
171
- `company_follower`, carrying `degree`/`followed_at` β€” than the pre-0.15.0
172
- method of the same name); `companies.invitableFollowers(identifier, query?)`
353
+ query?)` lists a page's followers (**re-added** under a different item shape
354
+ than the pre-0.15.0 method of the same name: `company_follower`, carrying
355
+ `degree`/`followed_at`); `companies.invitableFollowers(identifier, query?)`
173
356
  lists connections invitable to follow the page. All three require the
174
357
  connected account to administer the target page.
175
- - **`companies` gains 5 Beta company-inbox methods** β€” `companies.chats(identifier,
358
+ - **`companies` gains 5 Beta company-inbox methods**, `companies.chats(identifier,
176
359
  query?)`, `companies.chat(identifier, chatId)`, `companies.messages(identifier,
177
360
  chatId, query?)`, `companies.message(identifier, chatId, messageId)`, and
178
361
  `companies.searchChats(identifier, query?)` (exactly one of `query`/`topic`/
@@ -181,140 +364,140 @@ Mostly additive, plus one breaking removal on the connect/reconnect body.
181
364
  single-page listing and termination are verified; deep pagination (many
182
365
  pages / large cursor round-trips) is still being validated against a busier
183
366
  inbox. `companies` is now 12 methods (was 4).
184
- - **`search` gains 3 methods** β€” `search.groups(query)` searches LinkedIn
367
+ - **`search` gains 3 methods**, `search.groups(query)` searches LinkedIn
185
368
  groups by keyword; `search.services(body & query)` searches Services
186
369
  Marketplace providers with structured filters; `search.getServiceParameters(query)`
187
370
  resolves human-readable service-category/location terms into the opaque
188
371
  filter ids `services()` accepts. `search` is now 9 methods (was 6).
189
- - **`messaging` gains 1 method** β€” `messaging.searchChats(params)` free-text
372
+ - **`messaging` gains 1 method**, `messaging.searchChats(params)` free-text
190
373
  searches the account's own inbox (participant names and message content).
191
374
  `messaging` is now 13 methods (was 12).
192
- - **`posts` gains 3 saved-posts methods** β€” `posts.listSaved(query?)` lists the
193
- connected account's own saved posts (a self resource β€” previews only,
194
- `snippet` capped at ≀140 chars, never the full body); `posts.save(postId)`
375
+ - **`posts` gains 3 saved-posts methods**, `posts.listSaved(query?)` lists the
376
+ connected account's own saved posts (a self resource: previews only,
377
+ `snippet` capped at <=140 chars, never the full body); `posts.save(postId)`
195
378
  and `posts.unsave(postId)` add/remove a bookmark, both idempotent and
196
379
  accepting either `urn:li:activity:<id>` or a bare numeric id. `posts` is now
197
380
  12 methods (was 9).
198
- - **New account-scoped `notifications` namespace (3 methods)** β€” the connected
381
+ - **New account-scoped `notifications` namespace (3 methods)**, the connected
199
382
  account's own notification centre: `notifications.list(query?)` (cards +
200
383
  the account-level `unread_count`/`latest_published_at`);
201
384
  `notifications.delete(cardUrn)` and `notifications.showLess(cardUrn)`, two
202
385
  self-action writes on the account's own cards. Both writes are idempotent
203
- and take effect within a few seconds β€” a list read immediately after may
386
+ and take effect within a few seconds; a list read immediately after may
204
387
  still show the card for a moment, which is not a failure signal. The SDK
205
388
  percent-encodes `cardUrn` (which embeds `(`, `)`, `:`, `,`) into the path.
206
389
  - **Parity pin: 143 methods across 18 namespaces** (was 135 / 16 at the start
207
390
  of this release cycle). The `inboxes` namespace is the final addition.
208
391
 
209
- ## [0.15.0] β€” 2026-07-11
392
+ ## [0.15.0] - 2026-07-11
210
393
 
211
394
  Full v2 parity. The SDK is re-aligned 1:1 to the served API surface: every
212
395
  operation is now exactly one method at the exact wire encoding, the entire
213
396
  account-scoped surface moves to the account-first path grammar, and three
214
397
  namespaces are reorganized. This is a **breaking** release touching nearly
215
- every namespace. The five breaking categories below β€” removals, namespace
398
+ every namespace. The five breaking categories below (removals, namespace
216
399
  reorganization, method renames/relocations, request/response shape changes, and
217
- the account-first path-grammar migration β€” are the complete set of breaks a
218
- `0.14.1` β†’ `0.15.0` upgrade must reconcile, gathered here so a consumer finds
400
+ the account-first path-grammar migration) are the complete set of breaks a
401
+ `0.14.1` -> `0.15.0` upgrade must reconcile, gathered here so a consumer finds
219
402
  every break in one place.
220
403
 
221
404
  ### Removed (BREAKING)
222
405
 
223
406
  - **14 methods that map to no served operation are removed, with no alias:**
224
- - `accounts.createConnectLink()`, `accounts.createReconnectLink()`, `accounts.reconnect()` β€” hosted connect/reconnect link minting and in-place reconnect are no longer part of the API surface.
225
- - `messaging.syncChat()`, `messaging.syncMessages()`, `recruiter.syncMessages()`, `salesNavigator.syncMessages()` β€” explicit sync operations are gone; accounts sync continuously and deliver via webhooks.
226
- - `posts.list()` β€” the standalone feed list has no served operation (use `posts.listUserPosts(userId)` for a member's posts).
227
- - `companies.followers()` β€” no served operation.
228
- - `recruiter.addApplicant()`, `recruiter.rejectApplicant()`, `recruiter.solveJobCheckpoint()` β€” superseded by the project-centric pipeline surface.
229
- - `webhooks.getStateDiff()` β€” no served operation.
230
- - **`invites.respond()` is removed** β€” split into two dedicated bodyless methods, `invites.accept(invitationId)` and `invites.decline(invitationId)` (see below).
231
- - **`profiles.getCompany()` stays removed** β€” dropped ahead of this release with no return; company reads live on the `companies` namespace.
232
-
233
- ### Changed (BREAKING) β€” namespaces reorganized
234
-
235
- - **`profiles` namespace renamed to `users`.** Every `curviate.account(id).profiles.*` call becomes `…users.*`; the served group is "Users" and every path is `/users/{user_id}`.
236
- - **New root-scoped `auth` namespace, split out of `accounts`.** The connect/checkpoint operations move off `accounts`: `accounts.link` β†’ `auth.intent`, `accounts.solveCheckpoint` β†’ `auth.solveCheckpoint`, plus `auth.requestCheckpoint`, `auth.pollCheckpoint`, and `accounts.getConnectSession` β†’ `auth.getSession(sessionId)`. `accounts` now carries only `list` / `get` / `update` / `disconnect`.
407
+ - `accounts.createConnectLink()`, `accounts.createReconnectLink()`, `accounts.reconnect()`: hosted connect/reconnect link minting and in-place reconnect are no longer part of the API surface.
408
+ - `messaging.syncChat()`, `messaging.syncMessages()`, `recruiter.syncMessages()`, `salesNavigator.syncMessages()`: explicit sync operations are gone; accounts sync continuously and deliver via webhooks.
409
+ - `posts.list()`: the standalone feed list has no served operation (use `posts.listUserPosts(userId)` for a member's posts).
410
+ - `companies.followers()`: no served operation.
411
+ - `recruiter.addApplicant()`, `recruiter.rejectApplicant()`, `recruiter.solveJobCheckpoint()`: superseded by the project-centric pipeline surface.
412
+ - `webhooks.getStateDiff()`: no served operation.
413
+ - **`invites.respond()` is removed**, split into two dedicated bodyless methods, `invites.accept(invitationId)` and `invites.decline(invitationId)` (see below).
414
+ - **`profiles.getCompany()` stays removed**, dropped ahead of this release with no return; company reads live on the `companies` namespace.
415
+
416
+ ### Changed (BREAKING): namespaces reorganized
417
+
418
+ - **`profiles` namespace renamed to `users`.** Every `curviate.account(id).profiles.*` call becomes `...users.*`; the served group is "Users" and every path is `/users/{user_id}`.
419
+ - **New root-scoped `auth` namespace, split out of `accounts`.** The connect/checkpoint operations move off `accounts`: `accounts.link` -> `auth.intent`, `accounts.solveCheckpoint` -> `auth.solveCheckpoint`, plus `auth.requestCheckpoint`, `auth.pollCheckpoint`, and `accounts.getConnectSession` -> `auth.getSession(sessionId)`. `accounts` now carries only `list` / `get` / `update` / `disconnect`.
237
420
  - **New account-scoped `comments` namespace.** The comment-thread surface (create / edit / delete / reply / list replies / reactions) plus the relocated `listUserComments` live here rather than on `posts`.
238
- - **Root and account surfaces are now strictly disjoint.** The root client exposes only `accounts`, `auth`, `webhooks`. Every other namespace is reachable exclusively through `curviate.account(id)` β€” the account-scoped namespaces are no longer mounted on the root client (they cannot build a valid path without a bound `account_id`).
421
+ - **Root and account surfaces are now strictly disjoint.** The root client exposes only `accounts`, `auth`, `webhooks`. Every other namespace is reachable exclusively through `curviate.account(id)`; the account-scoped namespaces are no longer mounted on the root client (they cannot build a valid path without a bound `account_id`).
239
422
 
240
- ### Changed (BREAKING) β€” methods renamed / relocated
423
+ ### Changed (BREAKING): methods renamed / relocated
241
424
 
242
- - `profiles.endorse(userId, { skill_endorsement_id })` β†’ **`users.endorseSkill(userId, { endorsement_id })`** β€” renamed, and the body key changed from `skill_endorsement_id` to `endorsement_id`.
243
- - `profiles.listConnections()` β†’ **`users.listRelations()`**.
244
- - `messaging.getInMailBalance()` β†’ **`users.getInMailCredits()`** (relocated onto `users`).
245
- - `posts.comment(postId, …)` β†’ **`comments.create(postId, …)`**.
246
- - `profiles.listComments(userId)` β†’ **`comments.listUserComments(userId)`**.
247
- - `profiles.listPosts(userId)` β†’ **`posts.listUserPosts(userId)`**.
248
- - `profiles.listReactions(userId)` β†’ **`posts.listUserReactions(userId)`**.
249
- - `invites.respond(...)` β†’ **`invites.accept(invitationId)` / `invites.decline(invitationId)`** (split; both bodyless).
250
- - `recruiter.addCandidate(...)` β†’ **`recruiter.saveCandidate(projectId, { stage_id, candidate_id })`**.
251
- - `recruiter.getParameters(...)` (GET) → **`recruiter.searchParameters(body)` (POST)** — the HTTP verb changed GET→POST, with a `source`-discriminated body.
252
- - `recruiter.listProjectJobs(projectId)` β†’ **`recruiter.getProjectJob(projectId)`** β€” the operation returns the single attached job posting, not a list.
425
+ - `profiles.endorse(userId, { skill_endorsement_id })` -> **`users.endorseSkill(userId, { endorsement_id })`**, renamed, and the body key changed from `skill_endorsement_id` to `endorsement_id`.
426
+ - `profiles.listConnections()` -> **`users.listRelations()`**.
427
+ - `messaging.getInMailBalance()` -> **`users.getInMailCredits()`** (relocated onto `users`).
428
+ - `posts.comment(postId, ...)` -> **`comments.create(postId, ...)`**.
429
+ - `profiles.listComments(userId)` -> **`comments.listUserComments(userId)`**.
430
+ - `profiles.listPosts(userId)` -> **`posts.listUserPosts(userId)`**.
431
+ - `profiles.listReactions(userId)` -> **`posts.listUserReactions(userId)`**.
432
+ - `invites.respond(...)` -> **`invites.accept(invitationId)` / `invites.decline(invitationId)`** (split; both bodyless).
433
+ - `recruiter.addCandidate(...)` -> **`recruiter.saveCandidate(projectId, { stage_id, candidate_id })`**.
434
+ - `recruiter.getParameters(...)` (GET) -> **`recruiter.searchParameters(body)` (POST)**, the HTTP verb changed GET->POST, with a `source`-discriminated body.
435
+ - `recruiter.listProjectJobs(projectId)` -> **`recruiter.getProjectJob(projectId)`**, the operation returns the single attached job posting, not a list.
253
436
 
254
- ### Changed (BREAKING) β€” request / response shapes
437
+ ### Changed (BREAKING): request / response shapes
255
438
 
256
439
  - **`users.update(userId, body)`** never sends `description`; accepted keys are `{ first_name, last_name, headline, bio, skills, picture, background_picture }`.
257
440
  - **`users.endorseSkill`** body key is `endorsement_id` (was `skill_endorsement_id`).
258
441
  - **`jobs.publish` / `recruiter.publishJob`** take a `mode`-discriminated body (`FREE` | `PROMOTED` | `PROMOTED_PLUS`; the `PROMOTED*` modes require a `budget { currency, amount, scope }`) and respond `{ object, job_state }`.
259
442
  - **`jobs.close` / `recruiter.closeJob`** are bodyless POSTs responding `{ object }`.
260
443
  - **`invites.cancel`** withdrawal now discriminates on `invitation_withdrawn`.
261
- - **Message operations are re-homed under the chat.** `getMessage` / `editMessage` / `deleteMessage` / `addReaction` / `getAttachment` now path under `/chats/{chat_id}/messages/…` and take `chatId` as the leading argument.
444
+ - **Message operations are re-homed under the chat.** `getMessage` / `editMessage` / `deleteMessage` / `addReaction` / `getAttachment` now path under `/chats/{chat_id}/messages/...` and take `chatId` as the leading argument.
262
445
  - **`messaging.sendInMail`** body drops the `surface` field.
263
- - **Media-bearing writes moved from multipart to JSON + base64** where the surface retired multipart (recruiter, sales-navigator, posts, messaging); read each operation's declared content-type β€” some remain multipart.
446
+ - **Media-bearing writes moved from multipart to JSON + base64** where the surface retired multipart (recruiter, sales-navigator, posts, messaging); read each operation's declared content-type; some remain multipart.
264
447
  - **Sales Navigator `saveAccount` / `saveLead` bodies shrank** to the minimal saved-entity shape.
265
448
  - **`recruiter.startChat`** now requires a `signature` (alongside `attendees_ids`, `text`, `subject`).
266
449
  - **`recruiter.createJob`** now requires `project_name` and responds `{ job_id, object, project_id }` (was a full job object).
267
450
  - **`recruiter.saveCandidate`** body is `{ stage_id, candidate_id }`.
268
451
 
269
- ### Changed (BREAKING) β€” account-first path grammar
452
+ ### Changed (BREAKING): account-first path grammar
270
453
 
271
- - **Every account-scoped method now carries `account_id` as the leading path segment** (`/v1/{account_id}/…`), never as a query parameter, body field, or omitted. Bind it once with `curviate.account(id)` and it is injected on every call. Companies and relations, which previously carried `account_id` as a query argument, drop it entirely.
454
+ - **Every account-scoped method now carries `account_id` as the leading path segment** (`/v1/{account_id}/...`), never as a query parameter, body field, or omitted. Bind it once with `curviate.account(id)` and it is injected on every call. Companies and relations, which previously carried `account_id` as a query argument, drop it entirely.
272
455
 
273
456
  ### Added
274
457
 
275
- - **`comments` namespace (9 methods):** `listUserComments`, `create`, `edit`, `delete`, `reply`, `listReplies`, `listReactions`, `addReaction`, `removeReaction` β€” `removeReaction` is a DELETE that carries a `{ reaction }` body.
458
+ - **`comments` namespace (9 methods):** `listUserComments`, `create`, `edit`, `delete`, `reply`, `listReplies`, `listReactions`, `addReaction`, `removeReaction`; `removeReaction` is a DELETE that carries a `{ reaction }` body.
276
459
  - **`jobs` write surface (9 new methods):** `list`, `create`, `update`, `getBudget`, `publish`, `close`, `listApplicants`, `getApplicant`, `downloadResume` (binary `ArrayBuffer`). `jobs.create` takes object-shaped `job_title` / `company` and a `method`-discriminated `apply_method`.
277
460
  - **Project-centric `recruiter` surface (9 new methods):** `searchTalentPool`, `searchFromUrl`, `updateProject`, `listPipeline`, `getProjectJob`, `createProjectJob`, `getProjectJobBudget`, `updateProjectJob`, `closeJob`, with `getApplicant` / `downloadResume` re-homed under the project scope.
278
461
  - **`users.follow` / `users.unfollow`** (bodyless POST / DELETE), **`users.listFollowing`**, **`users.update`**.
279
462
  - **`messaging.markChatRead(chatId, { read })`**.
280
- - **`search.fromUrl({ url })`** and **`salesNavigator.searchFromUrl({ url })`** β€” resolve a LinkedIn search-results URL into a search.
463
+ - **`search.fromUrl({ url })`** and **`salesNavigator.searchFromUrl({ url })`**: resolve a LinkedIn search-results URL into a search.
281
464
  - **`posts.delete`** (bodyless, 204) and **`posts.unreact(postId, { reaction })`** (DELETE with body).
282
- - **`users.get(userId)` accepts `'me'`** β€” `users.get('me')` reads the caller's own profile (folds in the old `getMe`).
283
- - **New `ErrorCode` value: `LINKEDIN_OPERATION_NOT_SUPPORTED`.** The `422` a permanent LinkedIn platform limitation for the attempted operation returns (e.g. listing a non-self user's following list) β€” `user_fixable: true`, `retry_likely_to_succeed: false` (not a transient failure; retrying will not help). Added to the `ErrorCode` union and the transport's known-code set β€” previously this narrowed to `INTERNAL`, which is retryable.
465
+ - **`users.get(userId)` accepts `'me'`**: `users.get('me')` reads the caller's own profile (folds in the old `getMe`).
466
+ - **New `ErrorCode` value: `LINKEDIN_OPERATION_NOT_SUPPORTED`.** The `422` a permanent LinkedIn platform limitation for the attempted operation returns (e.g. listing a non-self user's following list): `user_fixable: true`, `retry_likely_to_succeed: false` (not a transient failure; retrying will not help). Added to the `ErrorCode` union and the transport's known-code set; previously this narrowed to `INTERNAL`, which is retryable.
284
467
 
285
468
  ### Fixed
286
469
 
287
- - **`CONNECTION_REQUEST_CONFLICT` was silently downgraded to `INTERNAL`.** The `409` the API returns when a connect-request to a member already exists (or you are already a first-degree connection) is a real, documented error code, but the transport's runtime known-code set omitted it β€” so callers received `code: "INTERNAL"` with the wrong semantics (`INTERNAL` is retryable, whereas this conflict is `user_fixable: true`, `retry_likely_to_succeed: false` and must never be re-sent). The transport now decodes `CONNECTION_REQUEST_CONFLICT` to itself. To eliminate the class of bug: the `ErrorCode` type and the transport's runtime known-code set are now both **derived from one source array**, so the code a caller narrows on and the code the transport recognizes can never drift apart again.
288
- - **`RATE_LIMITED` was silently downgraded to `INTERNAL`.** The `429` the Recruiter and Sales Navigator read surface returns under LinkedIn-platform throttling (dedicated `RateLimit-Policy` / `RateLimit` / `Retry-After` response headers, `retry_likely_to_succeed: true`) is a real, documented error code, but the transport's runtime known-code set omitted it β€” so callers received `code: "INTERNAL"`, which happened to auto-retry on `GET`/`HEAD` by accident but with the wrong (generic backoff) delay instead of honoring `Retry-After`. The transport now decodes `RATE_LIMITED` to itself and retries it on `GET`/`HEAD` like the other rate-limit codes, honoring `Retry-After`.
289
- - **`users.update` forwarded an unsupported `description` key if a caller smuggled one in.** The `UserUpdateBody` type never declared `description`, so a typed caller was already rejected at compile time β€” but an untyped/JS caller (or an `as`-cast) could still put it on the object, and `update()` forwarded the body verbatim with no runtime check. `update()` now strips `description` from the outgoing payload before the request is sent, regardless of caller strictness.
470
+ - **`CONNECTION_REQUEST_CONFLICT` was silently downgraded to `INTERNAL`.** The `409` the API returns when a connect-request to a member already exists (or you are already a first-degree connection) is a real, documented error code, but the transport's runtime known-code set omitted it, so callers received `code: "INTERNAL"` with the wrong semantics (`INTERNAL` is retryable, whereas this conflict is `user_fixable: true`, `retry_likely_to_succeed: false` and must never be re-sent). The transport now decodes `CONNECTION_REQUEST_CONFLICT` to itself. To eliminate the class of bug: the `ErrorCode` type and the transport's runtime known-code set are now both **derived from one source array**, so the code a caller narrows on and the code the transport recognizes can never drift apart again.
471
+ - **`RATE_LIMITED` was silently downgraded to `INTERNAL`.** The `429` the Recruiter and Sales Navigator read surface returns under LinkedIn-platform throttling (dedicated `RateLimit-Policy` / `RateLimit` / `Retry-After` response headers, `retry_likely_to_succeed: true`) is a real, documented error code, but the transport's runtime known-code set omitted it, so callers received `code: "INTERNAL"`, which happened to auto-retry on `GET`/`HEAD` by accident but with the wrong (generic backoff) delay instead of honoring `Retry-After`. The transport now decodes `RATE_LIMITED` to itself and retries it on `GET`/`HEAD` like the other rate-limit codes, honoring `Retry-After`.
472
+ - **`users.update` forwarded an unsupported `description` key if a caller smuggled one in.** The `UserUpdateBody` type never declared `description`, so a typed caller was already rejected at compile time, but an untyped/JS caller (or an `as`-cast) could still put it on the object, and `update()` forwarded the body verbatim with no runtime check. `update()` now strips `description` from the outgoing payload before the request is sent, regardless of caller strictness.
290
473
 
291
474
  ---
292
475
 
293
- ## [0.14.1] β€” 2026-07-07
476
+ ## [0.14.1] - 2026-07-07
294
477
 
295
478
  ### Fixed
296
479
 
297
- - **`constructEvent` examples referenced a wrong header name (`X-Curviate-Signature` β†’ `Curviate-Signature`); corrected a stale default-count description.** The JSDoc and both code examples (Express/Node, Hono) for `constructEvent` named the header `X-Curviate-Signature`; the dispatcher actually sends `Curviate-Signature` (Node lowercases it to `curviate-signature` on `req.headers`), so integrators copying the examples verbatim got `undefined` for the signature header. Also corrected the generated `events` field description on the webhook-create schema from a stale "default: all 7" to "default: all 11 lifecycle events", matching the account_status catalogue.
480
+ - **`constructEvent` examples referenced a wrong header name (`X-Curviate-Signature` -> `Curviate-Signature`); corrected a stale default-count description.** The JSDoc and both code examples (Express/Node, Hono) for `constructEvent` named the header `X-Curviate-Signature`; the dispatcher actually sends `Curviate-Signature` (Node lowercases it to `curviate-signature` on `req.headers`), so integrators copying the examples verbatim got `undefined` for the signature header. Also corrected the generated `events` field description on the webhook-create schema from a stale "default: all 7" to "default: all 11 lifecycle events", matching the account_status catalogue.
298
481
 
299
482
  ---
300
483
 
301
- ## [0.14.0] β€” 2026-07-07
484
+ ## [0.14.0] - 2026-07-07
302
485
 
303
486
  Webhooks surface re-based onto the v2 catalogue. Additive minor: one new method,
304
487
  one type-only breaking note for `CurviateEvent` (see below).
305
488
 
306
489
  ### Added
307
490
 
308
- - **`webhooks.get(id)`.** Return a single webhook owned by the calling tenant (`GET /v1/webhooks/{id}`). The plaintext secret is never present on a read β€” only `secret_prefix`. Type `WebhookGetResult`. The `webhooks` namespace is now 7 methods (was 6).
309
- - **Webhook event catalogue expanded 21 β†’ 27** (`webhooks.listEvents()`), grouped messaging (8) / user (2) / account_status (14), plus 3 tier-gated. New subscribable-but-not-default events: `chat.updated`, `chat.deleted` (messaging), `connection.new` (user), `account.initial_sync.running` / `account.initial_sync.completed` / `account.initial_sync.failed` (account_status). Catalogue entries may now carry an `availability: "realtime" | "no_longer_realtime" | "not_realtime"` field.
491
+ - **`webhooks.get(id)`.** Return a single webhook owned by the calling tenant (`GET /v1/webhooks/{id}`). The plaintext secret is never present on a read, only `secret_prefix`. Type `WebhookGetResult`. The `webhooks` namespace is now 7 methods (was 6).
492
+ - **Webhook event catalogue expanded 21 -> 27** (`webhooks.listEvents()`), grouped messaging (8) / user (2) / account_status (14), plus 3 tier-gated. New subscribable-but-not-default events: `chat.updated`, `chat.deleted` (messaging), `connection.new` (user), `account.initial_sync.running` / `account.initial_sync.completed` / `account.initial_sync.failed` (account_status). Catalogue entries may now carry an `availability: "realtime" | "no_longer_realtime" | "not_realtime"` field.
310
493
 
311
494
  ### Changed (type-only breaking note)
312
495
 
313
- - **`CurviateEvent` union re-keyed 19 β†’ 24 deliverable events** to match the create-subscribable catalogue. Renamed/removed: `account.stopped`, `account.sync_started`, `account.sync_complete`, `account.creation_success`, `account.sync_success`, `account.reconnect_required`, `account.checkpoint` are gone; the account-lifecycle names now split across `account.synced`, `account.reconnected`, `account.reconnect_needed`, `account.paused`, `account.connecting`, `account.permission_revoked`. Net-new members: `chat.updated`, `chat.deleted`, `connection.new`, `account.initial_sync.running`, `account.initial_sync.completed`, `account.initial_sync.failed`. A `switch`/exhaustiveness check on `event.type` for any of the 7 removed names will now fail to compile β€” update the case list. Runtime HMAC verification (`constructEvent`) is unaffected; this is a types-only change.
496
+ - **`CurviateEvent` union re-keyed 19 -> 24 deliverable events** to match the create-subscribable catalogue. Renamed/removed: `account.stopped`, `account.sync_started`, `account.sync_complete`, `account.creation_success`, `account.sync_success`, `account.reconnect_required`, `account.checkpoint` are gone; the account-lifecycle names now split across `account.synced`, `account.reconnected`, `account.reconnect_needed`, `account.paused`, `account.connecting`, `account.permission_revoked`. Net-new members: `chat.updated`, `chat.deleted`, `connection.new`, `account.initial_sync.running`, `account.initial_sync.completed`, `account.initial_sync.failed`. A `switch`/exhaustiveness check on `event.type` for any of the 7 removed names will now fail to compile; update the case list. Runtime HMAC verification (`constructEvent`) is unaffected; this is a types-only change.
314
497
 
315
498
  ---
316
499
 
317
- ## [0.13.0] β€” 2026-07-05
500
+ ## [0.13.0] - 2026-07-05
318
501
 
319
502
  Accounts/Auth surface migration. This is a **breaking** minor (pre-1.0): the account
320
503
  connection and checkpoint surface was reshaped end-to-end. All changes are on the
@@ -322,123 +505,123 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
322
505
 
323
506
  ### Removed (BREAKING)
324
507
 
325
- - **`accounts.refresh(accountId)` removed** β€” the underlying endpoint (`POST /v1/accounts/{account_id}/refresh`) no longer exists and has no alias. Accounts now restart and re-sync automatically; status freshness is served by the real-time account-status webhook, the nightly reconcile, and `accounts.get()`'s stale-while-revalidate background refresh. Remove any `accounts.refresh()` call sites. Type `AccountRefreshResult` is removed.
326
- - **`accounts.submitCheckpoint(body)` removed** β€” renamed to `accounts.solveCheckpoint(accountId, { code })` (see below). No alias.
327
- - **`accounts.resendCheckpoint(body)` removed** β€” renamed to `accounts.requestCheckpoint(accountId)` (see below). No alias.
508
+ - **`accounts.refresh(accountId)` removed**: the underlying endpoint (`POST /v1/accounts/{account_id}/refresh`) no longer exists and has no alias. Accounts now restart and re-sync automatically; status freshness is served by the real-time account-status webhook, the nightly reconcile, and `accounts.get()`'s stale-while-revalidate background refresh. Remove any `accounts.refresh()` call sites. Type `AccountRefreshResult` is removed.
509
+ - **`accounts.submitCheckpoint(body)` removed**, renamed to `accounts.solveCheckpoint(accountId, { code })` (see below). No alias.
510
+ - **`accounts.resendCheckpoint(body)` removed**, renamed to `accounts.requestCheckpoint(accountId)` (see below). No alias.
328
511
 
329
512
  ### Changed (BREAKING)
330
513
 
331
514
  - **Checkpoint operations are now account-in-path.** The three checkpoint methods take the account id as a **path argument** instead of an `account_id` body field:
332
- - `submitCheckpoint({ account_id, code })` β†’ **`solveCheckpoint(account_id, { code })`** (`POST /v1/accounts/{account_id}/checkpoint/solve`). Returns the connected account (201) or a chained checkpoint (202).
333
- - `resendCheckpoint({ account_id })` β†’ **`requestCheckpoint(account_id)`** (`POST /v1/accounts/{account_id}/checkpoint/request`). Returns `{ object, account_id, resent }` β€” `resent` is still honest (`false` when there is nothing to re-send).
334
- - `pollCheckpoint({ account_id })` β†’ **`pollCheckpoint(account_id)`** (`POST /v1/accounts/{account_id}/checkpoint/poll`) β€” same name, now a single string arg (no body).
335
- - Migration: `submitCheckpoint({ account_id, code })` β†’ `solveCheckpoint(account_id, { code })`; `resendCheckpoint({ account_id })` β†’ `requestCheckpoint(account_id)`; `pollCheckpoint({ account_id })` β†’ `pollCheckpoint(account_id)`.
336
- - Types: `AccountSubmitCheckpointBody`/`AccountSubmitCheckpointResult` β†’ `AccountSolveCheckpointBody`/`AccountSolveCheckpointResult`; `AccountResendCheckpointBody`/`AccountResendCheckpointResult` β†’ `AccountRequestCheckpointResult` (no body type); `AccountPollCheckpointBody` removed (no body).
337
- - **`accounts.createConnectLink()` is create-only.** The `purpose` and `account_id` body fields are removed β€” it now only mints a link to connect a **new** account (`{ seat_id, expires_in_seconds?, redirect_url? }`). To re-authorize an existing account via a hosted link, use the new `accounts.createReconnectLink()` (below). Type `AccountConnectLinkBody` no longer carries `purpose`/`account_id`.
338
- - **`accounts.update()` body reshaped.** The managed `country` / `ip` knobs are removed from `PATCH /v1/accounts/{account_id}`; the body is now `{ metadata?, proxy? }`. `metadata` is a flat string map that **replaces** the account's custom-data store wholesale; `proxy` sets a custom egress proxy (the managed location is now chosen at connect time, not here). Passing `country`/`ip` is rejected with `INVALID_REQUEST`. Type `AccountUpdateBody` changed. *(Known limitation: the generated body type does not yet express `proxy: null` to clear a custom proxy β€” the server accepts it, but a strict TypeScript caller must cast until the schema surfaces the nullability. The CLI's `account update --clear-proxy` sends it directly.)*
515
+ - `submitCheckpoint({ account_id, code })` -> **`solveCheckpoint(account_id, { code })`** (`POST /v1/accounts/{account_id}/checkpoint/solve`). Returns the connected account (201) or a chained checkpoint (202).
516
+ - `resendCheckpoint({ account_id })` -> **`requestCheckpoint(account_id)`** (`POST /v1/accounts/{account_id}/checkpoint/request`). Returns `{ object, account_id, resent }`; `resent` is still honest (`false` when there is nothing to re-send).
517
+ - `pollCheckpoint({ account_id })` -> **`pollCheckpoint(account_id)`** (`POST /v1/accounts/{account_id}/checkpoint/poll`), same name, now a single string arg (no body).
518
+ - Migration: `submitCheckpoint({ account_id, code })` -> `solveCheckpoint(account_id, { code })`; `resendCheckpoint({ account_id })` -> `requestCheckpoint(account_id)`; `pollCheckpoint({ account_id })` -> `pollCheckpoint(account_id)`.
519
+ - Types: `AccountSubmitCheckpointBody`/`AccountSubmitCheckpointResult` -> `AccountSolveCheckpointBody`/`AccountSolveCheckpointResult`; `AccountResendCheckpointBody`/`AccountResendCheckpointResult` -> `AccountRequestCheckpointResult` (no body type); `AccountPollCheckpointBody` removed (no body).
520
+ - **`accounts.createConnectLink()` is create-only.** The `purpose` and `account_id` body fields are removed; it now only mints a link to connect a **new** account (`{ seat_id, expires_in_seconds?, redirect_url? }`). To re-authorize an existing account via a hosted link, use the new `accounts.createReconnectLink()` (below). Type `AccountConnectLinkBody` no longer carries `purpose`/`account_id`.
521
+ - **`accounts.update()` body reshaped.** The managed `country` / `ip` knobs are removed from `PATCH /v1/accounts/{account_id}`; the body is now `{ metadata?, proxy? }`. `metadata` is a flat string map that **replaces** the account's custom-data store wholesale; `proxy` sets a custom egress proxy (the managed location is now chosen at connect time, not here). Passing `country`/`ip` is rejected with `INVALID_REQUEST`. Type `AccountUpdateBody` changed. *(Known limitation: the generated body type does not yet express `proxy: null` to clear a custom proxy; the server accepts it, but a strict TypeScript caller must cast until the schema surfaces the nullability. The CLI's `account update --clear-proxy` sends it directly.)*
339
522
  - **Cookie auth requires `user_agent`.** `accounts.link()` and `accounts.reconnect()` now require a `user_agent` when `auth_method: "cookie"`; without one the request is rejected with `INVALID_REQUEST`. It stays optional for `auth_method: "credentials"`. (Enforced server-side; the flat request-body type cannot make it conditionally required, so pass it whenever you connect by cookie.)
340
- - **`accounts.reconnect()` result is now a `200 | 202` union.** A reconnect can itself surface a checkpoint challenge β€” resolve it with `solveCheckpoint` / `pollCheckpoint`, exactly like `accounts.link()`. Discriminate on `result.object`. Type `AccountReconnectResult` is now a union.
523
+ - **`accounts.reconnect()` result is now a `200 | 202` union.** A reconnect can itself surface a checkpoint challenge; resolve it with `solveCheckpoint` / `pollCheckpoint`, exactly like `accounts.link()`. Discriminate on `result.object`. Type `AccountReconnectResult` is now a union.
341
524
 
342
525
  ### Added
343
526
 
344
- - **`accounts.createReconnectLink(accountId, body?)`.** Mint a one-time hosted **re-authorization** link for an existing disconnected account (`POST /v1/accounts/{account_id}/reconnect-link`) β€” the hosted counterpart of `accounts.reconnect()`. Body is optional (`{ expires_in_seconds?, redirect_url? }`). Returns `{ object: "hosted_auth_url", url, session_id, expires_at, account_id }`; poll completion with `accounts.getConnectSession(session_id)`. Types: `AccountReconnectLinkBody`, `AccountReconnectLinkResult`. The `accounts` namespace stays at 12 methods (`refresh` out, `createReconnectLink` in).
345
- - **New `ErrorCode` value: `ACCOUNT_ALREADY_LINKED`.** The `409` a duplicate connect now returns from `accounts.link()`, `accounts.reconnect()`, and `accounts.solveCheckpoint()` β€” the substrate refused linking a LinkedIn identity that's already linked. `user_fixable: true`, `retry_likely_to_succeed: false` (reconnect or disconnect the existing account instead of retrying). When the caller's own tenant already owns the existing account, the error body's `account_id` names it; otherwise it stands alone. Added to the `ErrorCode` union and the transport's known-code set β€” previously this narrowed to `INTERNAL`, which is retryable, so a client would have retried a request that can never succeed.
527
+ - **`accounts.createReconnectLink(accountId, body?)`.** Mint a one-time hosted **re-authorization** link for an existing disconnected account (`POST /v1/accounts/{account_id}/reconnect-link`), the hosted counterpart of `accounts.reconnect()`. Body is optional (`{ expires_in_seconds?, redirect_url? }`). Returns `{ object: "hosted_auth_url", url, session_id, expires_at, account_id }`; poll completion with `accounts.getConnectSession(session_id)`. Types: `AccountReconnectLinkBody`, `AccountReconnectLinkResult`. The `accounts` namespace stays at 12 methods (`refresh` out, `createReconnectLink` in).
528
+ - **New `ErrorCode` value: `ACCOUNT_ALREADY_LINKED`.** The `409` a duplicate connect now returns from `accounts.link()`, `accounts.reconnect()`, and `accounts.solveCheckpoint()`; the substrate refused linking a LinkedIn identity that's already linked. `user_fixable: true`, `retry_likely_to_succeed: false` (reconnect or disconnect the existing account instead of retrying). When the caller's own tenant already owns the existing account, the error body's `account_id` names it; otherwise it stands alone. Added to the `ErrorCode` union and the transport's known-code set; previously this narrowed to `INTERNAL`, which is retryable, so a client would have retried a request that can never succeed.
346
529
  - **Wider checkpoint challenge vocabulary.** The 202 `challenge_type` enum now covers `otp | two_factor_sms | two_factor_app | two_factor_whatsapp | mobile_app_approval | otp_or_mobile_app_approval | contract_selection`, and a `contract_selection` challenge additionally carries `contracts: [{ id, name }]` (choose one and pass its id to `solveCheckpoint`).
347
- - **422 dead-end challenge error documents a `challenge_type`, but it isn't typed yet.** When a checkpoint challenge can't be resolved automatically (e.g. a CAPTCHA or a phone-number registration), the `422` response carries a machine-readable `challenge_type: "captcha" | "phone_register"` in prose (`fixtures/openapi.json`) β€” but the response schema is still the generic `Error` type, so a caller can't type-branch on it programmatically yet.
530
+ - **422 dead-end challenge error documents a `challenge_type`, but it isn't typed yet.** When a checkpoint challenge can't be resolved automatically (e.g. a CAPTCHA or a phone-number registration), the `422` response carries a machine-readable `challenge_type: "captcha" | "phone_register"` in prose (`fixtures/openapi.json`), but the response schema is still the generic `Error` type, so a caller can't type-branch on it programmatically yet.
348
531
  - **Connect-recovery + honest terminal signals on the connection responses (additive, non-breaking).**
349
- - `accounts.link()` and `accounts.solveCheckpoint()` 201 responses now carry an optional `recovered` boolean β€” `true` only when the connect reclaimed a LinkedIn identity already present on the workspace (claiming it into your account) rather than connecting a brand-new one; absent on a normal connect.
350
- - The `status` on those same 201 responses is widened from `"active"` to `"active" | "reconnect_needed" | "restricted" | "disconnected"` β€” it now reflects the account's real observed state, which a recovered identity often reports as needing a reconnect.
532
+ - `accounts.link()` and `accounts.solveCheckpoint()` 201 responses now carry an optional `recovered` boolean, `true` only when the connect reclaimed a LinkedIn identity already present on the workspace (claiming it into your account) rather than connecting a brand-new one; absent on a normal connect.
533
+ - The `status` on those same 201 responses is widened from `"active"` to `"active" | "reconnect_needed" | "restricted" | "disconnected"`; it now reflects the account's real observed state, which a recovered identity often reports as needing a reconnect.
351
534
  - `accounts.pollCheckpoint()` now carries `challenge_type` (`"mobile_app_approval"`) and a human-readable `recovery_hint` on a `status: "expired"` mobile-approval timeout, so a client can render the right recovery guidance without parsing prose.
352
535
 
353
536
  ### Changed
354
537
 
355
- - Regenerated types from the current API surface. `accounts.get()` / `accounts.list()` still return the six cached enrichment fields, but `username`, `premium_id`, `public_identifier`, `signatures`, and `groups` are no longer refreshed by background enrichment β€” they read `null`/`[]` for newly connected accounts (any previously cached value is retained). `full_name` and `substrate_created_at` continue to populate; `substrate_created_at` remains ISO-8601 UTC.
538
+ - Regenerated types from the current API surface. `accounts.get()` / `accounts.list()` still return the six cached enrichment fields, but `username`, `premium_id`, `public_identifier`, `signatures`, and `groups` are no longer refreshed by background enrichment; they read `null`/`[]` for newly connected accounts (any previously cached value is retained). `full_name` and `substrate_created_at` continue to populate; `substrate_created_at` remains ISO-8601 UTC.
356
539
 
357
- ## [0.12.0] β€” 2026-07-05
540
+ ## [0.12.0] - 2026-07-05
358
541
 
359
542
  ### Added
360
543
 
361
- - **New `companies` namespace (5 methods).** `companies.get(identifier)` retrieves a company's full LinkedIn profile β€” accepts either a public handle (the slug in `linkedin.com/company/<handle>`, e.g. `"t-systems"`) or a numeric id. `companies.employees(identifier, params?)`, `companies.posts(identifier, params?)`, and `companies.jobs(identifier, params?)` list company sub-resources (filterable with `keywords`/`location` where supported); `companies.followers(identifier, params?)` lists company followers (requires the acting account to administer the target page). The four sub-resource methods require the company's **numeric provider_id** (the same `id` field `companies.get()` returns) β€” a handle or URN is rejected server-side before any upstream call. Types: `CompanyProfile`, `CompanyEmployeeListPage`, `CompanyPostListPage`, `CompanyJobListPage`, `CompanyFollowerListPage`.
362
- - **New `ErrorCode` value: `RESOURCE_ACCESS_RESTRICTED`.** The non-admin mapping for `companies.followers()` β€” surfaced when the acting account does not administer the target company page. Added to the `ErrorCode` union and the transport's known-code set (previously unknown codes silently narrowed to `INTERNAL`).
363
- - **`salesNavigator` gains 5 new v2 list-surface methods (7β†’12).** `salesNavigator.accountLists(query?)` and `salesNavigator.leadLists(query?)` list the operator's saved-account/saved-lead lists (`account_id` required, `limit`/`cursor` paginate). `salesNavigator.browseAccountList(listId, body?, query?)` and `salesNavigator.browseLeadList(listId, body?, query?)` return the saved items in one list, with optional enum filters (`filter`/`sort_by`/`sort_order` for accounts; `spotlight`/`sort_by`/`sort_order` for leads) in the body. `salesNavigator.saveAccount({ list_id, company_id, account_id })` saves a company into an account list β€” a `2xx` response **is** the success signal; no `saved` boolean is invented. All five are additive. Types: `SNAccountListsQuery/Result`, `SNLeadListsQuery/Result`, `SNBrowseAccountListQuery/Body/Result`, `SNBrowseLeadListQuery/Body/Result`, `SNSaveAccountBody/Input/Result`.
544
+ - **New `companies` namespace (5 methods).** `companies.get(identifier)` retrieves a company's full LinkedIn profile, and accepts either a public handle (the slug in `linkedin.com/company/<handle>`, e.g. `"t-systems"`) or a numeric id. `companies.employees(identifier, params?)`, `companies.posts(identifier, params?)`, and `companies.jobs(identifier, params?)` list company sub-resources (filterable with `keywords`/`location` where supported); `companies.followers(identifier, params?)` lists company followers (requires the acting account to administer the target page). The four sub-resource methods require the company's **numeric provider_id** (the same `id` field `companies.get()` returns); a handle or URN is rejected server-side before any upstream call. Types: `CompanyProfile`, `CompanyEmployeeListPage`, `CompanyPostListPage`, `CompanyJobListPage`, `CompanyFollowerListPage`.
545
+ - **New `ErrorCode` value: `RESOURCE_ACCESS_RESTRICTED`.** The non-admin mapping for `companies.followers()`, surfaced when the acting account does not administer the target company page. Added to the `ErrorCode` union and the transport's known-code set (previously unknown codes silently narrowed to `INTERNAL`).
546
+ - **`salesNavigator` gains 5 new v2 list-surface methods (7->12).** `salesNavigator.accountLists(query?)` and `salesNavigator.leadLists(query?)` list the operator's saved-account/saved-lead lists (`account_id` required, `limit`/`cursor` paginate). `salesNavigator.browseAccountList(listId, body?, query?)` and `salesNavigator.browseLeadList(listId, body?, query?)` return the saved items in one list, with optional enum filters (`filter`/`sort_by`/`sort_order` for accounts; `spotlight`/`sort_by`/`sort_order` for leads) in the body. `salesNavigator.saveAccount({ list_id, company_id, account_id })` saves a company into an account list; a `2xx` response **is** the success signal; no `saved` boolean is invented. All five are additive. Types: `SNAccountListsQuery/Result`, `SNLeadListsQuery/Result`, `SNBrowseAccountListQuery/Body/Result`, `SNBrowseLeadListQuery/Body/Result`, `SNSaveAccountBody/Input/Result`.
364
547
 
365
548
  ### Removed (BREAKING)
366
549
 
367
- - **`profiles.getCompany(companyId)` removed** β€” hard-moved to `companies.get(identifier)`. The underlying endpoint (`GET /v1/profiles/companies/{company_id}`) no longer exists; there is no alias. Update call sites to `companies.get()`.
550
+ - **`profiles.getCompany(companyId)` removed**, hard-moved to `companies.get(identifier)`. The underlying endpoint (`GET /v1/profiles/companies/{company_id}`) no longer exists; there is no alias. Update call sites to `companies.get()`.
368
551
 
369
552
  ### Changed (BREAKING)
370
553
 
371
- - **`salesNavigator.saveLead` re-signed for the v2 save-lead surface.** The v1 `saveLead(userId, { account_id, list_id? })` (`POST /v1/sales-navigator/leads/{user_id}`) is **retired, no alias** β€” the endpoint itself no longer exists server-side. The replacement `saveLead({ list_id, user_id, account_id })` calls `POST /v1/sales-navigator/lead-lists/{list_id}/save`; `list_id` is now **mandatory** (the v1 `list_id`-optional semantics do not exist in v2) and addresses the path instead of the member id. Update call sites: `saveLead(userId, { account_id, list_id })` β†’ `saveLead({ list_id, user_id, account_id })`. Types: `SNSaveLeadBody`/`SNSaveLeadResult` now alias the v2 endpoint; new `SNSaveLeadInput`.
554
+ - **`salesNavigator.saveLead` re-signed for the v2 save-lead surface.** The v1 `saveLead(userId, { account_id, list_id? })` (`POST /v1/sales-navigator/leads/{user_id}`) is **retired, no alias**; the endpoint itself no longer exists server-side. The replacement `saveLead({ list_id, user_id, account_id })` calls `POST /v1/sales-navigator/lead-lists/{list_id}/save`; `list_id` is now **mandatory** (the v1 `list_id`-optional semantics do not exist in v2) and addresses the path instead of the member id. Update call sites: `saveLead(userId, { account_id, list_id })` -> `saveLead({ list_id, user_id, account_id })`. Types: `SNSaveLeadBody`/`SNSaveLeadResult` now alias the v2 endpoint; new `SNSaveLeadInput`.
372
555
 
373
- ## [0.11.0] β€” 2026-07-04
556
+ ## [0.11.0] - 2026-07-04
374
557
 
375
558
  ### Added
376
559
 
377
- - **`accounts.getConnectSession(session_id)`.** Poll a hosted connect session minted by `accounts.createConnectLink()`. A pure status read β€” it makes no external call and does not itself complete the connection (the connection is signalled complete out-of-band once the end user finishes the hosted flow). Returns `{ object: "connect_session", session_id, status, account_id, expires_at }`, where `status` is `"pending" | "resolved" | "expired" | "failed"` and `account_id` is populated only once `status` is `"resolved"`. Poll until it leaves `pending`. Type: `AccountConnectSessionResult`. Extends the Accounts surface to 12 methods.
378
- - **`accounts.resendCheckpoint({ account_id })`.** Re-sends the pending verification challenge notification for an account (e.g. when the end user says they never received the OTP/2FA code or the mobile-app push). Returns `{ object: "checkpoint", account_id, resent }` β€” `resent` echoes the outcome honestly (`false` when there was nothing to re-send for that challenge type; this never throws just because a resend wasn't applicable). Does not reset the checkpoint's expiry.
379
- - **`session_id` on the `accounts.createConnectLink()` 201 response** β€” the durable poll handle to pass to `accounts.getConnectSession()`. Type: `AccountConnectLinkResult`.
380
- - **`seat_id` on the account-connection responses.** `accounts.link()` (201), `accounts.submitCheckpoint()` (201), and `accounts.pollCheckpoint()` now carry `seat_id` (`string | null`) β€” the seat the account occupies. `accounts.createConnectLink()` (201) also carries it. This is the canonical replacement for the deprecated `attached_seat_id` (same value).
381
- - **`auth_method: "hosted"`** is now a possible value on `accounts.list()` items (accounts connected through a hosted link). Response-only β€” the `link()` / `reconnect()` request `auth_method` remains `"credentials" | "cookie"`.
382
- - **`"disconnected"` account status.** Now a possible `status` on `accounts.list()` items, `accounts.get()`, and both sides of the account `state-diff` event β€” whose `previous_status` / `current_status` are now typed enums (`"active" | "reconnect_needed" | "restricted" | "connecting" | "disconnected"`) rather than free-form strings.
560
+ - **`accounts.getConnectSession(session_id)`.** Poll a hosted connect session minted by `accounts.createConnectLink()`. A pure status read; it makes no external call and does not itself complete the connection (the connection is signalled complete out-of-band once the end user finishes the hosted flow). Returns `{ object: "connect_session", session_id, status, account_id, expires_at }`, where `status` is `"pending" | "resolved" | "expired" | "failed"` and `account_id` is populated only once `status` is `"resolved"`. Poll until it leaves `pending`. Type: `AccountConnectSessionResult`. Extends the Accounts surface to 12 methods.
561
+ - **`accounts.resendCheckpoint({ account_id })`.** Re-sends the pending verification challenge notification for an account (e.g. when the end user says they never received the OTP/2FA code or the mobile-app push). Returns `{ object: "checkpoint", account_id, resent }`; `resent` echoes the outcome honestly (`false` when there was nothing to re-send for that challenge type; this never throws just because a resend wasn't applicable). Does not reset the checkpoint's expiry.
562
+ - **`session_id` on the `accounts.createConnectLink()` 201 response**, the durable poll handle to pass to `accounts.getConnectSession()`. Type: `AccountConnectLinkResult`.
563
+ - **`seat_id` on the account-connection responses.** `accounts.link()` (201), `accounts.submitCheckpoint()` (201), and `accounts.pollCheckpoint()` now carry `seat_id` (`string | null`), the seat the account occupies. `accounts.createConnectLink()` (201) also carries it. This is the canonical replacement for the deprecated `attached_seat_id` (same value).
564
+ - **`auth_method: "hosted"`** is now a possible value on `accounts.list()` items (accounts connected through a hosted link). Response-only; the `link()` / `reconnect()` request `auth_method` remains `"credentials" | "cookie"`.
565
+ - **`"disconnected"` account status.** Now a possible `status` on `accounts.list()` items, `accounts.get()`, and both sides of the account `state-diff` event, whose `previous_status` / `current_status` are now typed enums (`"active" | "reconnect_needed" | "restricted" | "connecting" | "disconnected"`) rather than free-form strings.
383
566
 
384
567
  ### Deprecated
385
568
 
386
- - **`attached_seat_id`** (on `accounts.link()`, `accounts.submitCheckpoint()`, and `accounts.pollCheckpoint()` responses) β€” use `seat_id` instead (identical value). Retained for backward compatibility; slated for removal at the GA `/v1` cutover.
569
+ - **`attached_seat_id`** (on `accounts.link()`, `accounts.submitCheckpoint()`, and `accounts.pollCheckpoint()` responses); use `seat_id` instead (identical value). Retained for backward compatibility; slated for removal at the GA `/v1` cutover.
387
570
 
388
571
  ### Changed
389
572
 
390
- - Regenerated types from the current API surface (full refresh). Beyond the additions above, `accounts.update()` (`PATCH /v1/accounts/{account_id}`) gains a `501` response variant, returned when a managed-proxy configuration isn't supported for the account's current plan (not retryable as-is β€” change the request rather than resubmitting). No resource method signatures changed.
573
+ - Regenerated types from the current API surface (full refresh). Beyond the additions above, `accounts.update()` (`PATCH /v1/accounts/{account_id}`) gains a `501` response variant, returned when a managed-proxy configuration isn't supported for the account's current plan (not retryable as-is; change the request rather than resubmitting). No resource method signatures changed.
391
574
 
392
- ## [0.10.0] β€” 2026-07-03
575
+ ## [0.10.0] - 2026-07-03
393
576
 
394
577
  ### Added
395
578
 
396
- - **New `jobs` namespace.** `jobs.get(jobIdOrUrl)` retrieves one public LinkedIn job posting's full detail β€” title, company, location, description, applicant count, and more. Accepts either a bare numeric job id (e.g. `"4428113858"`) or a full job URL (`"https://www.linkedin.com/jobs/view/4428113858"`) β€” the SDK extracts the numeric id client-side, so both forms issue the identical request. Passing a value with no extractable numeric id throws `CurviateError({ code: "INVALID_REQUEST" })` synchronously, before any network call. Type: `JobPosting`.
397
- - `recruiter.getJob(jobIdOrUrl)` β€” the Recruiter-lens sibling, retrieving any public job posting (not only the operator's own postings). Accepts the same bare-id-or-URL forms as `jobs.get()` and returns the identical `JobPosting` type (no separate response shape to learn). Extends the Recruiter surface to 18 methods.
579
+ - **New `jobs` namespace.** `jobs.get(jobIdOrUrl)` retrieves one public LinkedIn job posting's full detail: title, company, location, description, applicant count, and more. Accepts either a bare numeric job id (e.g. `"4428113858"`) or a full job URL (`"https://www.linkedin.com/jobs/view/4428113858"`); the SDK extracts the numeric id client-side, so both forms issue the identical request. Passing a value with no extractable numeric id throws `CurviateError({ code: "INVALID_REQUEST" })` synchronously, before any network call. Type: `JobPosting`.
580
+ - `recruiter.getJob(jobIdOrUrl)`: the Recruiter-lens sibling, retrieving any public job posting (not only the operator's own postings). Accepts the same bare-id-or-URL forms as `jobs.get()` and returns the identical `JobPosting` type (no separate response shape to learn). Extends the Recruiter surface to 18 methods.
398
581
 
399
- ## [0.9.0] β€” 2026-07-03
582
+ ## [0.9.0] - 2026-07-03
400
583
 
401
584
  ### Added
402
585
 
403
- - **Account enrichment fields.** `accounts.list()` items and `accounts.get()` now carry six cached account-detail fields, populated by an async background enrichment on every successful account activation: `username`, `premium_id`, `public_identifier`, `substrate_created_at` (ISO-8601 UTC), `signatures` (`{title, content}[]`), and `groups` (`string[]`). All six are `null`/`[]` until the account's first enrichment pass completes β€” never `undefined`, never a missing key. Types: `AccountListPage`, `AccountDetail`.
404
- - `accounts.get()` gains `seat_id` (`string | null`) β€” the seat the account occupies, `null` for an admin seatless account. Previously only `accounts.list()` items carried this field.
586
+ - **Account enrichment fields.** `accounts.list()` items and `accounts.get()` now carry six cached account-detail fields, populated by an async background enrichment on every successful account activation: `username`, `premium_id`, `public_identifier`, `substrate_created_at` (ISO-8601 UTC), `signatures` (`{title, content}[]`), and `groups` (`string[]`). All six are `null`/`[]` until the account's first enrichment pass completes, never `undefined`, never a missing key. Types: `AccountListPage`, `AccountDetail`.
587
+ - `accounts.get()` gains `seat_id` (`string | null`), the seat the account occupies, `null` for an admin seatless account. Previously only `accounts.list()` items carried this field.
405
588
 
406
589
  ### Changed
407
590
 
408
- - `connected_at` (on both `accounts.list()` items and `accounts.get()`) and `last_checked_at` (`accounts.get()`) now consistently emit ISO-8601 UTC (`…Z`) timestamps β€” a prior docs-vs-runtime drift meant these could reach callers in raw Postgres wire format. No type change (already typed as `string`/`string | null`); this is a runtime-correctness fix reflected in the regenerated example values.
409
- - Regenerated types from the current API surface. No resource method signatures changed β€” purely additive response fields.
410
- - `quotas[]` (on `accounts.get()`) is now documented as advisory usage-safety recommendations: daily families never cause a rejected request; only `account.per_minute` is a binding limit enforced with HTTP 429. `recommended_throttle_hint` semantics documented per level (`none` / `slow_down` / `backoff` are advisory; `stop` is reserved for the binding per-minute limit). JSDoc-only β€” no type shape change.
591
+ - `connected_at` (on both `accounts.list()` items and `accounts.get()`) and `last_checked_at` (`accounts.get()`) now consistently emit ISO-8601 UTC (`...Z`) timestamps; a prior docs-vs-runtime drift meant these could reach callers in raw Postgres wire format. No type change (already typed as `string`/`string | null`); this is a runtime-correctness fix reflected in the regenerated example values.
592
+ - Regenerated types from the current API surface. No resource method signatures changed; purely additive response fields.
593
+ - `quotas[]` (on `accounts.get()`) is now documented as advisory usage-safety recommendations: daily families never cause a rejected request; only `account.per_minute` is a binding limit enforced with HTTP 429. `recommended_throttle_hint` semantics documented per level (`none` / `slow_down` / `backoff` are advisory; `stop` is reserved for the binding per-minute limit). JSDoc-only, no type shape change.
411
594
 
412
595
  ---
413
596
 
414
- ## [0.8.0] β€” 2026-07-02
597
+ ## [0.8.0] - 2026-07-02
415
598
 
416
599
  ### Added
417
600
 
418
- - **Recruiter job-lifecycle endpoints are now fully implemented server-side.** The following operations no longer return `501` and are safe to call in production: getting/rejecting an applicant, downloading an applicant's resume, listing a job's applicants, solving a job's publish checkpoint, publishing a job, and fetching a Recruiter profile. Their generated response types no longer include a `501` variant β€” if your code branched on it, that branch is now dead and can be removed.
601
+ - **Recruiter job-lifecycle endpoints are now fully implemented server-side.** The following operations no longer return `501` and are safe to call in production: getting/rejecting an applicant, downloading an applicant's resume, listing a job's applicants, solving a job's publish checkpoint, publishing a job, and fetching a Recruiter profile. Their generated response types no longer include a `501` variant; if your code branched on it, that branch is now dead and can be removed.
419
602
  - Richer parameter documentation across the Recruiter and search surfaces: job/applicant/hiring-project IDs now describe where to obtain them (e.g. `job_id` from `GET /v1/recruiter/jobs`, `user_id` from a people-search result), and several previously-terse descriptions were corrected (e.g. the saved-search `industry` filter's cross-reference).
420
- - Server-side parameter defaults are now surfaced in the generated JSDoc (`@default`) wherever the API applies one when a param is omitted β€” link-expiry seconds, chat visibility, hiring-project list `limit`, webhook delivery `format`/`enabled`/`headers`/`data`, and more.
421
- - **Breaking (typed consumers):** the Sales Navigator company-search `annual_revenue.min`/`max` filter is now a bucketed numeric enum (`0 | 0.2 | 1 | 2.5 | 5 | 10 | 20 | 50 | 100 | 500 | 1000 | 1001`) instead of a free-form `number`, matching what the API actually accepts. Code passing arbitrary numbers no longer typechecks β€” pick the nearest bucket value. Runtime behavior is unchanged (the API already only accepted these buckets).
603
+ - Server-side parameter defaults are now surfaced in the generated JSDoc (`@default`) wherever the API applies one when a param is omitted: link-expiry seconds, chat visibility, hiring-project list `limit`, webhook delivery `format`/`enabled`/`headers`/`data`, and more.
604
+ - **Breaking (typed consumers):** the Sales Navigator company-search `annual_revenue.min`/`max` filter is now a bucketed numeric enum (`0 | 0.2 | 1 | 2.5 | 5 | 10 | 20 | 50 | 100 | 500 | 1000 | 1001`) instead of a free-form `number`, matching what the API actually accepts. Code passing arbitrary numbers no longer typechecks; pick the nearest bucket value. Runtime behavior is unchanged (the API already only accepted these buckets).
422
605
 
423
606
  ### Changed
424
607
 
425
- - **Search filter guard-rails are now documented.** `search.companies` `limit` requires a minimum of 2 (was 1) β€” the API rejects single-result company searches. `search.jobs` `benefits` and `commitments` filters now document their accepted values. `search.people` `open_to` now documents its accepted values (`proBono`, `boardMember`). The `search.posts` request examples were corrected to use the actual `member`/`company` array filter shape instead of a `member_urn` string.
608
+ - **Search filter guard-rails are now documented.** `search.companies` `limit` requires a minimum of 2 (was 1); the API rejects single-result company searches. `search.jobs` `benefits` and `commitments` filters now document their accepted values. `search.people` `open_to` now documents its accepted values (`proBono`, `boardMember`). The `search.posts` request examples were corrected to use the actual `member`/`company` array filter shape instead of a `member_urn` string.
426
609
  - Regenerated types from the current API surface. No resource method signatures changed.
427
610
 
428
611
  ---
429
612
 
430
- ## [0.7.0] β€” 2026-07-01
613
+ ## [0.7.0] - 2026-07-01
431
614
 
432
615
  ### Changed
433
616
 
434
- - **Breaking (typed consumers):** `recruiter.startChat` 201 response β€” the `attendee_ids` field is removed and replaced by `message_id` (the opening message identifier). Final shape: `{ object: "chat_started", chat_id, message_id }`, matching `messaging.startChat` and `salesNavigator.startChat`. `chat_id` is now non-nullable on a 201. Type: `RecruiterStartChatResult`. Migration: read `res.message_id`.
435
- - *Why:* product-API + core / Sales Navigator parity β€” the underlying LinkedIn start-chat operation returns an identical `{ object, chat_id, message_id }` for every product; the prior `attendee_ids` echo had no product-API basis.
617
+ - **Breaking (typed consumers):** `recruiter.startChat` 201 response: the `attendee_ids` field is removed and replaced by `message_id` (the opening message identifier). Final shape: `{ object: "chat_started", chat_id, message_id }`, matching `messaging.startChat` and `salesNavigator.startChat`. `chat_id` is now non-nullable on a 201. Type: `RecruiterStartChatResult`. Migration: read `res.message_id`.
618
+ - *Why:* product-API + core / Sales Navigator parity: the underlying LinkedIn start-chat operation returns an identical `{ object, chat_id, message_id }` for every product; the prior `attendee_ids` echo had no product-API basis.
436
619
  - The `recruiter.startChat` request body is unchanged (`attendees_ids` plus all recruiter-specific params).
437
620
  - Regenerated types from the updated API surface.
438
621
 
439
622
  ---
440
623
 
441
- ## [0.6.0] β€” 2026-07-01
624
+ ## [0.6.0] - 2026-07-01
442
625
 
443
626
  ### Added
444
627
 
@@ -446,19 +629,19 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
446
629
 
447
630
  ### Changed
448
631
 
449
- - **Breaking (typed consumers):** `salesNavigator.syncMessages` and `recruiter.syncMessages` β€” the 200 response field `sync_status` is renamed to `status`. Enum values (`sync_started | running | done | error`) are unchanged. Types: `SNSyncMessagesResult`, `RecruiterSyncMessagesResult`.
450
- - **Breaking (typed consumers):** `recruiter.startChat` request body field `attendee_ids` β†’ `attendees_ids`. The **response** `attendee_ids` field is unchanged. Type: `RecruiterStartChatBody`.
451
- - Recruiter start-chat 201 response no longer surfaces a separate `quota` field β€” remaining InMail capacity is read from `GET /v1/accounts/{account_id}`.
632
+ - **Breaking (typed consumers):** `salesNavigator.syncMessages` and `recruiter.syncMessages`: the 200 response field `sync_status` is renamed to `status`. Enum values (`sync_started | running | done | error`) are unchanged. Types: `SNSyncMessagesResult`, `RecruiterSyncMessagesResult`.
633
+ - **Breaking (typed consumers):** `recruiter.startChat` request body field `attendee_ids` -> `attendees_ids`. The **response** `attendee_ids` field is unchanged. Type: `RecruiterStartChatBody`.
634
+ - Recruiter start-chat 201 response no longer surfaces a separate `quota` field; remaining InMail capacity is read from `GET /v1/accounts/{account_id}`.
452
635
  - Regenerated types from the updated API surface (SN/Recruiter messaging parity).
453
636
 
454
637
  ---
455
638
 
456
- ## [0.5.0] β€” 2026-07-01
639
+ ## [0.5.0] - 2026-07-01
457
640
 
458
641
  ### Added
459
642
 
460
643
  - Posts search gains nested `posted_by`, `mentioning`, and `author` filter objects. `posted_by` and `mentioning` take `member` / `company` arrays of opaque IDs (from `GET /v1/search/parameters`); `posted_by` also accepts `me`, `first_connections`, and `people_you_follow` booleans; `author` filters by `industry`, `company`, or `keywords`.
461
- - Search-parameters items (`GET /v1/search/parameters`) now carry nullable company-disambiguation fields β€” `industry`, `location`, `headcount` (human-readable size range), and `followers_count` β€” populated only for `type=COMPANY` results.
644
+ - Search-parameters items (`GET /v1/search/parameters`) now carry nullable company-disambiguation fields: `industry`, `location`, `headcount` (human-readable size range), and `followers_count`, populated only for `type=COMPANY` results.
462
645
 
463
646
  ### Changed
464
647
 
@@ -471,11 +654,11 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
471
654
 
472
655
  ---
473
656
 
474
- ## [0.4.2] β€” 2026-07-01
657
+ ## [0.4.2] - 2026-07-01
475
658
 
476
659
  ### Added
477
660
 
478
- - Invitation item `specifics` now includes `provider: "LINKEDIN"` on both `InvitationSent` and `InvitationReceived` β€” the platform the invitation belongs to, passed through alongside `shared_secret`.
661
+ - Invitation item `specifics` now includes `provider: "LINKEDIN"` on both `InvitationSent` and `InvitationReceived`, the platform the invitation belongs to, passed through alongside `shared_secret`.
479
662
 
480
663
  ### Changed
481
664
 
@@ -484,19 +667,19 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
484
667
 
485
668
  ---
486
669
 
487
- ## [0.4.1] β€” 2026-06-30
670
+ ## [0.4.1] - 2026-06-30
488
671
 
489
672
  ### Added
490
673
 
491
- - `people_search_result` item now includes `id: string` β€” the raw LinkedIn provider id for the person (e.g. `ACoAA…` format). This is the first property on the item type.
674
+ - `people_search_result` item now includes `id: string`, the raw LinkedIn provider id for the person (e.g. `ACoAA...` format). This is the first property on the item type.
492
675
 
493
676
  ---
494
677
 
495
- ## [0.4.0] β€” 2026-06-30
678
+ ## [0.4.0] - 2026-06-30
496
679
 
497
680
  ### Added
498
681
 
499
- - `primary_locale` (`{ country, language } | null`) on the `Profile` and `OwnProfile` types β€” a profile's primary locale as set by LinkedIn (`language` is a BCP 47 tag, `country` an ISO 3166-1 alpha-2 code). Present on `GET /v1/profiles/{id}`; on `/me` it is populated when `linkedin_sections` is supplied.
682
+ - `primary_locale` (`{ country, language } | null`) on the `Profile` and `OwnProfile` types, a profile's primary locale as set by LinkedIn (`language` is a BCP 47 tag, `country` an ISO 3166-1 alpha-2 code). Present on `GET /v1/profiles/{id}`; on `/me` it is populated when `linkedin_sections` is supplied.
500
683
 
501
684
  ### Changed
502
685
 
@@ -505,11 +688,11 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
505
688
 
506
689
  ---
507
690
 
508
- ## [0.3.0] β€” 2026-06-29
691
+ ## [0.3.0] - 2026-06-29
509
692
 
510
693
  ### Added
511
694
 
512
- - `sendInMail` now accepts `surface: "classic"` for sending InMail from an account's own premium InMail credits (in addition to `"sales_nav"` and `"recruiter"`). The `recipient_urn` field accepts either a member URN (`urn:li:member:<id>`) or a member provider id (`ACo…`).
695
+ - `sendInMail` now accepts `surface: "classic"` for sending InMail from an account's own premium InMail credits (in addition to `"sales_nav"` and `"recruiter"`). The `recipient_urn` field accepts either a member URN (`urn:li:member:<id>`) or a member provider id (`ACo...`).
513
696
 
514
697
  ### Changed
515
698
 
@@ -517,15 +700,15 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
517
700
 
518
701
  ---
519
702
 
520
- ## [0.2.1] β€” 2026-06-29
703
+ ## [0.2.1] - 2026-06-29
521
704
 
522
705
  ### Fixed
523
706
 
524
- - `deleteMessage` and `addReaction` no longer inject `account_id` into the request. The server resolves the owning account from the message id β€” sending a client-supplied `account_id` was rejected by the server's strict schema, making both methods unusable via `client.account(id)`. The server's schemas are also relaxed (patch on the server side) so existing SDK installs continue to work without updating.
707
+ - `deleteMessage` and `addReaction` no longer inject `account_id` into the request. The server resolves the owning account from the message id; sending a client-supplied `account_id` was rejected by the server's strict schema, making both methods unusable via `client.account(id)`. The server's schemas are also relaxed (patch on the server side) so existing SDK installs continue to work without updating.
525
708
 
526
709
  ---
527
710
 
528
- ## [0.2.0] β€” 2026-06-28
711
+ ## [0.2.0] - 2026-06-28
529
712
 
530
713
  ### Added
531
714
 
@@ -539,127 +722,127 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
539
722
 
540
723
  ---
541
724
 
542
- ## [0.1.1] β€” 2026-06-22
725
+ ## [0.1.1] - 2026-06-22
543
726
 
544
727
  ### Fixed
545
728
 
546
- - Account-scoped write requests now send the account identifier in the request body where the API expects it. Previously the account identifier was always sent as a query parameter, regardless of the operation, which caused account-scoped write requests (sending invitations and InMail, posting and commenting, reacting, endorsing, saving leads, and the Recruiter pipeline writes) to be rejected. Read requests, body-less deletes, and filter-search requests are unaffected β€” they continue to carry the account identifier as a query parameter, matching the API. An account identifier you pass explicitly in a request still takes precedence and is never overwritten.
729
+ - Account-scoped write requests now send the account identifier in the request body where the API expects it. Previously the account identifier was always sent as a query parameter, regardless of the operation, which caused account-scoped write requests (sending invitations and InMail, posting and commenting, reacting, endorsing, saving leads, and the Recruiter pipeline writes) to be rejected. Read requests, body-less deletes, and filter-search requests are unaffected; they continue to carry the account identifier as a query parameter, matching the API. An account identifier you pass explicitly in a request still takes precedence and is never overwritten.
547
730
 
548
731
  ---
549
732
 
550
- ## [0.1.0] β€” 2026-06-21
733
+ ## [0.1.0] - 2026-06-21
551
734
 
552
735
  Initial public release.
553
736
 
554
737
  ### Added
555
738
 
556
739
  **Client and auth**
557
- - `new Curviate({ apiKey })` β€” configures the SDK with a Bearer API key, base URL (`https://api.curviate.com`), timeout, and max retries.
558
- - `curviate.account(accountId)` β€” returns an account-scoped accessor; all LinkedIn operations are tied to a managed account.
740
+ - `new Curviate({ apiKey })`: configures the SDK with a Bearer API key, base URL (`https://api.curviate.com`), timeout, and max retries.
741
+ - `curviate.account(accountId)`: returns an account-scoped accessor; all LinkedIn operations are tied to a managed account.
559
742
 
560
743
  **Resource surface**
561
744
 
562
745
  - `curviate.accounts.*`:
563
- - `list(params?)` β€” list managed accounts with optional `limit` and `cursor`.
564
- - `get(accountId)` β€” fetch a single managed account.
565
- - `link(body)` β€” connect a LinkedIn account via cookie or API key; returns an account or a checkpoint.
566
- - `reconnect(accountId, body)` β€” reconnect a disconnected account.
567
- - `refresh(accountId)` β€” force a session refresh.
568
- - `update(accountId, body)` β€” update account settings.
569
- - `disconnect(accountId)` β€” disconnect a managed account.
570
- - `submitCheckpoint(body)` β€” submit a 2FA / CAPTCHA checkpoint challenge.
571
- - `pollCheckpoint(body)` β€” poll for checkpoint resolution.
572
- - `createConnectLink(body)` β€” generate a hosted connect link.
746
+ - `list(params?)`: list managed accounts with optional `limit` and `cursor`.
747
+ - `get(accountId)`: fetch a single managed account.
748
+ - `link(body)`: connect a LinkedIn account via cookie or API key; returns an account or a checkpoint.
749
+ - `reconnect(accountId, body)`: reconnect a disconnected account.
750
+ - `refresh(accountId)`: force a session refresh.
751
+ - `update(accountId, body)`: update account settings.
752
+ - `disconnect(accountId)`: disconnect a managed account.
753
+ - `submitCheckpoint(body)`: submit a 2FA / CAPTCHA checkpoint challenge.
754
+ - `pollCheckpoint(body)`: poll for checkpoint resolution.
755
+ - `createConnectLink(body)`: generate a hosted connect link.
573
756
 
574
757
  - `curviate.account(id).messaging.*`:
575
- - `listChats(params?)` β€” list conversation threads.
576
- - `getChat(chatId)` β€” fetch a single chat.
577
- - `startChat(body)` β€” start a new conversation.
578
- - `listMessages(chatId, params?)` β€” list messages in a chat.
579
- - `getMessage(messageId)` β€” fetch a single message.
580
- - `sendMessage(chatId, body)` β€” send a message into a chat.
581
- - `editMessage(messageId, body)` β€” edit a sent message.
582
- - `deleteMessage(messageId)` β€” delete a sent message.
583
- - `addReaction(messageId, body)` β€” add an emoji reaction.
584
- - `getAttachment(messageId, attachmentId)` β€” download a message attachment.
585
- - `syncChat(chatId)` β€” trigger a chat history sync.
586
- - `syncMessages(params?)` β€” trigger a full message sync.
587
- - `sendInMail(body)` β€” send an InMail.
588
- - `getInMailBalance(params?)` β€” fetch InMail credit balance.
758
+ - `listChats(params?)`: list conversation threads.
759
+ - `getChat(chatId)`: fetch a single chat.
760
+ - `startChat(body)`: start a new conversation.
761
+ - `listMessages(chatId, params?)`: list messages in a chat.
762
+ - `getMessage(messageId)`: fetch a single message.
763
+ - `sendMessage(chatId, body)`: send a message into a chat.
764
+ - `editMessage(messageId, body)`: edit a sent message.
765
+ - `deleteMessage(messageId)`: delete a sent message.
766
+ - `addReaction(messageId, body)`: add an emoji reaction.
767
+ - `getAttachment(messageId, attachmentId)`: download a message attachment.
768
+ - `syncChat(chatId)`: trigger a chat history sync.
769
+ - `syncMessages(params?)`: trigger a full message sync.
770
+ - `sendInMail(body)`: send an InMail.
771
+ - `getInMailBalance(params?)`: fetch InMail credit balance.
589
772
 
590
773
  - `curviate.account(id).profiles.*`:
591
- - `get(profileId, params?)` β€” fetch a LinkedIn profile by id or handle.
592
- - `getMe()` β€” fetch the profile for the managed account holder.
593
- - `getCompany(companyId)` β€” fetch a company profile.
594
- - `listConnections(params?)` β€” list the account's connections.
595
- - `listFollowers(profileId, params?)` β€” list a profile's followers.
596
- - `listPosts(profileId, params?)` β€” list a profile's posts.
597
- - `listComments(profileId, params?)` β€” list a profile's post comments.
598
- - `listReactions(profileId, params?)` β€” list a profile's post reactions.
599
- - `endorse(profileId, body)` β€” endorse skills on a profile.
774
+ - `get(profileId, params?)`: fetch a LinkedIn profile by id or handle.
775
+ - `getMe()`: fetch the profile for the managed account holder.
776
+ - `getCompany(companyId)`: fetch a company profile.
777
+ - `listConnections(params?)`: list the account's connections.
778
+ - `listFollowers(profileId, params?)`: list a profile's followers.
779
+ - `listPosts(profileId, params?)`: list a profile's posts.
780
+ - `listComments(profileId, params?)`: list a profile's post comments.
781
+ - `listReactions(profileId, params?)`: list a profile's post reactions.
782
+ - `endorse(profileId, body)`: endorse skills on a profile.
600
783
 
601
784
  - `curviate.account(id).invites.*`:
602
- - `send(body)` β€” send a connection invitation.
603
- - `listSent(params?)` β€” list sent invitations.
604
- - `listReceived(params?)` β€” list received invitations.
605
- - `respond(invitationId, body)` β€” accept or ignore a received invitation.
606
- - `cancel(invitationId)` β€” cancel a sent invitation.
785
+ - `send(body)`: send a connection invitation.
786
+ - `listSent(params?)`: list sent invitations.
787
+ - `listReceived(params?)`: list received invitations.
788
+ - `respond(invitationId, body)`: accept or ignore a received invitation.
789
+ - `cancel(invitationId)`: cancel a sent invitation.
607
790
 
608
791
  - `curviate.account(id).posts.*`:
609
- - `list(params?)` β€” list posts visible to the account.
610
- - `get(postId)` β€” fetch a single post.
611
- - `create(body)` β€” create a post.
612
- - `listComments(postId, params?)` β€” list comments on a post.
613
- - `comment(postId, body)` β€” comment on a post.
614
- - `listReactions(postId, params?)` β€” list reactions on a post.
615
- - `react(postId, body)` β€” react to a post.
792
+ - `list(params?)`: list posts visible to the account.
793
+ - `get(postId)`: fetch a single post.
794
+ - `create(body)`: create a post.
795
+ - `listComments(postId, params?)`: list comments on a post.
796
+ - `comment(postId, body)`: comment on a post.
797
+ - `listReactions(postId, params?)`: list reactions on a post.
798
+ - `react(postId, body)`: react to a post.
616
799
 
617
800
  - `curviate.account(id).salesNavigator.*`:
618
- - `searchPeople(body, params?)` β€” search people via Sales Navigator.
619
- - `searchCompanies(body, params?)` β€” search companies via Sales Navigator.
620
- - `getProfile(identifier, params?)` β€” fetch a Sales Navigator profile.
621
- - `getParameters(params)` β€” fetch available search filter parameters.
622
- - `saveLead(userId, body)` β€” save a lead.
623
- - `startChat(body)` β€” start a Sales Navigator conversation.
624
- - `syncMessages(params)` β€” sync Sales Navigator messages.
801
+ - `searchPeople(body, params?)`: search people via Sales Navigator.
802
+ - `searchCompanies(body, params?)`: search companies via Sales Navigator.
803
+ - `getProfile(identifier, params?)`: fetch a Sales Navigator profile.
804
+ - `getParameters(params)`: fetch available search filter parameters.
805
+ - `saveLead(userId, body)`: save a lead.
806
+ - `startChat(body)`: start a Sales Navigator conversation.
807
+ - `syncMessages(params)`: sync Sales Navigator messages.
625
808
 
626
809
  - `curviate.account(id).recruiter.*`:
627
- - `searchPeople(body, params?)` β€” search candidates via LinkedIn Recruiter.
628
- - `getProfile(identifier, params?)` β€” fetch a Recruiter candidate profile.
629
- - `getParameters(params)` β€” fetch available search filter parameters.
630
- - `syncMessages(params)` β€” sync Recruiter messages.
631
- - `startChat(body)` β€” start a Recruiter conversation.
632
- - `listProjects(params?)` β€” list Recruiter projects.
633
- - `getProject(projectId)` β€” fetch a single Recruiter project.
634
- - `addCandidate(userId, body)` β€” add a candidate to a project.
635
- - `addApplicant(userId, body)` β€” add an applicant to a job.
636
- - `rejectApplicant(userId, body)` β€” reject an applicant.
637
- - `listJobs(params?)` β€” list Recruiter job postings.
638
- - `createJob(body)` β€” create a Recruiter job posting.
639
- - `publishJob(jobId, body)` β€” publish a Recruiter job posting.
640
- - `solveJobCheckpoint(jobId, body)` β€” solve a job-posting checkpoint.
641
- - `listApplicants(jobId, params?)` β€” list applicants for a job.
642
- - `getApplicant(applicantId)` β€” fetch a single applicant.
643
- - `downloadResume(applicantId)` β€” download an applicant's resume.
810
+ - `searchPeople(body, params?)`: search candidates via LinkedIn Recruiter.
811
+ - `getProfile(identifier, params?)`: fetch a Recruiter candidate profile.
812
+ - `getParameters(params)`: fetch available search filter parameters.
813
+ - `syncMessages(params)`: sync Recruiter messages.
814
+ - `startChat(body)`: start a Recruiter conversation.
815
+ - `listProjects(params?)`: list Recruiter projects.
816
+ - `getProject(projectId)`: fetch a single Recruiter project.
817
+ - `addCandidate(userId, body)`: add a candidate to a project.
818
+ - `addApplicant(userId, body)`: add an applicant to a job.
819
+ - `rejectApplicant(userId, body)`: reject an applicant.
820
+ - `listJobs(params?)`: list Recruiter job postings.
821
+ - `createJob(body)`: create a Recruiter job posting.
822
+ - `publishJob(jobId, body)`: publish a Recruiter job posting.
823
+ - `solveJobCheckpoint(jobId, body)`: solve a job-posting checkpoint.
824
+ - `listApplicants(jobId, params?)`: list applicants for a job.
825
+ - `getApplicant(applicantId)`: fetch a single applicant.
826
+ - `downloadResume(applicantId)`: download an applicant's resume.
644
827
 
645
828
  - `curviate.account(id).search.*` (classic LinkedIn search):
646
- - `people(body)` β€” search people.
647
- - `companies(body)` β€” search companies.
648
- - `posts(body)` β€” search posts.
649
- - `jobs(body)` β€” search jobs.
650
- - `getParameters(query)` β€” fetch available search filter parameters.
829
+ - `people(body)`: search people.
830
+ - `companies(body)`: search companies.
831
+ - `posts(body)`: search posts.
832
+ - `jobs(body)`: search jobs.
833
+ - `getParameters(query)`: fetch available search filter parameters.
651
834
 
652
835
  - `curviate.webhooks.*`:
653
- - `create(body)` β€” register a new webhook endpoint.
654
- - `list(params?)` β€” list registered webhooks.
655
- - `update(id, body)` β€” update a webhook endpoint.
656
- - `delete(id)` β€” delete a webhook endpoint.
657
- - `listEvents()` β€” list available webhook event types.
658
- - `getStateDiff(accountId, params?)` β€” fetch an account state diff.
836
+ - `create(body)`: register a new webhook endpoint.
837
+ - `list(params?)`: list registered webhooks.
838
+ - `update(id, body)`: update a webhook endpoint.
839
+ - `delete(id)`: delete a webhook endpoint.
840
+ - `listEvents()`: list available webhook event types.
841
+ - `getStateDiff(accountId, params?)`: fetch an account state diff.
659
842
 
660
843
  **Typed error model**
661
- - `CurviateError` β€” single thrown type for all API errors, carrying a stable `code`, `httpStatus`, `retryHint`, `userFixable`, `retryLikelyToSucceed`, and `requiredTier`.
662
- - `isCurviateError(err)` β€” type guard for narrowing in `catch` blocks.
844
+ - `CurviateError`: single thrown type for all API errors, carrying a stable `code`, `httpStatus`, `retryHint`, `userFixable`, `retryLikelyToSucceed`, and `requiredTier`.
845
+ - `isCurviateError(err)`: type guard for narrowing in `catch` blocks.
663
846
  - 34-member `ErrorCode` union covering every observable API error code.
664
847
 
665
848
  **HTTP transport with retry and backoff**
@@ -669,13 +852,13 @@ Initial public release.
669
852
  - JSON and binary (ArrayBuffer) response parsing.
670
853
 
671
854
  **Cursor pagination**
672
- - `curviate.paginate(method, params?)` β€” async iterator that follows the `cursor` field automatically.
855
+ - `curviate.paginate(method, params?)`: async iterator that follows the `cursor` field automatically.
673
856
 
674
857
  **Webhook signature verification**
675
- - `constructEvent(rawBody, signatureHeader, secret, opts?)` β€” verifies `HMAC-SHA256` webhook signatures using Web Crypto (`globalThis.crypto.subtle`); constant-time comparison prevents timing attacks; configurable replay window via `replayWindowSecs` (default 300 s). Always async β€” always `await` it.
676
- - `WebhookSignatureError` β€” thrown on malformed header, invalid signature, or replay; not a `CurviateError`.
677
- - `CurviateEvent` β€” 19-member discriminated union covering all canonical webhook event types.
858
+ - `constructEvent(rawBody, signatureHeader, secret, opts?)`: verifies `HMAC-SHA256` webhook signatures using Web Crypto (`globalThis.crypto.subtle`); constant-time comparison prevents timing attacks; configurable replay window via `replayWindowSecs` (default 300 s). Always async, always `await` it.
859
+ - `WebhookSignatureError`: thrown on malformed header, invalid signature, or replay; not a `CurviateError`.
860
+ - `CurviateEvent`: 19-member discriminated union covering all canonical webhook event types.
678
861
 
679
862
  **TypeScript types**
680
- - Full OpenAPI-generated types at `src/generated/types.ts` β€” request bodies, responses, and path params.
863
+ - Full OpenAPI-generated types at `src/generated/types.ts`: request bodies, responses, and path params.
681
864
  - ESM-only build targeting ES2020; works in Node 18+, Cloudflare Workers, Vercel Edge.