@curviate/sdk 0.18.1 → 0.20.0
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 +405 -230
- package/README.md +50 -17
- package/dist/index.d.ts +1578 -1364
- package/dist/index.js +127 -104
- package/package.json +6 -4
- package/src/generated/types.ts +1277 -1128
package/CHANGELOG.md
CHANGED
|
@@ -3,41 +3,216 @@
|
|
|
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
|
|
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.
|
|
12
|
+
## [0.20.0] - 2026-08-07
|
|
13
|
+
|
|
14
|
+
Generated types are rebuilt from the **served** OpenAPI document rather than a
|
|
15
|
+
hand-refreshed snapshot, and the snapshot itself now records where it came from.
|
|
16
|
+
Source of this release: `https://api.staging.curviate.com` at server commit
|
|
17
|
+
`1015999221a2dafc505cc565369a40b8ceca1888`, recorded in
|
|
18
|
+
`fixtures/PROVENANCE.json`.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **BREAKING (type-level): `headers[].value` is gone from every webhook response,
|
|
23
|
+
replaced by `headers[].value_prefix`.** It affects `webhooks.create` (201),
|
|
24
|
+
`webhooks.get`, `webhooks.update`, and `webhooks.list`. The API stopped
|
|
25
|
+
returning the plaintext header value some time ago and returns a masked prefix
|
|
26
|
+
instead; the vendored snapshot had not caught up, so the SDK typed a field the
|
|
27
|
+
server no longer sends. `tsc` will point at every `h.value` read:
|
|
28
|
+
|
|
29
|
+
```diff
|
|
30
|
+
- const configured = webhook.headers?.[0]?.value;
|
|
31
|
+
+ const configured = webhook.headers?.[0]?.value_prefix; // masked, never the full value
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This is the only breaking change in the release. Read the label honestly: the
|
|
35
|
+
old type was already wrong at runtime, so any code relying on it was reading
|
|
36
|
+
`undefined`; the break is `tsc` finally saying so.
|
|
37
|
+
|
|
38
|
+
- `POST /v1/auth/checkpoint/poll` reports three further `status` values
|
|
39
|
+
(`reconnect_needed`, `restricted`, `disconnected`) on top of the five it
|
|
40
|
+
already had. Purely additive, but an exhaustive `switch` over `status` with a
|
|
41
|
+
`never` fallthrough will need the new arms.
|
|
42
|
+
|
|
43
|
+
- `GET /v1/{account_id}/search/jobs`, `POST /v1/{account_id}/search`, and the
|
|
44
|
+
company jobs listing now mark `id` required on a job item alongside
|
|
45
|
+
`job_urn` (same value, shorter name, chains into `get_job`).
|
|
46
|
+
|
|
47
|
+
- **Punctuation swept out of the published copy.** The README, this changelog,
|
|
48
|
+
and every JSDoc comment that ships inside `dist/index.d.ts` used typographic
|
|
49
|
+
characters with no plain-ASCII equivalent on the keyboard: em and en dashes,
|
|
50
|
+
the single-glyph ellipsis, arrows. They are gone, replaced with commas,
|
|
51
|
+
semicolons, colons, `...` and `->`. No release note changed what it claims;
|
|
52
|
+
the word stream is byte-identical apart from one inserted "and". Version
|
|
53
|
+
headers now use the ` - ` separator that Keep a Changelog specifies.
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- **`account.restricted` is now a member of the `CurviateEvent` union.** The
|
|
58
|
+
server publishes it and delivers it by default on the `account_status` source,
|
|
59
|
+
but the SDK union did not carry it, so an exhaustive `switch (event.event)`
|
|
60
|
+
could not compile a handler for the one event that means "stop sending on this
|
|
61
|
+
account". The union is pinned at compile time to the generated create-events
|
|
62
|
+
enum, so this addition was produced by the regeneration rather than typed by
|
|
63
|
+
hand.
|
|
64
|
+
|
|
65
|
+
- **`EventPayloadBase`**, exported: the `account_id`, `event`, and `occurred_at`
|
|
66
|
+
every webhook payload carries. `occurred_at` was previously reachable only
|
|
67
|
+
through the payload index signature, typed `unknown`, which meant the
|
|
68
|
+
de-duplication key documented below could not be built without a cast.
|
|
69
|
+
|
|
70
|
+
- Response fields surfaced by the regeneration: `notices[]` on every search and
|
|
71
|
+
company-employees listing, `visibility` on people / services / company-employee
|
|
72
|
+
results, `id` on job results, `recovered` on a checkpoint poll,
|
|
73
|
+
`trial_premium` on an auth intent, `next_action` / `notices` / `unresolved` on
|
|
74
|
+
the shared `Error` schema, a `409` on checkpoint poll and a `403` on the user
|
|
75
|
+
read.
|
|
76
|
+
|
|
77
|
+
- `pnpm gen:fixture` (`scripts/refresh-fixture.mjs`): fetches the live document,
|
|
78
|
+
refuses to write it if it carries a forbidden name, canonicalizes the
|
|
79
|
+
environment-dependent `servers` order so a refresh from staging and one from
|
|
80
|
+
production produce identical bytes, and writes `fixtures/PROVENANCE.json`. The
|
|
81
|
+
snapshot previously had no writer at all, which is why nobody could tell how
|
|
82
|
+
old it was.
|
|
83
|
+
|
|
84
|
+
- **`pnpm check:copy`**, a pre-publish copy-quality gate, chained into
|
|
85
|
+
`prepack` alongside the existing internal-reference scan. It fails the pack
|
|
86
|
+
on any non-ASCII typographic character in the published copy and reports
|
|
87
|
+
(without failing) emoji and wording from the generic-marketing register. See
|
|
88
|
+
the header of `scripts/check-copy.mjs` for why only the first tier blocks.
|
|
89
|
+
`test/check-copy.test.ts` exercises each pattern on its own, because a
|
|
90
|
+
pattern set that silently stops matching still reports zero.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- **The documented webhook de-duplication key did not work.** The README said
|
|
95
|
+
the delivered `event.id` was something you "can de-duplicate retries on". Every
|
|
96
|
+
delivery attempt mints a fresh `wdl_` id, so a retry arrives with a different
|
|
97
|
+
one and a consumer following that advice stored the same logical event twice,
|
|
98
|
+
double-processing exactly the retries the guidance claimed to cover. The stable
|
|
99
|
+
key is the payload composite `event.event` plus `event.data.account_id` plus
|
|
100
|
+
`event.data.occurred_at`, which the platform re-sends identically on every
|
|
101
|
+
attempt. The README, the `CurviateEventEnvelope.id` JSDoc, and the 0.19.0
|
|
102
|
+
entry below all carried the wrong claim; all three are corrected, and the
|
|
103
|
+
0.19.0 entry was edited in place rather than left standing as advice someone
|
|
104
|
+
would still follow.
|
|
105
|
+
|
|
106
|
+
- Doc comments no longer hardcode the size of a set that grows. "27 events" on
|
|
107
|
+
`webhooks.listEvents`, "24 canonical events" on the `CurviateEvent` union,
|
|
108
|
+
"22 more event types" and "34 error codes" in the README were each hand-typed
|
|
109
|
+
and each stale. The served catalogue description counts itself and the union is
|
|
110
|
+
pinned to the generated enum, so the numbers were redundant as well as wrong.
|
|
111
|
+
|
|
112
|
+
- `test/docs-contract.test.ts`: the two claim families above now go red in the
|
|
113
|
+
suite. Prose was the one representation of the contract that nothing checked,
|
|
114
|
+
which is why both defects shipped.
|
|
115
|
+
|
|
116
|
+
- The banner on `src/generated/types.ts` carried an em dash. The file ships (it
|
|
117
|
+
is in `files[]` and is inlined into `dist/index.d.ts`), so the banner is
|
|
118
|
+
published copy and the dash was the same tell the sweep removed everywhere
|
|
119
|
+
else. It is fixed where it is emitted rather than in the output, and the
|
|
120
|
+
string now has one home: `gen-types.mjs` wrote it and `check-types.mjs`
|
|
121
|
+
reconstructed it to diff against, so the two hand-written copies could
|
|
122
|
+
disagree and red the drift gate over a file nobody had touched. Both now
|
|
123
|
+
import `GENERATED_HEADER` from `scripts/openapi-sanitize.mjs`.
|
|
124
|
+
|
|
125
|
+
## [0.19.0] - 2026-08-03
|
|
126
|
+
|
|
127
|
+
### Fixed
|
|
128
|
+
|
|
129
|
+
- **`constructEvent` now parses real webhook deliveries. It previously rejected
|
|
130
|
+
every single one.** The Curviate platform sends the event name in `event`;
|
|
131
|
+
the SDK required a `type` field that no delivery has ever contained, so
|
|
132
|
+
verification threw `WebhookSignatureError` on 100% of genuine webhooks. The
|
|
133
|
+
HMAC path was never at fault (a tampered body was correctly rejected as
|
|
134
|
+
`invalid_signature`); the parse step was. If webhook verification has never
|
|
135
|
+
worked for you, this is why, and this release fixes it.
|
|
136
|
+
|
|
137
|
+
- **A body problem no longer reports a header problem.** A payload whose
|
|
138
|
+
signature verifies but whose body cannot be parsed used to fail with
|
|
139
|
+
`reason: "malformed_header"`, pointing at the two things that had just been
|
|
140
|
+
proven correct: the header and the signing secret. Integrators read that and
|
|
141
|
+
rotated a secret that was never wrong. Those cases now report the new
|
|
142
|
+
`reason: "malformed_payload"`, and the message no longer mentions the header,
|
|
143
|
+
the signature, or the secret.
|
|
144
|
+
|
|
145
|
+
### Changed
|
|
146
|
+
|
|
147
|
+
- **BREAKING (type-level), and it breaks no working code.** The `CurviateEvent`
|
|
148
|
+
discriminant is now `event`, not `type`:
|
|
149
|
+
|
|
150
|
+
```diff
|
|
151
|
+
- if (event.type === "message.received") { ... }
|
|
152
|
+
+ if (event.event === "message.received") { ... }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Read the label carefully before deferring the upgrade: **there is no working
|
|
156
|
+
consumer of `event.type` to break.** `constructEvent` has never once returned
|
|
157
|
+
successfully for a real delivery, so no `event.type` branch has ever executed
|
|
158
|
+
in production. Any handler you have written behind it is unreachable code
|
|
159
|
+
today. `event.type` is now a compile error precisely so `tsc` points you at
|
|
160
|
+
every site that needs the one-word edit; nothing changes at runtime for code
|
|
161
|
+
that was previously working, because there was none.
|
|
162
|
+
|
|
163
|
+
`WebhookSignatureError["reason"]` gains the `"malformed_payload"` member. An
|
|
164
|
+
exhaustive `switch` over `reason` will need the new arm.
|
|
165
|
+
|
|
166
|
+
### Added
|
|
167
|
+
|
|
168
|
+
- **`CurviateEventEnvelope`**: the per-delivery metadata now carried on every
|
|
169
|
+
event. `event.id` is the `wdl_...` delivery id, `event.webhook_id` is the
|
|
170
|
+
registration, `event.delivered_at` is the attempt timestamp. These are typed
|
|
171
|
+
optional and are **not** required by the parser:
|
|
172
|
+
requiring an envelope field is what caused this defect, so nothing beyond the
|
|
173
|
+
discriminant can fail a verification again.
|
|
174
|
+
|
|
175
|
+
- Forward compatibility: a payload carrying `type` instead of `event` is
|
|
176
|
+
accepted and normalized onto `event`, so a future platform-side change to the
|
|
177
|
+
envelope cannot break verification a second time. `type` is never required.
|
|
178
|
+
|
|
179
|
+
- `test/webhooks.dispatcherEnvelope.test.ts` and
|
|
180
|
+
`test/fixtures/webhook-delivery.capture.json`: the regression test replays
|
|
181
|
+
bytes and a signature header **captured off the wire from the real dispatch
|
|
182
|
+
worker**, rather than constructing its own payload. The pre-existing suite was
|
|
183
|
+
green throughout this defect because it built its own `{ type: ... }` fixtures;
|
|
184
|
+
a suite that constructs its own input cannot detect an emitter/parser
|
|
185
|
+
divergence, and that is what let this ship.
|
|
186
|
+
|
|
187
|
+
## [0.18.1] - 2026-07-18
|
|
13
188
|
|
|
14
189
|
### Changed
|
|
15
190
|
|
|
16
|
-
- **`companies.sendMessage(identifier, chatId, body)`** now takes the normal `2
|
|
191
|
+
- **`companies.sendMessage(identifier, chatId, body)`** now takes the normal `2-...`
|
|
17
192
|
chat id that `companies.chats()` / `chat()` / `searchChats()` return; the endpoint
|
|
18
193
|
resolves the page mailbox internally from `identifier`. The previous send-ready
|
|
19
194
|
`COMPANY_` chat-id requirement is gone: pass the id straight from the company chat
|
|
20
195
|
reads. Method signature and response shape are unchanged (documentation/behavior
|
|
21
196
|
only).
|
|
22
197
|
|
|
23
|
-
## [0.18.0]
|
|
198
|
+
## [0.18.0] - 2026-07-17
|
|
24
199
|
|
|
25
200
|
### Added
|
|
26
201
|
|
|
27
202
|
- **`companies.sendMessage(identifier, chatId, body)`**: reply to a company-inbox
|
|
28
203
|
conversation as the page. `POST /v1/{account_id}/companies/{identifier}/chats/{chat_id}/messages`.
|
|
29
204
|
`chatId` must be the send-ready `COMPANY_` chat id from `inboxes.listChats()`, not the
|
|
30
|
-
`2
|
|
205
|
+
`2-...` id `companies.chats()`/`chat()`/`searchChats()` return; a non-`COMPANY_` id is
|
|
31
206
|
rejected with a guiding 400. Reply-only. The response echoes `sent_as`, the acting
|
|
32
207
|
identity actually used.
|
|
33
208
|
|
|
34
|
-
## [0.17.0]
|
|
209
|
+
## [0.17.0] - 2026-07-17
|
|
35
210
|
|
|
36
211
|
### Added
|
|
37
212
|
|
|
38
213
|
- **`companies.followInvite(identifier, body)`**: invite the connected
|
|
39
214
|
account's 1st-degree connections to follow a company page it administers with the
|
|
40
|
-
invite-to-follow entitlement. Pass the `AC
|
|
215
|
+
invite-to-follow entitlement. Pass the `AC...` member ids from an `invitableFollowers()`
|
|
41
216
|
read. `POST /v1/{account_id}/companies/{identifier}/follow-invite`. All-or-nothing:
|
|
42
217
|
for an all-valid request, resolves to one outcome per requested invitee, in request
|
|
43
218
|
order (`status: "invited" | "already_invited" | "ineligible" | "not_found"`); if any
|
|
@@ -45,7 +220,7 @@ Versioning: semantic — minor for additive changes, patch for bug fixes; no sta
|
|
|
45
220
|
Re-inviting an already-invited member is a safe no-op (the same `invitation_id`,
|
|
46
221
|
never a duplicate).
|
|
47
222
|
|
|
48
|
-
## [0.16.0]
|
|
223
|
+
## [0.16.0] - 2026-07-17
|
|
49
224
|
|
|
50
225
|
Mostly additive, plus one breaking removal on the connect/reconnect body.
|
|
51
226
|
|
|
@@ -92,25 +267,25 @@ Mostly additive, plus one breaking removal on the connect/reconnect body.
|
|
|
92
267
|
profile; `user_fixable`, never retryable) and `REAUTH_REQUIRED` (a
|
|
93
268
|
scope-changing reconnect attempted with a cookie instead of credentials;
|
|
94
269
|
`user_fixable`, never retryable).
|
|
95
|
-
- **New account-scoped `profile` namespace (4 methods)
|
|
270
|
+
- **New account-scoped `profile` namespace (4 methods)**, the connected
|
|
96
271
|
account's own insight surface: `profile.subscription()`, `profile.analytics()`,
|
|
97
272
|
`profile.visitors(query?)`, `profile.ssi()`. Distinct from the retired
|
|
98
273
|
`profiles` namespace (renamed to `users` in 0.15.0).
|
|
99
|
-
- **New account-scoped `groups` namespace (3 methods)
|
|
274
|
+
- **New account-scoped `groups` namespace (3 methods)**, `groups.list(query?)`
|
|
100
275
|
(own groups by default, or another member's via `{ profile }`),
|
|
101
276
|
`groups.get(group)`, and `groups.members(group, query?)` (with the folded-in
|
|
102
277
|
`{ name }` member search).
|
|
103
|
-
- **New account-scoped `feed` namespace (1 method)
|
|
278
|
+
- **New account-scoped `feed` namespace (1 method)**, `feed.home(query?)` reads
|
|
104
279
|
the connected account's home feed as agent-actionable posts, with `relevant`
|
|
105
280
|
or `recent` sort orders.
|
|
106
|
-
- **`companies` gains 3 company-insights methods
|
|
281
|
+
- **`companies` gains 3 company-insights methods**, `companies.managed(query?)`
|
|
107
282
|
lists the pages the connected account administers; `companies.followers(identifier,
|
|
108
|
-
query?)` lists a page's followers (**re-added** under a different item shape
|
|
109
|
-
|
|
110
|
-
|
|
283
|
+
query?)` lists a page's followers (**re-added** under a different item shape
|
|
284
|
+
than the pre-0.15.0 method of the same name: `company_follower`, carrying
|
|
285
|
+
`degree`/`followed_at`); `companies.invitableFollowers(identifier, query?)`
|
|
111
286
|
lists connections invitable to follow the page. All three require the
|
|
112
287
|
connected account to administer the target page.
|
|
113
|
-
- **`companies` gains 5 Beta company-inbox methods
|
|
288
|
+
- **`companies` gains 5 Beta company-inbox methods**, `companies.chats(identifier,
|
|
114
289
|
query?)`, `companies.chat(identifier, chatId)`, `companies.messages(identifier,
|
|
115
290
|
chatId, query?)`, `companies.message(identifier, chatId, messageId)`, and
|
|
116
291
|
`companies.searchChats(identifier, query?)` (exactly one of `query`/`topic`/
|
|
@@ -119,140 +294,140 @@ Mostly additive, plus one breaking removal on the connect/reconnect body.
|
|
|
119
294
|
single-page listing and termination are verified; deep pagination (many
|
|
120
295
|
pages / large cursor round-trips) is still being validated against a busier
|
|
121
296
|
inbox. `companies` is now 12 methods (was 4).
|
|
122
|
-
- **`search` gains 3 methods
|
|
297
|
+
- **`search` gains 3 methods**, `search.groups(query)` searches LinkedIn
|
|
123
298
|
groups by keyword; `search.services(body & query)` searches Services
|
|
124
299
|
Marketplace providers with structured filters; `search.getServiceParameters(query)`
|
|
125
300
|
resolves human-readable service-category/location terms into the opaque
|
|
126
301
|
filter ids `services()` accepts. `search` is now 9 methods (was 6).
|
|
127
|
-
- **`messaging` gains 1 method
|
|
302
|
+
- **`messaging` gains 1 method**, `messaging.searchChats(params)` free-text
|
|
128
303
|
searches the account's own inbox (participant names and message content).
|
|
129
304
|
`messaging` is now 13 methods (was 12).
|
|
130
|
-
- **`posts` gains 3 saved-posts methods
|
|
131
|
-
connected account's own saved posts (a self resource
|
|
132
|
-
`snippet` capped at
|
|
305
|
+
- **`posts` gains 3 saved-posts methods**, `posts.listSaved(query?)` lists the
|
|
306
|
+
connected account's own saved posts (a self resource: previews only,
|
|
307
|
+
`snippet` capped at <=140 chars, never the full body); `posts.save(postId)`
|
|
133
308
|
and `posts.unsave(postId)` add/remove a bookmark, both idempotent and
|
|
134
309
|
accepting either `urn:li:activity:<id>` or a bare numeric id. `posts` is now
|
|
135
310
|
12 methods (was 9).
|
|
136
|
-
- **New account-scoped `notifications` namespace (3 methods)
|
|
311
|
+
- **New account-scoped `notifications` namespace (3 methods)**, the connected
|
|
137
312
|
account's own notification centre: `notifications.list(query?)` (cards +
|
|
138
313
|
the account-level `unread_count`/`latest_published_at`);
|
|
139
314
|
`notifications.delete(cardUrn)` and `notifications.showLess(cardUrn)`, two
|
|
140
315
|
self-action writes on the account's own cards. Both writes are idempotent
|
|
141
|
-
and take effect within a few seconds
|
|
316
|
+
and take effect within a few seconds; a list read immediately after may
|
|
142
317
|
still show the card for a moment, which is not a failure signal. The SDK
|
|
143
318
|
percent-encodes `cardUrn` (which embeds `(`, `)`, `:`, `,`) into the path.
|
|
144
319
|
- **Parity pin: 143 methods across 18 namespaces** (was 135 / 16 at the start
|
|
145
320
|
of this release cycle). The `inboxes` namespace is the final addition.
|
|
146
321
|
|
|
147
|
-
## [0.15.0]
|
|
322
|
+
## [0.15.0] - 2026-07-11
|
|
148
323
|
|
|
149
324
|
Full v2 parity. The SDK is re-aligned 1:1 to the served API surface: every
|
|
150
325
|
operation is now exactly one method at the exact wire encoding, the entire
|
|
151
326
|
account-scoped surface moves to the account-first path grammar, and three
|
|
152
327
|
namespaces are reorganized. This is a **breaking** release touching nearly
|
|
153
|
-
every namespace. The five breaking categories below
|
|
328
|
+
every namespace. The five breaking categories below (removals, namespace
|
|
154
329
|
reorganization, method renames/relocations, request/response shape changes, and
|
|
155
|
-
the account-first path-grammar migration
|
|
156
|
-
`0.14.1`
|
|
330
|
+
the account-first path-grammar migration) are the complete set of breaks a
|
|
331
|
+
`0.14.1` -> `0.15.0` upgrade must reconcile, gathered here so a consumer finds
|
|
157
332
|
every break in one place.
|
|
158
333
|
|
|
159
334
|
### Removed (BREAKING)
|
|
160
335
|
|
|
161
336
|
- **14 methods that map to no served operation are removed, with no alias:**
|
|
162
|
-
- `accounts.createConnectLink()`, `accounts.createReconnectLink()`, `accounts.reconnect()
|
|
163
|
-
- `messaging.syncChat()`, `messaging.syncMessages()`, `recruiter.syncMessages()`, `salesNavigator.syncMessages()
|
|
164
|
-
- `posts.list()
|
|
165
|
-
- `companies.followers()
|
|
166
|
-
- `recruiter.addApplicant()`, `recruiter.rejectApplicant()`, `recruiter.solveJobCheckpoint()
|
|
167
|
-
- `webhooks.getStateDiff()
|
|
168
|
-
- **`invites.respond()` is removed
|
|
169
|
-
- **`profiles.getCompany()` stays removed
|
|
170
|
-
|
|
171
|
-
### Changed (BREAKING)
|
|
172
|
-
|
|
173
|
-
- **`profiles` namespace renamed to `users`.** Every `curviate.account(id).profiles.*` call becomes
|
|
174
|
-
- **New root-scoped `auth` namespace, split out of `accounts`.** The connect/checkpoint operations move off `accounts`: `accounts.link`
|
|
337
|
+
- `accounts.createConnectLink()`, `accounts.createReconnectLink()`, `accounts.reconnect()`: hosted connect/reconnect link minting and in-place reconnect are no longer part of the API surface.
|
|
338
|
+
- `messaging.syncChat()`, `messaging.syncMessages()`, `recruiter.syncMessages()`, `salesNavigator.syncMessages()`: explicit sync operations are gone; accounts sync continuously and deliver via webhooks.
|
|
339
|
+
- `posts.list()`: the standalone feed list has no served operation (use `posts.listUserPosts(userId)` for a member's posts).
|
|
340
|
+
- `companies.followers()`: no served operation.
|
|
341
|
+
- `recruiter.addApplicant()`, `recruiter.rejectApplicant()`, `recruiter.solveJobCheckpoint()`: superseded by the project-centric pipeline surface.
|
|
342
|
+
- `webhooks.getStateDiff()`: no served operation.
|
|
343
|
+
- **`invites.respond()` is removed**, split into two dedicated bodyless methods, `invites.accept(invitationId)` and `invites.decline(invitationId)` (see below).
|
|
344
|
+
- **`profiles.getCompany()` stays removed**, dropped ahead of this release with no return; company reads live on the `companies` namespace.
|
|
345
|
+
|
|
346
|
+
### Changed (BREAKING): namespaces reorganized
|
|
347
|
+
|
|
348
|
+
- **`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}`.
|
|
349
|
+
- **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`.
|
|
175
350
|
- **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`.
|
|
176
|
-
- **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)
|
|
351
|
+
- **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`).
|
|
177
352
|
|
|
178
|
-
### Changed (BREAKING)
|
|
353
|
+
### Changed (BREAKING): methods renamed / relocated
|
|
179
354
|
|
|
180
|
-
- `profiles.endorse(userId, { skill_endorsement_id })`
|
|
181
|
-
- `profiles.listConnections()`
|
|
182
|
-
- `messaging.getInMailBalance()`
|
|
183
|
-
- `posts.comment(postId,
|
|
184
|
-
- `profiles.listComments(userId)`
|
|
185
|
-
- `profiles.listPosts(userId)`
|
|
186
|
-
- `profiles.listReactions(userId)`
|
|
187
|
-
- `invites.respond(...)`
|
|
188
|
-
- `recruiter.addCandidate(...)`
|
|
189
|
-
- `recruiter.getParameters(...)` (GET)
|
|
190
|
-
- `recruiter.listProjectJobs(projectId)`
|
|
355
|
+
- `profiles.endorse(userId, { skill_endorsement_id })` -> **`users.endorseSkill(userId, { endorsement_id })`**, renamed, and the body key changed from `skill_endorsement_id` to `endorsement_id`.
|
|
356
|
+
- `profiles.listConnections()` -> **`users.listRelations()`**.
|
|
357
|
+
- `messaging.getInMailBalance()` -> **`users.getInMailCredits()`** (relocated onto `users`).
|
|
358
|
+
- `posts.comment(postId, ...)` -> **`comments.create(postId, ...)`**.
|
|
359
|
+
- `profiles.listComments(userId)` -> **`comments.listUserComments(userId)`**.
|
|
360
|
+
- `profiles.listPosts(userId)` -> **`posts.listUserPosts(userId)`**.
|
|
361
|
+
- `profiles.listReactions(userId)` -> **`posts.listUserReactions(userId)`**.
|
|
362
|
+
- `invites.respond(...)` -> **`invites.accept(invitationId)` / `invites.decline(invitationId)`** (split; both bodyless).
|
|
363
|
+
- `recruiter.addCandidate(...)` -> **`recruiter.saveCandidate(projectId, { stage_id, candidate_id })`**.
|
|
364
|
+
- `recruiter.getParameters(...)` (GET) -> **`recruiter.searchParameters(body)` (POST)**, the HTTP verb changed GET->POST, with a `source`-discriminated body.
|
|
365
|
+
- `recruiter.listProjectJobs(projectId)` -> **`recruiter.getProjectJob(projectId)`**, the operation returns the single attached job posting, not a list.
|
|
191
366
|
|
|
192
|
-
### Changed (BREAKING)
|
|
367
|
+
### Changed (BREAKING): request / response shapes
|
|
193
368
|
|
|
194
369
|
- **`users.update(userId, body)`** never sends `description`; accepted keys are `{ first_name, last_name, headline, bio, skills, picture, background_picture }`.
|
|
195
370
|
- **`users.endorseSkill`** body key is `endorsement_id` (was `skill_endorsement_id`).
|
|
196
371
|
- **`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 }`.
|
|
197
372
|
- **`jobs.close` / `recruiter.closeJob`** are bodyless POSTs responding `{ object }`.
|
|
198
373
|
- **`invites.cancel`** withdrawal now discriminates on `invitation_withdrawn`.
|
|
199
|
-
- **Message operations are re-homed under the chat.** `getMessage` / `editMessage` / `deleteMessage` / `addReaction` / `getAttachment` now path under `/chats/{chat_id}/messages
|
|
374
|
+
- **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.
|
|
200
375
|
- **`messaging.sendInMail`** body drops the `surface` field.
|
|
201
|
-
- **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
|
|
376
|
+
- **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.
|
|
202
377
|
- **Sales Navigator `saveAccount` / `saveLead` bodies shrank** to the minimal saved-entity shape.
|
|
203
378
|
- **`recruiter.startChat`** now requires a `signature` (alongside `attendees_ids`, `text`, `subject`).
|
|
204
379
|
- **`recruiter.createJob`** now requires `project_name` and responds `{ job_id, object, project_id }` (was a full job object).
|
|
205
380
|
- **`recruiter.saveCandidate`** body is `{ stage_id, candidate_id }`.
|
|
206
381
|
|
|
207
|
-
### Changed (BREAKING)
|
|
382
|
+
### Changed (BREAKING): account-first path grammar
|
|
208
383
|
|
|
209
|
-
- **Every account-scoped method now carries `account_id` as the leading path segment** (`/v1/{account_id}
|
|
384
|
+
- **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.
|
|
210
385
|
|
|
211
386
|
### Added
|
|
212
387
|
|
|
213
|
-
- **`comments` namespace (9 methods):** `listUserComments`, `create`, `edit`, `delete`, `reply`, `listReplies`, `listReactions`, `addReaction`, `removeReaction
|
|
388
|
+
- **`comments` namespace (9 methods):** `listUserComments`, `create`, `edit`, `delete`, `reply`, `listReplies`, `listReactions`, `addReaction`, `removeReaction`; `removeReaction` is a DELETE that carries a `{ reaction }` body.
|
|
214
389
|
- **`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`.
|
|
215
390
|
- **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.
|
|
216
391
|
- **`users.follow` / `users.unfollow`** (bodyless POST / DELETE), **`users.listFollowing`**, **`users.update`**.
|
|
217
392
|
- **`messaging.markChatRead(chatId, { read })`**.
|
|
218
|
-
- **`search.fromUrl({ url })`** and **`salesNavigator.searchFromUrl({ url })
|
|
393
|
+
- **`search.fromUrl({ url })`** and **`salesNavigator.searchFromUrl({ url })`**: resolve a LinkedIn search-results URL into a search.
|
|
219
394
|
- **`posts.delete`** (bodyless, 204) and **`posts.unreact(postId, { reaction })`** (DELETE with body).
|
|
220
|
-
- **`users.get(userId)` accepts `'me'
|
|
221
|
-
- **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)
|
|
395
|
+
- **`users.get(userId)` accepts `'me'`**: `users.get('me')` reads the caller's own profile (folds in the old `getMe`).
|
|
396
|
+
- **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.
|
|
222
397
|
|
|
223
398
|
### Fixed
|
|
224
399
|
|
|
225
|
-
- **`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
|
|
226
|
-
- **`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
|
|
227
|
-
- **`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
|
|
400
|
+
- **`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.
|
|
401
|
+
- **`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`.
|
|
402
|
+
- **`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.
|
|
228
403
|
|
|
229
404
|
---
|
|
230
405
|
|
|
231
|
-
## [0.14.1]
|
|
406
|
+
## [0.14.1] - 2026-07-07
|
|
232
407
|
|
|
233
408
|
### Fixed
|
|
234
409
|
|
|
235
|
-
- **`constructEvent` examples referenced a wrong header name (`X-Curviate-Signature`
|
|
410
|
+
- **`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.
|
|
236
411
|
|
|
237
412
|
---
|
|
238
413
|
|
|
239
|
-
## [0.14.0]
|
|
414
|
+
## [0.14.0] - 2026-07-07
|
|
240
415
|
|
|
241
416
|
Webhooks surface re-based onto the v2 catalogue. Additive minor: one new method,
|
|
242
417
|
one type-only breaking note for `CurviateEvent` (see below).
|
|
243
418
|
|
|
244
419
|
### Added
|
|
245
420
|
|
|
246
|
-
- **`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
|
|
247
|
-
- **Webhook event catalogue expanded 21
|
|
421
|
+
- **`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).
|
|
422
|
+
- **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.
|
|
248
423
|
|
|
249
424
|
### Changed (type-only breaking note)
|
|
250
425
|
|
|
251
|
-
- **`CurviateEvent` union re-keyed 19
|
|
426
|
+
- **`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.
|
|
252
427
|
|
|
253
428
|
---
|
|
254
429
|
|
|
255
|
-
## [0.13.0]
|
|
430
|
+
## [0.13.0] - 2026-07-05
|
|
256
431
|
|
|
257
432
|
Accounts/Auth surface migration. This is a **breaking** minor (pre-1.0): the account
|
|
258
433
|
connection and checkpoint surface was reshaped end-to-end. All changes are on the
|
|
@@ -260,123 +435,123 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
260
435
|
|
|
261
436
|
### Removed (BREAKING)
|
|
262
437
|
|
|
263
|
-
- **`accounts.refresh(accountId)` removed
|
|
264
|
-
- **`accounts.submitCheckpoint(body)` removed
|
|
265
|
-
- **`accounts.resendCheckpoint(body)` removed
|
|
438
|
+
- **`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.
|
|
439
|
+
- **`accounts.submitCheckpoint(body)` removed**, renamed to `accounts.solveCheckpoint(accountId, { code })` (see below). No alias.
|
|
440
|
+
- **`accounts.resendCheckpoint(body)` removed**, renamed to `accounts.requestCheckpoint(accountId)` (see below). No alias.
|
|
266
441
|
|
|
267
442
|
### Changed (BREAKING)
|
|
268
443
|
|
|
269
444
|
- **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:
|
|
270
|
-
- `submitCheckpoint({ account_id, code })`
|
|
271
|
-
- `resendCheckpoint({ account_id })`
|
|
272
|
-
- `pollCheckpoint({ account_id })`
|
|
273
|
-
- Migration: `submitCheckpoint({ account_id, code })`
|
|
274
|
-
- Types: `AccountSubmitCheckpointBody`/`AccountSubmitCheckpointResult`
|
|
275
|
-
- **`accounts.createConnectLink()` is create-only.** The `purpose` and `account_id` body fields are removed
|
|
276
|
-
- **`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
|
|
445
|
+
- `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).
|
|
446
|
+
- `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).
|
|
447
|
+
- `pollCheckpoint({ account_id })` -> **`pollCheckpoint(account_id)`** (`POST /v1/accounts/{account_id}/checkpoint/poll`), same name, now a single string arg (no body).
|
|
448
|
+
- Migration: `submitCheckpoint({ account_id, code })` -> `solveCheckpoint(account_id, { code })`; `resendCheckpoint({ account_id })` -> `requestCheckpoint(account_id)`; `pollCheckpoint({ account_id })` -> `pollCheckpoint(account_id)`.
|
|
449
|
+
- Types: `AccountSubmitCheckpointBody`/`AccountSubmitCheckpointResult` -> `AccountSolveCheckpointBody`/`AccountSolveCheckpointResult`; `AccountResendCheckpointBody`/`AccountResendCheckpointResult` -> `AccountRequestCheckpointResult` (no body type); `AccountPollCheckpointBody` removed (no body).
|
|
450
|
+
- **`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`.
|
|
451
|
+
- **`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.)*
|
|
277
452
|
- **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.)
|
|
278
|
-
- **`accounts.reconnect()` result is now a `200 | 202` union.** A reconnect can itself surface a checkpoint challenge
|
|
453
|
+
- **`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.
|
|
279
454
|
|
|
280
455
|
### Added
|
|
281
456
|
|
|
282
|
-
- **`accounts.createReconnectLink(accountId, body?)`.** Mint a one-time hosted **re-authorization** link for an existing disconnected account (`POST /v1/accounts/{account_id}/reconnect-link`)
|
|
283
|
-
- **New `ErrorCode` value: `ACCOUNT_ALREADY_LINKED`.** The `409` a duplicate connect now returns from `accounts.link()`, `accounts.reconnect()`, and `accounts.solveCheckpoint()
|
|
457
|
+
- **`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).
|
|
458
|
+
- **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.
|
|
284
459
|
- **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`).
|
|
285
|
-
- **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`)
|
|
460
|
+
- **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.
|
|
286
461
|
- **Connect-recovery + honest terminal signals on the connection responses (additive, non-breaking).**
|
|
287
|
-
- `accounts.link()` and `accounts.solveCheckpoint()` 201 responses now carry an optional `recovered` boolean
|
|
288
|
-
- The `status` on those same 201 responses is widened from `"active"` to `"active" | "reconnect_needed" | "restricted" | "disconnected"
|
|
462
|
+
- `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.
|
|
463
|
+
- 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.
|
|
289
464
|
- `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.
|
|
290
465
|
|
|
291
466
|
### Changed
|
|
292
467
|
|
|
293
|
-
- 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
|
|
468
|
+
- 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.
|
|
294
469
|
|
|
295
|
-
## [0.12.0]
|
|
470
|
+
## [0.12.0] - 2026-07-05
|
|
296
471
|
|
|
297
472
|
### Added
|
|
298
473
|
|
|
299
|
-
- **New `companies` namespace (5 methods).** `companies.get(identifier)` retrieves a company's full LinkedIn profile
|
|
300
|
-
- **New `ErrorCode` value: `RESOURCE_ACCESS_RESTRICTED`.** The non-admin mapping for `companies.followers()
|
|
301
|
-
- **`salesNavigator` gains 5 new v2 list-surface methods (7
|
|
474
|
+
- **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`.
|
|
475
|
+
- **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`).
|
|
476
|
+
- **`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`.
|
|
302
477
|
|
|
303
478
|
### Removed (BREAKING)
|
|
304
479
|
|
|
305
|
-
- **`profiles.getCompany(companyId)` removed
|
|
480
|
+
- **`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()`.
|
|
306
481
|
|
|
307
482
|
### Changed (BREAKING)
|
|
308
483
|
|
|
309
|
-
- **`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
|
|
484
|
+
- **`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`.
|
|
310
485
|
|
|
311
|
-
## [0.11.0]
|
|
486
|
+
## [0.11.0] - 2026-07-04
|
|
312
487
|
|
|
313
488
|
### Added
|
|
314
489
|
|
|
315
|
-
- **`accounts.getConnectSession(session_id)`.** Poll a hosted connect session minted by `accounts.createConnectLink()`. A pure status read
|
|
316
|
-
- **`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 }
|
|
317
|
-
- **`session_id` on the `accounts.createConnectLink()` 201 response
|
|
318
|
-
- **`seat_id` on the account-connection responses.** `accounts.link()` (201), `accounts.submitCheckpoint()` (201), and `accounts.pollCheckpoint()` now carry `seat_id` (`string | null`)
|
|
319
|
-
- **`auth_method: "hosted"`** is now a possible value on `accounts.list()` items (accounts connected through a hosted link). Response-only
|
|
320
|
-
- **`"disconnected"` account status.** Now a possible `status` on `accounts.list()` items, `accounts.get()`, and both sides of the account `state-diff` event
|
|
490
|
+
- **`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.
|
|
491
|
+
- **`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.
|
|
492
|
+
- **`session_id` on the `accounts.createConnectLink()` 201 response**, the durable poll handle to pass to `accounts.getConnectSession()`. Type: `AccountConnectLinkResult`.
|
|
493
|
+
- **`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).
|
|
494
|
+
- **`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"`.
|
|
495
|
+
- **`"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.
|
|
321
496
|
|
|
322
497
|
### Deprecated
|
|
323
498
|
|
|
324
|
-
- **`attached_seat_id`** (on `accounts.link()`, `accounts.submitCheckpoint()`, and `accounts.pollCheckpoint()` responses)
|
|
499
|
+
- **`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.
|
|
325
500
|
|
|
326
501
|
### Changed
|
|
327
502
|
|
|
328
|
-
- 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
|
|
503
|
+
- 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.
|
|
329
504
|
|
|
330
|
-
## [0.10.0]
|
|
505
|
+
## [0.10.0] - 2026-07-03
|
|
331
506
|
|
|
332
507
|
### Added
|
|
333
508
|
|
|
334
|
-
- **New `jobs` namespace.** `jobs.get(jobIdOrUrl)` retrieves one public LinkedIn job posting's full detail
|
|
335
|
-
- `recruiter.getJob(jobIdOrUrl)
|
|
509
|
+
- **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`.
|
|
510
|
+
- `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.
|
|
336
511
|
|
|
337
|
-
## [0.9.0]
|
|
512
|
+
## [0.9.0] - 2026-07-03
|
|
338
513
|
|
|
339
514
|
### Added
|
|
340
515
|
|
|
341
|
-
- **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
|
|
342
|
-
- `accounts.get()` gains `seat_id` (`string | null`)
|
|
516
|
+
- **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`.
|
|
517
|
+
- `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.
|
|
343
518
|
|
|
344
519
|
### Changed
|
|
345
520
|
|
|
346
|
-
- `connected_at` (on both `accounts.list()` items and `accounts.get()`) and `last_checked_at` (`accounts.get()`) now consistently emit ISO-8601 UTC (
|
|
347
|
-
- Regenerated types from the current API surface. No resource method signatures changed
|
|
348
|
-
- `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
|
|
521
|
+
- `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.
|
|
522
|
+
- Regenerated types from the current API surface. No resource method signatures changed; purely additive response fields.
|
|
523
|
+
- `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.
|
|
349
524
|
|
|
350
525
|
---
|
|
351
526
|
|
|
352
|
-
## [0.8.0]
|
|
527
|
+
## [0.8.0] - 2026-07-02
|
|
353
528
|
|
|
354
529
|
### Added
|
|
355
530
|
|
|
356
|
-
- **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
|
|
531
|
+
- **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.
|
|
357
532
|
- 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).
|
|
358
|
-
- Server-side parameter defaults are now surfaced in the generated JSDoc (`@default`) wherever the API applies one when a param is omitted
|
|
359
|
-
- **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
|
|
533
|
+
- 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.
|
|
534
|
+
- **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).
|
|
360
535
|
|
|
361
536
|
### Changed
|
|
362
537
|
|
|
363
|
-
- **Search filter guard-rails are now documented.** `search.companies` `limit` requires a minimum of 2 (was 1)
|
|
538
|
+
- **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.
|
|
364
539
|
- Regenerated types from the current API surface. No resource method signatures changed.
|
|
365
540
|
|
|
366
541
|
---
|
|
367
542
|
|
|
368
|
-
## [0.7.0]
|
|
543
|
+
## [0.7.0] - 2026-07-01
|
|
369
544
|
|
|
370
545
|
### Changed
|
|
371
546
|
|
|
372
|
-
- **Breaking (typed consumers):** `recruiter.startChat` 201 response
|
|
373
|
-
- *Why:* product-API + core / Sales Navigator parity
|
|
547
|
+
- **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`.
|
|
548
|
+
- *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.
|
|
374
549
|
- The `recruiter.startChat` request body is unchanged (`attendees_ids` plus all recruiter-specific params).
|
|
375
550
|
- Regenerated types from the updated API surface.
|
|
376
551
|
|
|
377
552
|
---
|
|
378
553
|
|
|
379
|
-
## [0.6.0]
|
|
554
|
+
## [0.6.0] - 2026-07-01
|
|
380
555
|
|
|
381
556
|
### Added
|
|
382
557
|
|
|
@@ -384,19 +559,19 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
384
559
|
|
|
385
560
|
### Changed
|
|
386
561
|
|
|
387
|
-
- **Breaking (typed consumers):** `salesNavigator.syncMessages` and `recruiter.syncMessages
|
|
388
|
-
- **Breaking (typed consumers):** `recruiter.startChat` request body field `attendee_ids`
|
|
389
|
-
- Recruiter start-chat 201 response no longer surfaces a separate `quota` field
|
|
562
|
+
- **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`.
|
|
563
|
+
- **Breaking (typed consumers):** `recruiter.startChat` request body field `attendee_ids` -> `attendees_ids`. The **response** `attendee_ids` field is unchanged. Type: `RecruiterStartChatBody`.
|
|
564
|
+
- Recruiter start-chat 201 response no longer surfaces a separate `quota` field; remaining InMail capacity is read from `GET /v1/accounts/{account_id}`.
|
|
390
565
|
- Regenerated types from the updated API surface (SN/Recruiter messaging parity).
|
|
391
566
|
|
|
392
567
|
---
|
|
393
568
|
|
|
394
|
-
## [0.5.0]
|
|
569
|
+
## [0.5.0] - 2026-07-01
|
|
395
570
|
|
|
396
571
|
### Added
|
|
397
572
|
|
|
398
573
|
- 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`.
|
|
399
|
-
- Search-parameters items (`GET /v1/search/parameters`) now carry nullable company-disambiguation fields
|
|
574
|
+
- 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.
|
|
400
575
|
|
|
401
576
|
### Changed
|
|
402
577
|
|
|
@@ -409,11 +584,11 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
409
584
|
|
|
410
585
|
---
|
|
411
586
|
|
|
412
|
-
## [0.4.2]
|
|
587
|
+
## [0.4.2] - 2026-07-01
|
|
413
588
|
|
|
414
589
|
### Added
|
|
415
590
|
|
|
416
|
-
- Invitation item `specifics` now includes `provider: "LINKEDIN"` on both `InvitationSent` and `InvitationReceived
|
|
591
|
+
- Invitation item `specifics` now includes `provider: "LINKEDIN"` on both `InvitationSent` and `InvitationReceived`, the platform the invitation belongs to, passed through alongside `shared_secret`.
|
|
417
592
|
|
|
418
593
|
### Changed
|
|
419
594
|
|
|
@@ -422,19 +597,19 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
422
597
|
|
|
423
598
|
---
|
|
424
599
|
|
|
425
|
-
## [0.4.1]
|
|
600
|
+
## [0.4.1] - 2026-06-30
|
|
426
601
|
|
|
427
602
|
### Added
|
|
428
603
|
|
|
429
|
-
- `people_search_result` item now includes `id: string
|
|
604
|
+
- `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.
|
|
430
605
|
|
|
431
606
|
---
|
|
432
607
|
|
|
433
|
-
## [0.4.0]
|
|
608
|
+
## [0.4.0] - 2026-06-30
|
|
434
609
|
|
|
435
610
|
### Added
|
|
436
611
|
|
|
437
|
-
- `primary_locale` (`{ country, language } | null`) on the `Profile` and `OwnProfile` types
|
|
612
|
+
- `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.
|
|
438
613
|
|
|
439
614
|
### Changed
|
|
440
615
|
|
|
@@ -443,11 +618,11 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
443
618
|
|
|
444
619
|
---
|
|
445
620
|
|
|
446
|
-
## [0.3.0]
|
|
621
|
+
## [0.3.0] - 2026-06-29
|
|
447
622
|
|
|
448
623
|
### Added
|
|
449
624
|
|
|
450
|
-
- `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
|
|
625
|
+
- `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...`).
|
|
451
626
|
|
|
452
627
|
### Changed
|
|
453
628
|
|
|
@@ -455,15 +630,15 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
455
630
|
|
|
456
631
|
---
|
|
457
632
|
|
|
458
|
-
## [0.2.1]
|
|
633
|
+
## [0.2.1] - 2026-06-29
|
|
459
634
|
|
|
460
635
|
### Fixed
|
|
461
636
|
|
|
462
|
-
- `deleteMessage` and `addReaction` no longer inject `account_id` into the request. The server resolves the owning account from the message id
|
|
637
|
+
- `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.
|
|
463
638
|
|
|
464
639
|
---
|
|
465
640
|
|
|
466
|
-
## [0.2.0]
|
|
641
|
+
## [0.2.0] - 2026-06-28
|
|
467
642
|
|
|
468
643
|
### Added
|
|
469
644
|
|
|
@@ -477,127 +652,127 @@ connection and checkpoint surface was reshaped end-to-end. All changes are on th
|
|
|
477
652
|
|
|
478
653
|
---
|
|
479
654
|
|
|
480
|
-
## [0.1.1]
|
|
655
|
+
## [0.1.1] - 2026-06-22
|
|
481
656
|
|
|
482
657
|
### Fixed
|
|
483
658
|
|
|
484
|
-
- 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
|
|
659
|
+
- 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.
|
|
485
660
|
|
|
486
661
|
---
|
|
487
662
|
|
|
488
|
-
## [0.1.0]
|
|
663
|
+
## [0.1.0] - 2026-06-21
|
|
489
664
|
|
|
490
665
|
Initial public release.
|
|
491
666
|
|
|
492
667
|
### Added
|
|
493
668
|
|
|
494
669
|
**Client and auth**
|
|
495
|
-
- `new Curviate({ apiKey })
|
|
496
|
-
- `curviate.account(accountId)
|
|
670
|
+
- `new Curviate({ apiKey })`: configures the SDK with a Bearer API key, base URL (`https://api.curviate.com`), timeout, and max retries.
|
|
671
|
+
- `curviate.account(accountId)`: returns an account-scoped accessor; all LinkedIn operations are tied to a managed account.
|
|
497
672
|
|
|
498
673
|
**Resource surface**
|
|
499
674
|
|
|
500
675
|
- `curviate.accounts.*`:
|
|
501
|
-
- `list(params?)
|
|
502
|
-
- `get(accountId)
|
|
503
|
-
- `link(body)
|
|
504
|
-
- `reconnect(accountId, body)
|
|
505
|
-
- `refresh(accountId)
|
|
506
|
-
- `update(accountId, body)
|
|
507
|
-
- `disconnect(accountId)
|
|
508
|
-
- `submitCheckpoint(body)
|
|
509
|
-
- `pollCheckpoint(body)
|
|
510
|
-
- `createConnectLink(body)
|
|
676
|
+
- `list(params?)`: list managed accounts with optional `limit` and `cursor`.
|
|
677
|
+
- `get(accountId)`: fetch a single managed account.
|
|
678
|
+
- `link(body)`: connect a LinkedIn account via cookie or API key; returns an account or a checkpoint.
|
|
679
|
+
- `reconnect(accountId, body)`: reconnect a disconnected account.
|
|
680
|
+
- `refresh(accountId)`: force a session refresh.
|
|
681
|
+
- `update(accountId, body)`: update account settings.
|
|
682
|
+
- `disconnect(accountId)`: disconnect a managed account.
|
|
683
|
+
- `submitCheckpoint(body)`: submit a 2FA / CAPTCHA checkpoint challenge.
|
|
684
|
+
- `pollCheckpoint(body)`: poll for checkpoint resolution.
|
|
685
|
+
- `createConnectLink(body)`: generate a hosted connect link.
|
|
511
686
|
|
|
512
687
|
- `curviate.account(id).messaging.*`:
|
|
513
|
-
- `listChats(params?)
|
|
514
|
-
- `getChat(chatId)
|
|
515
|
-
- `startChat(body)
|
|
516
|
-
- `listMessages(chatId, params?)
|
|
517
|
-
- `getMessage(messageId)
|
|
518
|
-
- `sendMessage(chatId, body)
|
|
519
|
-
- `editMessage(messageId, body)
|
|
520
|
-
- `deleteMessage(messageId)
|
|
521
|
-
- `addReaction(messageId, body)
|
|
522
|
-
- `getAttachment(messageId, attachmentId)
|
|
523
|
-
- `syncChat(chatId)
|
|
524
|
-
- `syncMessages(params?)
|
|
525
|
-
- `sendInMail(body)
|
|
526
|
-
- `getInMailBalance(params?)
|
|
688
|
+
- `listChats(params?)`: list conversation threads.
|
|
689
|
+
- `getChat(chatId)`: fetch a single chat.
|
|
690
|
+
- `startChat(body)`: start a new conversation.
|
|
691
|
+
- `listMessages(chatId, params?)`: list messages in a chat.
|
|
692
|
+
- `getMessage(messageId)`: fetch a single message.
|
|
693
|
+
- `sendMessage(chatId, body)`: send a message into a chat.
|
|
694
|
+
- `editMessage(messageId, body)`: edit a sent message.
|
|
695
|
+
- `deleteMessage(messageId)`: delete a sent message.
|
|
696
|
+
- `addReaction(messageId, body)`: add an emoji reaction.
|
|
697
|
+
- `getAttachment(messageId, attachmentId)`: download a message attachment.
|
|
698
|
+
- `syncChat(chatId)`: trigger a chat history sync.
|
|
699
|
+
- `syncMessages(params?)`: trigger a full message sync.
|
|
700
|
+
- `sendInMail(body)`: send an InMail.
|
|
701
|
+
- `getInMailBalance(params?)`: fetch InMail credit balance.
|
|
527
702
|
|
|
528
703
|
- `curviate.account(id).profiles.*`:
|
|
529
|
-
- `get(profileId, params?)
|
|
530
|
-
- `getMe()
|
|
531
|
-
- `getCompany(companyId)
|
|
532
|
-
- `listConnections(params?)
|
|
533
|
-
- `listFollowers(profileId, params?)
|
|
534
|
-
- `listPosts(profileId, params?)
|
|
535
|
-
- `listComments(profileId, params?)
|
|
536
|
-
- `listReactions(profileId, params?)
|
|
537
|
-
- `endorse(profileId, body)
|
|
704
|
+
- `get(profileId, params?)`: fetch a LinkedIn profile by id or handle.
|
|
705
|
+
- `getMe()`: fetch the profile for the managed account holder.
|
|
706
|
+
- `getCompany(companyId)`: fetch a company profile.
|
|
707
|
+
- `listConnections(params?)`: list the account's connections.
|
|
708
|
+
- `listFollowers(profileId, params?)`: list a profile's followers.
|
|
709
|
+
- `listPosts(profileId, params?)`: list a profile's posts.
|
|
710
|
+
- `listComments(profileId, params?)`: list a profile's post comments.
|
|
711
|
+
- `listReactions(profileId, params?)`: list a profile's post reactions.
|
|
712
|
+
- `endorse(profileId, body)`: endorse skills on a profile.
|
|
538
713
|
|
|
539
714
|
- `curviate.account(id).invites.*`:
|
|
540
|
-
- `send(body)
|
|
541
|
-
- `listSent(params?)
|
|
542
|
-
- `listReceived(params?)
|
|
543
|
-
- `respond(invitationId, body)
|
|
544
|
-
- `cancel(invitationId)
|
|
715
|
+
- `send(body)`: send a connection invitation.
|
|
716
|
+
- `listSent(params?)`: list sent invitations.
|
|
717
|
+
- `listReceived(params?)`: list received invitations.
|
|
718
|
+
- `respond(invitationId, body)`: accept or ignore a received invitation.
|
|
719
|
+
- `cancel(invitationId)`: cancel a sent invitation.
|
|
545
720
|
|
|
546
721
|
- `curviate.account(id).posts.*`:
|
|
547
|
-
- `list(params?)
|
|
548
|
-
- `get(postId)
|
|
549
|
-
- `create(body)
|
|
550
|
-
- `listComments(postId, params?)
|
|
551
|
-
- `comment(postId, body)
|
|
552
|
-
- `listReactions(postId, params?)
|
|
553
|
-
- `react(postId, body)
|
|
722
|
+
- `list(params?)`: list posts visible to the account.
|
|
723
|
+
- `get(postId)`: fetch a single post.
|
|
724
|
+
- `create(body)`: create a post.
|
|
725
|
+
- `listComments(postId, params?)`: list comments on a post.
|
|
726
|
+
- `comment(postId, body)`: comment on a post.
|
|
727
|
+
- `listReactions(postId, params?)`: list reactions on a post.
|
|
728
|
+
- `react(postId, body)`: react to a post.
|
|
554
729
|
|
|
555
730
|
- `curviate.account(id).salesNavigator.*`:
|
|
556
|
-
- `searchPeople(body, params?)
|
|
557
|
-
- `searchCompanies(body, params?)
|
|
558
|
-
- `getProfile(identifier, params?)
|
|
559
|
-
- `getParameters(params)
|
|
560
|
-
- `saveLead(userId, body)
|
|
561
|
-
- `startChat(body)
|
|
562
|
-
- `syncMessages(params)
|
|
731
|
+
- `searchPeople(body, params?)`: search people via Sales Navigator.
|
|
732
|
+
- `searchCompanies(body, params?)`: search companies via Sales Navigator.
|
|
733
|
+
- `getProfile(identifier, params?)`: fetch a Sales Navigator profile.
|
|
734
|
+
- `getParameters(params)`: fetch available search filter parameters.
|
|
735
|
+
- `saveLead(userId, body)`: save a lead.
|
|
736
|
+
- `startChat(body)`: start a Sales Navigator conversation.
|
|
737
|
+
- `syncMessages(params)`: sync Sales Navigator messages.
|
|
563
738
|
|
|
564
739
|
- `curviate.account(id).recruiter.*`:
|
|
565
|
-
- `searchPeople(body, params?)
|
|
566
|
-
- `getProfile(identifier, params?)
|
|
567
|
-
- `getParameters(params)
|
|
568
|
-
- `syncMessages(params)
|
|
569
|
-
- `startChat(body)
|
|
570
|
-
- `listProjects(params?)
|
|
571
|
-
- `getProject(projectId)
|
|
572
|
-
- `addCandidate(userId, body)
|
|
573
|
-
- `addApplicant(userId, body)
|
|
574
|
-
- `rejectApplicant(userId, body)
|
|
575
|
-
- `listJobs(params?)
|
|
576
|
-
- `createJob(body)
|
|
577
|
-
- `publishJob(jobId, body)
|
|
578
|
-
- `solveJobCheckpoint(jobId, body)
|
|
579
|
-
- `listApplicants(jobId, params?)
|
|
580
|
-
- `getApplicant(applicantId)
|
|
581
|
-
- `downloadResume(applicantId)
|
|
740
|
+
- `searchPeople(body, params?)`: search candidates via LinkedIn Recruiter.
|
|
741
|
+
- `getProfile(identifier, params?)`: fetch a Recruiter candidate profile.
|
|
742
|
+
- `getParameters(params)`: fetch available search filter parameters.
|
|
743
|
+
- `syncMessages(params)`: sync Recruiter messages.
|
|
744
|
+
- `startChat(body)`: start a Recruiter conversation.
|
|
745
|
+
- `listProjects(params?)`: list Recruiter projects.
|
|
746
|
+
- `getProject(projectId)`: fetch a single Recruiter project.
|
|
747
|
+
- `addCandidate(userId, body)`: add a candidate to a project.
|
|
748
|
+
- `addApplicant(userId, body)`: add an applicant to a job.
|
|
749
|
+
- `rejectApplicant(userId, body)`: reject an applicant.
|
|
750
|
+
- `listJobs(params?)`: list Recruiter job postings.
|
|
751
|
+
- `createJob(body)`: create a Recruiter job posting.
|
|
752
|
+
- `publishJob(jobId, body)`: publish a Recruiter job posting.
|
|
753
|
+
- `solveJobCheckpoint(jobId, body)`: solve a job-posting checkpoint.
|
|
754
|
+
- `listApplicants(jobId, params?)`: list applicants for a job.
|
|
755
|
+
- `getApplicant(applicantId)`: fetch a single applicant.
|
|
756
|
+
- `downloadResume(applicantId)`: download an applicant's resume.
|
|
582
757
|
|
|
583
758
|
- `curviate.account(id).search.*` (classic LinkedIn search):
|
|
584
|
-
- `people(body)
|
|
585
|
-
- `companies(body)
|
|
586
|
-
- `posts(body)
|
|
587
|
-
- `jobs(body)
|
|
588
|
-
- `getParameters(query)
|
|
759
|
+
- `people(body)`: search people.
|
|
760
|
+
- `companies(body)`: search companies.
|
|
761
|
+
- `posts(body)`: search posts.
|
|
762
|
+
- `jobs(body)`: search jobs.
|
|
763
|
+
- `getParameters(query)`: fetch available search filter parameters.
|
|
589
764
|
|
|
590
765
|
- `curviate.webhooks.*`:
|
|
591
|
-
- `create(body)
|
|
592
|
-
- `list(params?)
|
|
593
|
-
- `update(id, body)
|
|
594
|
-
- `delete(id)
|
|
595
|
-
- `listEvents()
|
|
596
|
-
- `getStateDiff(accountId, params?)
|
|
766
|
+
- `create(body)`: register a new webhook endpoint.
|
|
767
|
+
- `list(params?)`: list registered webhooks.
|
|
768
|
+
- `update(id, body)`: update a webhook endpoint.
|
|
769
|
+
- `delete(id)`: delete a webhook endpoint.
|
|
770
|
+
- `listEvents()`: list available webhook event types.
|
|
771
|
+
- `getStateDiff(accountId, params?)`: fetch an account state diff.
|
|
597
772
|
|
|
598
773
|
**Typed error model**
|
|
599
|
-
- `CurviateError
|
|
600
|
-
- `isCurviateError(err)
|
|
774
|
+
- `CurviateError`: single thrown type for all API errors, carrying a stable `code`, `httpStatus`, `retryHint`, `userFixable`, `retryLikelyToSucceed`, and `requiredTier`.
|
|
775
|
+
- `isCurviateError(err)`: type guard for narrowing in `catch` blocks.
|
|
601
776
|
- 34-member `ErrorCode` union covering every observable API error code.
|
|
602
777
|
|
|
603
778
|
**HTTP transport with retry and backoff**
|
|
@@ -607,13 +782,13 @@ Initial public release.
|
|
|
607
782
|
- JSON and binary (ArrayBuffer) response parsing.
|
|
608
783
|
|
|
609
784
|
**Cursor pagination**
|
|
610
|
-
- `curviate.paginate(method, params?)
|
|
785
|
+
- `curviate.paginate(method, params?)`: async iterator that follows the `cursor` field automatically.
|
|
611
786
|
|
|
612
787
|
**Webhook signature verification**
|
|
613
|
-
- `constructEvent(rawBody, signatureHeader, secret, opts?)
|
|
614
|
-
- `WebhookSignatureError
|
|
615
|
-
- `CurviateEvent
|
|
788
|
+
- `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.
|
|
789
|
+
- `WebhookSignatureError`: thrown on malformed header, invalid signature, or replay; not a `CurviateError`.
|
|
790
|
+
- `CurviateEvent`: 19-member discriminated union covering all canonical webhook event types.
|
|
616
791
|
|
|
617
792
|
**TypeScript types**
|
|
618
|
-
- Full OpenAPI-generated types at `src/generated/types.ts
|
|
793
|
+
- Full OpenAPI-generated types at `src/generated/types.ts`: request bodies, responses, and path params.
|
|
619
794
|
- ESM-only build targeting ES2020; works in Node 18+, Cloudflare Workers, Vercel Edge.
|