@curviate/sdk 0.40.1 → 0.42.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/dist/index.js CHANGED
@@ -79,6 +79,19 @@ var ERROR_CODES = [
79
79
  "CHECKPOINT_ALREADY_RESOLVED",
80
80
  "CHECKPOINT_UNSUPPORTED",
81
81
  "CONNECTION_IN_PROGRESS",
82
+ // Connection location (where LinkedIn sees the account connecting from).
83
+ // `CONNECTION_LOCATION_REQUIRED` (400): a new connect named no location, or
84
+ // a PATCH cleared your own proxy (`proxy: null`) without a `country`. Send
85
+ // exactly one of `country`, `ip` or `proxy`. user_fixable, never retryable
86
+ // as sent.
87
+ // `CONNECTION_LOCATION_UNAVAILABLE` (422): no connection is free in that
88
+ // country right now (strict), or the change landed elsewhere. When a change
89
+ // was applied, {@link CurviateError.connectionLocation} says where the
90
+ // account connects from now, which may mean your own proxy is already gone.
91
+ // Try a nearby country, or allow fallback. user_fixable, never retryable as
92
+ // sent.
93
+ "CONNECTION_LOCATION_REQUIRED",
94
+ "CONNECTION_LOCATION_UNAVAILABLE",
82
95
  // A reconnect whose seat-derived scope differs from the account's recorded
83
96
  // scope was attempted with cookie auth; a cookie replay cannot change
84
97
  // scope, so a full credentials re-authentication is required. user_fixable,
@@ -167,6 +180,28 @@ var ERROR_CODES = [
167
180
  // was charged. Stop and surface it to a human (or support) rather than
168
181
  // backing off.
169
182
  "STRIPE_DRIFT_DETECTED",
183
+ // Drafts and scheduled posts (`/v1/drafts`). Each is user_fixable and none
184
+ // clears on an unchanged retry; read the code, then change the request.
185
+ //
186
+ // - `DRAFT_LIMIT_REACHED` (422): the bucket (one account, or "no account")
187
+ // already holds 50 Drafts. Delete one, or use another account.
188
+ // - `MEDIA_QUOTA_EXCEEDED` (422): the file would take the bucket past its
189
+ // media quota; nothing was stored. Delete media first.
190
+ // - `ACCOUNT_REQUIRED` (422): publishing or scheduling needs an account on the
191
+ // Draft (also `account_id: null` on a scheduled Draft: unschedule first).
192
+ // - `DRAFT_NOT_PUBLISHABLE` (422): a content gap (empty text, ...); the
193
+ // message names the field.
194
+ // - `SCHEDULE_CONFLICT` (422): another scheduled Draft on the same account is
195
+ // within 5 minutes of this `scheduled_at`; the message names it.
196
+ // - `DRAFT_PUBLISHING` (409): a publish of this Draft is in flight (or its
197
+ // outcome is being settled). Read the Draft again in a few seconds; if it
198
+ // says to check the account's posts first, do that before retrying.
199
+ "DRAFT_LIMIT_REACHED",
200
+ "MEDIA_QUOTA_EXCEEDED",
201
+ "ACCOUNT_REQUIRED",
202
+ "DRAFT_NOT_PUBLISHABLE",
203
+ "SCHEDULE_CONFLICT",
204
+ "DRAFT_PUBLISHING",
170
205
  // Generic
171
206
  "INTERNAL"
172
207
  ];
@@ -187,6 +222,7 @@ var CurviateError = class _CurviateError extends Error {
187
222
  this.safetyHint = init.safetyHint;
188
223
  this.safetyReason = init.safetyReason;
189
224
  this.blocked = init.blocked;
225
+ this.connectionLocation = init.connectionLocation;
190
226
  Object.setPrototypeOf(this, _CurviateError.prototype);
191
227
  }
192
228
  /**
@@ -211,6 +247,7 @@ var CurviateError = class _CurviateError extends Error {
211
247
  if (this.safetyHint !== void 0) json.safetyHint = this.safetyHint;
212
248
  if (this.safetyReason !== void 0) json.safetyReason = this.safetyReason;
213
249
  if (this.blocked !== void 0) json.blocked = this.blocked;
250
+ if (this.connectionLocation !== void 0) json.connectionLocation = this.connectionLocation;
214
251
  return json;
215
252
  }
216
253
  };
@@ -325,7 +362,8 @@ async function errorFromResponse(res) {
325
362
  ...typeof env?.reset_at === "string" || env?.reset_at === null ? { resetAt: env.reset_at } : {},
326
363
  ...safetyHint !== void 0 ? { safetyHint } : {},
327
364
  ...safetyReason !== void 0 ? { safetyReason } : {},
328
- ...typeof env?.blocked === "boolean" ? { blocked: env.blocked } : {}
365
+ ...typeof env?.blocked === "boolean" ? { blocked: env.blocked } : {},
366
+ ...env?.connection_location !== null && typeof env?.connection_location === "object" ? { connectionLocation: env.connection_location } : {}
329
367
  });
330
368
  return { err, refusesRetry: env?.retry_likely_to_succeed === false };
331
369
  }
@@ -341,6 +379,11 @@ function buildInit(method, opts, signal) {
341
379
  authorization: `Bearer ${opts.apiKey}`
342
380
  };
343
381
  const init = { method, headers, signal };
382
+ if (opts.rawBody !== void 0 && method !== "GET" && method !== "HEAD") {
383
+ headers["content-type"] = opts.rawBody.contentType;
384
+ init.body = opts.rawBody.data;
385
+ return init;
386
+ }
344
387
  if (method === "GET" || method === "HEAD" || opts.body === void 0) {
345
388
  return init;
346
389
  }
@@ -471,7 +514,8 @@ function createContext(config, accountId) {
471
514
  maxRetries: config.maxRetries,
472
515
  ...config.fetch ? { fetch: config.fetch } : {},
473
516
  ...args.query !== void 0 ? { query: args.query } : {},
474
- ...args.body !== void 0 ? { body: args.body } : {}
517
+ ...args.body !== void 0 ? { body: args.body } : {},
518
+ ...args.rawBody !== void 0 ? { rawBody: args.rawBody } : {}
475
519
  });
476
520
  };
477
521
  return { request, accountId };
@@ -543,7 +587,7 @@ var AccountsResource = class {
543
587
  * const { items } = await curviate.accounts.listSeats();
544
588
  * const free = items.find((seat) => !seat.occupied);
545
589
  * if (!free) throw new Error("No seat is free to connect to right now (add a seat, or check billing).");
546
- * await curviate.auth.intent({ seat_id: free.seat_id, auth_method: "cookie", cookie: { li_at }, user_agent });
590
+ * await curviate.auth.intent({ seat_id: free.seat_id, auth_method: "cookie", cookie: { li_at }, user_agent, country: "US" });
547
591
  */
548
592
  listSeats() {
549
593
  return this.ctx.request({ method: "GET", path: "/v1/accounts/seats" });
@@ -597,13 +641,30 @@ var AccountsResource = class {
597
641
  * `metadata` is a flat string->string map (at most 16 keys, key 40 chars,
598
642
  * value 500) that **replaces** the stored map wholesale; `null` clears it.
599
643
  * Both are stored by Curviate, read back on `get`/`list`, and a PATCH
600
- * carrying only them makes no LinkedIn call. `proxy` sets a custom egress
601
- * proxy, or clears it (reverting to automatic proxy protection) when passed
602
- * as `null`. The `proxy.password`, if given, is stored securely and never
603
- * returned.
644
+ * carrying only them makes no LinkedIn call.
645
+ *
646
+ * `country` moves the account's connection location to that supported ISO
647
+ * country. LinkedIn sees a new IP there and may ask the owner to verify, so
648
+ * change it only when the current location is wrong. Strict unless
649
+ * `allow_country_fallback: true`; `allow_country_fallback` alone changes the
650
+ * strictness of the configured country. `proxy` sets your own proxy; `null`
651
+ * returns to a managed location and **requires** a `country` in the same
652
+ * call (else `CONNECTION_LOCATION_REQUIRED`), and an account on your own
653
+ * proxy can only take a `country` together with `proxy: null`. The
654
+ * `proxy.password`, if given, is never returned.
655
+ *
656
+ * Returns the account (as `get` returns it, without `quotas` and
657
+ * `event_log`). After a location change its `connection_location` is re-read,
658
+ * so it shows where the account really connects from now. A change that did
659
+ * not land where asked throws `CONNECTION_LOCATION_UNAVAILABLE`, whose
660
+ * `connectionLocation` carries the actual location.
604
661
  *
605
662
  * @example
606
663
  * await curviate.accounts.update("acc_123", { external_id: "usr_42", metadata: { plan: "pro" } });
664
+ *
665
+ * @example
666
+ * const account = await curviate.accounts.update("acc_123", { country: "DE" });
667
+ * console.log(account.connection_location?.country); // "DE"
607
668
  */
608
669
  update(accountId, body) {
609
670
  return this.ctx.request({
@@ -623,7 +684,7 @@ var AccountsResource = class {
623
684
  }
624
685
  /**
625
686
  * Buy seats on the workspace's active subscription. Returns the new
626
- * `seat_ids`; connect an account into one with `auth.intent({ seat_id })`.
687
+ * `seat_ids`; connect an account into one with `auth.intent({ seat_id, country, ... })`.
627
688
  *
628
689
  * Every call buys the `qty` it asks for, so a repeated call buys again.
629
690
  * The SDK never retries it for you. A
@@ -728,8 +789,30 @@ var AuthResource = class {
728
789
  * LinkedIn account does not hold throws
729
790
  * `CurviateError(code: "LINKEDIN_FEATURE_NOT_SUBSCRIBED")` (403); the remedy
730
791
  * is to activate the subscription on LinkedIn or to point `linkedin_premium`
731
- * at the premium the account really has. Pin a managed proxy with the
732
- * optional `country`/`ip`, or supply `proxy` to override it entirely.
792
+ * at the premium the account really has.
793
+ *
794
+ * **Connection location.** A NEW connect must say where LinkedIn sees the
795
+ * account connecting from, with exactly one of: `country` (a supported ISO
796
+ * code such as `"US"`; pick the one the owner normally signs in from), `ip`
797
+ * (a public IPv4 whose country is used) or your own `proxy`. Without one the
798
+ * API throws `CurviateError(code: "CONNECTION_LOCATION_REQUIRED")`, and the
799
+ * {@link AuthIntentBody} type refuses it at compile time. `country` and `ip`
800
+ * are strict by default: the connect fails with
801
+ * `CONNECTION_LOCATION_UNAVAILABLE` rather than use another country, unless
802
+ * you send `allow_country_fallback: true`. A reconnect may omit the location
803
+ * to keep the account's configured one. The account's `connection_location`
804
+ * says where it connects from; when a connect re-attached an account you
805
+ * already had and moved it, `connection_location.previous_country` names
806
+ * where it was.
807
+ *
808
+ * @example
809
+ * const result = await curviate.auth.intent({
810
+ * seat_id: "YOUR_SEAT_ID",
811
+ * auth_method: "credentials",
812
+ * credentials: { email: "YOUR_EMAIL", password: "YOUR_PASSWORD" },
813
+ * country: "US",
814
+ * });
815
+ * console.log(result.account_id);
733
816
  */
734
817
  intent(body) {
735
818
  return this.ctx.request({
@@ -777,7 +860,7 @@ var AuthResource = class {
777
860
  * @param body - optional `{ challenge }` for a `challenge_selection` checkpoint.
778
861
  *
779
862
  * @example
780
- * const cp = await curviate.auth.intent({ seat_id, auth_method: "credentials", credentials });
863
+ * const cp = await curviate.auth.intent({ seat_id, auth_method: "credentials", credentials, country: "US" });
781
864
  * if (cp.object === "checkpoint" && cp.challenge_type === "challenge_selection") {
782
865
  * const next = await curviate.auth.requestCheckpoint(cp.account_id!, { challenge: cp.challenges![0]!.id });
783
866
  * }
@@ -1387,15 +1470,15 @@ var PostsResource = class {
1387
1470
  * Get a single post. `GET /v1/{account_id}/posts/{post_id}`
1388
1471
  *
1389
1472
  * @param postId - the post's `id` as returned by {@link listUserPosts} or
1390
- * {@link create}. A bare numeric id (`7332661864792854528`), a URN
1391
- * (`urn:li:activity:N`, `urn:li:ugcPost:N`, `urn:li:share:N`), and a full
1392
- * share URL (`https://www.linkedin.com/posts/...` or
1473
+ * {@link create}, or a post search result's `id`. A bare numeric id
1474
+ * (`7332661864792854528`), a URN (`urn:li:activity:N`, `urn:li:ugcPost:N`,
1475
+ * `urn:li:share:N`), and a full post URL (`https://www.linkedin.com/posts/...`
1476
+ * with an `activity-`, `ugcPost-` or `share-` slug, or
1393
1477
  * `https://www.linkedin.com/feed/update/urn:li:activity:N`) all resolve to
1394
1478
  * the same post. Pass the value as-is; it is percent-encoded into the path
1395
1479
  * for you, so do not encode it yourself.
1396
1480
  *
1397
- * {@link listComments} and the reaction operations are stricter: they take
1398
- * the opaque `id` form only.
1481
+ * {@link listComments} and the reaction operations accept the same forms.
1399
1482
  */
1400
1483
  get(postId) {
1401
1484
  return this.ctx.request({
@@ -3034,6 +3117,145 @@ var WebhooksResource = class {
3034
3117
  }
3035
3118
  };
3036
3119
 
3120
+ // src/resources/drafts.ts
3121
+ var DraftsResource = class {
3122
+ constructor(ctx) {
3123
+ this.ctx = ctx;
3124
+ }
3125
+ /**
3126
+ * Store a Draft. Every field is optional, `account_id` and `text` included.
3127
+ * `POST /v1/drafts`
3128
+ *
3129
+ * With `scheduled_at` (ISO 8601 with offset, between 5 minutes and 365 days
3130
+ * ahead) the Draft is scheduled in the same call and needs an account and
3131
+ * publishable text. Up to 50 Drafts and 2 GiB of media per account, and per
3132
+ * no-account. A retry creates a second Draft.
3133
+ *
3134
+ * @example
3135
+ * const draft = await curviate.drafts.create({
3136
+ * account_id: "acc_01J8Z3K9P0Q1R2S3T4V5W6X7Y8",
3137
+ * text: "Three things we learned shipping our first agent integration.",
3138
+ * scheduled_at: "2026-10-12T09:00:00+02:00",
3139
+ * });
3140
+ * // draft.status === "scheduled"
3141
+ */
3142
+ create(body = {}) {
3143
+ return this.ctx.request({
3144
+ method: "POST",
3145
+ path: "/v1/drafts",
3146
+ body
3147
+ });
3148
+ }
3149
+ /**
3150
+ * List Drafts, cursor-paginated. `GET /v1/drafts`
3151
+ *
3152
+ * `status` defaults to `["draft", "scheduled", "failed"]`; add `"published"`
3153
+ * to include publish records (`object: "publish_record"`, never Drafts).
3154
+ * `account_id` is an `acc_` id or `"none"`. `from`/`to` are a half-open
3155
+ * `[from, to)` range on each item's own date.
3156
+ *
3157
+ * @example
3158
+ * const page = await curviate.drafts.list({ status: ["scheduled"], order: "asc" });
3159
+ */
3160
+ list(params) {
3161
+ const { status, ...rest } = params ?? {};
3162
+ return this.ctx.request({
3163
+ method: "GET",
3164
+ path: "/v1/drafts",
3165
+ query: { ...rest, ...status !== void 0 ? { status: status.join(",") } : {} }
3166
+ });
3167
+ }
3168
+ /**
3169
+ * Return one Draft, with a fresh signed link (valid 1 hour) per attachment.
3170
+ * `GET /v1/drafts/{id}`
3171
+ *
3172
+ * @example
3173
+ * const draft = await curviate.drafts.get("drf_01J8Z3K9P0Q1R2S3T4V5W6X7Y8");
3174
+ */
3175
+ get(id) {
3176
+ return this.ctx.request({
3177
+ method: "GET",
3178
+ path: apiPath`/v1/drafts/${id}`
3179
+ });
3180
+ }
3181
+ /**
3182
+ * Partial update: an omitted field is left as is, `null` clears it.
3183
+ * `PATCH /v1/drafts/{id}`
3184
+ *
3185
+ * `attachments` replaces the whole list (`{ id }` keeps a file, a base64
3186
+ * object adds one, anything left out is deleted). `scheduled_at` schedules,
3187
+ * reschedules or, with `null`, unschedules. Editing a `failed` Draft clears
3188
+ * its `failure`. A Draft being published answers `409 DRAFT_PUBLISHING`.
3189
+ *
3190
+ * @example
3191
+ * await curviate.drafts.update(id, { scheduled_at: null }); // unschedule
3192
+ */
3193
+ update(id, body) {
3194
+ return this.ctx.request({
3195
+ method: "PATCH",
3196
+ path: apiPath`/v1/drafts/${id}`,
3197
+ body
3198
+ });
3199
+ }
3200
+ /**
3201
+ * Delete the Draft and its stored media (bodyless, 204).
3202
+ * `DELETE /v1/drafts/{id}`
3203
+ *
3204
+ * @example
3205
+ * await curviate.drafts.delete(id);
3206
+ */
3207
+ async delete(id) {
3208
+ await this.ctx.request({
3209
+ method: "DELETE",
3210
+ path: apiPath`/v1/drafts/${id}`
3211
+ });
3212
+ }
3213
+ /**
3214
+ * Publish the Draft now, under the same rules and safety limits as
3215
+ * `posts.create`. `POST /v1/drafts/{id}/publish`
3216
+ *
3217
+ * Success deletes the Draft and its media, writes a publish record and
3218
+ * returns `{ object: "post_created", id }`; no webhook is sent. A known
3219
+ * failure leaves the Draft unchanged. A `504` or `502` whose message says
3220
+ * the outcome is unknown means the post may be live: check the account's
3221
+ * posts before retrying.
3222
+ *
3223
+ * @example
3224
+ * const { id: postId } = await curviate.drafts.publish(draftId);
3225
+ */
3226
+ publish(id) {
3227
+ return this.ctx.request({
3228
+ method: "POST",
3229
+ path: apiPath`/v1/drafts/${id}/publish`
3230
+ });
3231
+ }
3232
+ /**
3233
+ * Add one file to the Draft as the raw request body, for media too large to
3234
+ * send inline: an MP4 video or a PDF, each up to 50 MiB. An image is capped at 5 MiB on
3235
+ * every route. One file per call.
3236
+ * `POST /v1/drafts/{id}/attachments?filename=...`
3237
+ *
3238
+ * Small files (up to 5 MiB) can instead ride inline in `create` / `update`
3239
+ * as base64. Resolves with the Draft, the new attachment last.
3240
+ *
3241
+ * @example
3242
+ * const bytes = await readFile("clip.mp4");
3243
+ * const draft = await curviate.drafts.uploadAttachment(id, bytes, {
3244
+ * filename: "clip.mp4",
3245
+ * contentType: "video/mp4",
3246
+ * });
3247
+ */
3248
+ uploadAttachment(id, data, options) {
3249
+ const contentType = options.contentType ?? (data instanceof Blob ? data.type : "");
3250
+ return this.ctx.request({
3251
+ method: "POST",
3252
+ path: apiPath`/v1/drafts/${id}/attachments`,
3253
+ query: { filename: options.filename },
3254
+ rawBody: { data, contentType }
3255
+ });
3256
+ }
3257
+ };
3258
+
3037
3259
  // src/resources/index.ts
3038
3260
  function buildNamespaces(ctx) {
3039
3261
  return {
@@ -3054,14 +3276,16 @@ function buildNamespaces(ctx) {
3054
3276
  recruiter: new RecruiterResource(ctx),
3055
3277
  jobs: new JobsResource(ctx),
3056
3278
  companies: new CompaniesResource(ctx),
3057
- webhooks: new WebhooksResource(ctx)
3279
+ webhooks: new WebhooksResource(ctx),
3280
+ drafts: new DraftsResource(ctx)
3058
3281
  };
3059
3282
  }
3060
3283
  function buildRootNamespaces(ctx) {
3061
3284
  return {
3062
3285
  accounts: new AccountsResource(ctx),
3063
3286
  auth: new AuthResource(ctx),
3064
- webhooks: new WebhooksResource(ctx)
3287
+ webhooks: new WebhooksResource(ctx),
3288
+ drafts: new DraftsResource(ctx)
3065
3289
  };
3066
3290
  }
3067
3291
  function buildAccountScopedNamespaces(ctx) {
@@ -3069,11 +3293,13 @@ function buildAccountScopedNamespaces(ctx) {
3069
3293
  accounts: _accounts,
3070
3294
  auth: _auth,
3071
3295
  webhooks: _webhooks,
3296
+ drafts: _drafts,
3072
3297
  ...rest
3073
3298
  } = buildNamespaces(ctx);
3074
3299
  void _accounts;
3075
3300
  void _auth;
3076
3301
  void _webhooks;
3302
+ void _drafts;
3077
3303
  return rest;
3078
3304
  }
3079
3305
 
@@ -3086,6 +3312,7 @@ var Curviate = class {
3086
3312
  this.accounts = ns.accounts;
3087
3313
  this.auth = ns.auth;
3088
3314
  this.webhooks = ns.webhooks;
3315
+ this.drafts = ns.drafts;
3089
3316
  }
3090
3317
  /**
3091
3318
  * Return an account-scoped accessor that fixes `account_id` on every call,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curviate/sdk",
3
- "version": "0.40.1",
3
+ "version": "0.42.0",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for the Curviate API.",
6
6
  "license": "MIT",