@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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @curviate/sdk
2
2
 
3
- The official TypeScript SDK for the [Curviate API](https://docs.curviate.com) — agent-native
3
+ The official TypeScript SDK for the [Curviate API](https://docs.curviate.com). Agent-native
4
4
  LinkedIn infrastructure for AI engineers and agent builders.
5
5
 
6
- > **Status:** `0.16.0` — pre-1.0. Full v2 API parity; the surface is public but not yet stability-promised.
6
+ > **Status:** pre-1.0. Full v2 API parity; the surface is public but not yet stability-promised.
7
7
 
8
8
  ---
9
9
 
@@ -17,7 +17,7 @@ Requires Node 18+. Works in Cloudflare Workers and Vercel Edge.
17
17
 
18
18
  ---
19
19
 
20
- ## Auth — construct the client
20
+ ## Auth: construct the client
21
21
 
22
22
  Get your API key from the Curviate dashboard. Store it as an environment variable.
23
23
 
@@ -37,7 +37,7 @@ const curviate = new Curviate({
37
37
 
38
38
  ## Account-scoped accessor
39
39
 
40
- Every LinkedIn operation (messages, member profiles, invites, posts) is tied to a **managed account** — a LinkedIn session you have connected via the connect flow (`curviate.auth.intent()`). The `curviate.account(id)` accessor fixes the `account_id` on every call so you do not have to thread it manually:
40
+ Every LinkedIn operation (messages, member profiles, invites, posts) is tied to a **managed account**, a LinkedIn session you have connected via the connect flow (`curviate.auth.intent()`). The `curviate.account(id)` accessor fixes the `account_id` on every call so you do not have to thread it manually:
41
41
 
42
42
  ```ts
43
43
  // Root-level: tenant-wide operations (accounts, auth, webhooks)
@@ -56,7 +56,7 @@ const profile = await acc.users.get("some-user-id"); // someone else's
56
56
 
57
57
  ## First end-to-end call
58
58
 
59
- This snippet lists your connected accounts, picks the first one, and sends a message — something an agent might do to automate outreach:
59
+ This snippet lists your connected accounts, picks the first one, and sends a message, something an agent might do to automate outreach:
60
60
 
61
61
  ```ts
62
62
  import { Curviate, isCurviateError } from "@curviate/sdk";
@@ -78,7 +78,7 @@ async function sendFirstMessage() {
78
78
  // 3. Send a message to the first chat
79
79
  const chat = chats[0]!;
80
80
  await acc.messaging.sendMessage(chat.id ?? "", {
81
- text: "Hi — following up from our conversation.",
81
+ text: "Hi, following up from our conversation.",
82
82
  });
83
83
 
84
84
  console.log("Message sent.");
@@ -114,20 +114,22 @@ try {
114
114
  break;
115
115
  default:
116
116
  if (err.retryLikelyToSucceed) {
117
- // Safe to retry — server-side transient error
117
+ // Safe to retry: server-side transient error
118
118
  await retry();
119
119
  }
120
120
  }
121
121
  }
122
122
  ```
123
123
 
124
- All 34 error codes are documented in the [API reference](https://docs.curviate.com).
124
+ Every error code is documented in the [API reference](https://docs.curviate.com). The
125
+ exported `ErrorCode` type is the complete set, so `tsc` tells you when a `switch` over
126
+ `err.code` has missed one.
125
127
 
126
128
  ---
127
129
 
128
130
  ## Cursor pagination
129
131
 
130
- Resources that return lists support cursor pagination. `curviate.paginate()` is an async iterator that follows the `cursor` field automatically — pull items one at a time without managing cursors:
132
+ Resources that return lists support cursor pagination. `curviate.paginate()` is an async iterator that follows the `cursor` field automatically, so you pull items one at a time without managing cursors:
131
133
 
132
134
  ```ts
133
135
  // Iterate over every chat across all pages
@@ -163,27 +165,28 @@ app.post("/webhook", express.raw({ type: "application/json" }), async (req, res)
163
165
  event = await constructEvent(req.body, sig, secret);
164
166
  } catch (err) {
165
167
  if (err instanceof WebhookSignatureError) {
166
- console.warn("Bad webhook:", err.reason); // 'invalid_signature' | 'replay_detected' | 'malformed_header'
168
+ // 'invalid_signature' | 'replay_detected' | 'malformed_header' | 'malformed_payload'
169
+ console.warn("Bad webhook:", err.reason);
167
170
  return res.sendStatus(400);
168
171
  }
169
172
  throw err;
170
173
  }
171
174
 
172
- switch (event.type) {
175
+ switch (event.event) {
173
176
  case "message.received":
174
- // event.data is MessagePayload — account_id, message_id, etc.
177
+ // event.data is MessagePayload: account_id, message_id, and so on.
175
178
  handleNewMessage(event.data);
176
179
  break;
177
180
  case "account.connected":
178
181
  handleAccountConnected(event.data);
179
182
  break;
180
- // 22 more event types — all in the CurviateEvent union
183
+ // Every other event type is a case in the CurviateEvent union.
181
184
  }
182
185
 
183
186
  res.sendStatus(200);
184
187
  });
185
188
 
186
- // Hono / Vercel Edge — always await (Web Crypto is async)
189
+ // Hono / Vercel Edge. Always await; Web Crypto is async.
187
190
  app.post("/webhook", async (c) => {
188
191
  const rawBody = await c.req.text();
189
192
  const event = await constructEvent(rawBody, c.req.header("curviate-signature")!, secret);
@@ -191,16 +194,46 @@ app.post("/webhook", async (c) => {
191
194
  });
192
195
  ```
193
196
 
194
- `constructEvent` always returns a `Promise<CurviateEvent>` — always `await` it.
197
+ `constructEvent` always returns a `Promise<CurviateEvent>`. Always `await` it.
195
198
 
196
- `WebhookSignatureError` is NOT a `CurviateError` — narrow with `instanceof WebhookSignatureError`.
199
+ Each event also carries its delivery metadata: `event.id`, `event.webhook_id`, and
200
+ `event.delivered_at`.
201
+
202
+ ### De-duplicating retries
203
+
204
+ Delivery is at-least-once, so your handler can see the same logical event more than
205
+ once. The key that survives a retry is the payload composite `event.event` plus
206
+ `event.data.account_id` plus `event.data.occurred_at`, which the platform re-sends
207
+ byte-identical on every attempt:
208
+
209
+ ```ts
210
+ const key = `${event.event}:${event.data.account_id}:${event.data.occurred_at}`;
211
+ if (await alreadyProcessed(key)) return res.sendStatus(200);
212
+ ```
213
+
214
+ **Do not key on `event.id`.** That is a `wdl_` id minted per delivery *attempt*, so a
215
+ retry arrives with a different one and storing it would record the same event twice,
216
+ missing exactly the duplicates you were guarding against. It is still worth logging:
217
+ `event.id` is how you match one attempt to one line in your delivery logs, keyed
218
+ alongside the stable `occurred_at` composite above.
219
+
220
+ `WebhookSignatureError` is NOT a `CurviateError`. Narrow with `instanceof WebhookSignatureError`.
221
+ Its `reason` tells you where to look: `malformed_header` means the signature header could not be
222
+ read, `invalid_signature` means the HMAC did not match (check the secret, and check that you passed
223
+ the raw request bytes), `replay_detected` means the event is outside the replay window, and
224
+ `malformed_payload` means the signature was **valid** but the body was not a Curviate event, so
225
+ your secret and header are both fine.
226
+
227
+ > **Upgrading from 0.18.x or earlier?** The event discriminant is `event.event`, not `event.type`.
228
+ > See the 0.19.0 entry in [CHANGELOG.md](./CHANGELOG.md); the change is a one-word edit and it
229
+ > breaks no working code, because `constructEvent` never returned successfully before 0.19.0.
197
230
 
198
231
  ---
199
232
 
200
233
  ## Links
201
234
 
202
235
  - API reference: https://docs.curviate.com
203
- - Issues: https://github.com/curviate/sdk/issues
236
+ - Issues: https://github.com/curviate/curviate-sdk/issues
204
237
  - Changelog: [CHANGELOG.md](./CHANGELOG.md)
205
238
 
206
239
  ## License