@curviate/sdk 0.27.0 → 0.29.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 CHANGED
@@ -7,6 +7,93 @@ Versioning: semantic. Minor for additive changes, patch for bug fixes; no stabil
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.29.0] - 2026-09-07
11
+
12
+ Fixture and types regenerated against the deployed staging document
13
+ (`https://api.staging.curviate.com`, `server_git_sha 1447ffd4...`, 125 paths).
14
+ Additive only: `safety-policy` now says where `limit_profile` came from, and an
15
+ operator override can be released without a reconnect.
16
+
17
+ ### Added
18
+
19
+ - **`GET /v1/{account_id}/safety-policy` gains `limit_profile_source` and
20
+ `limit_profile_detected_at`.** `limit_profile_source` is `"default"`,
21
+ `"detected"` or `"operator"`: `default` means the value has never been
22
+ observed and is the seeded `basic`, so it says nothing about the account;
23
+ `detected` means Curviate read it from LinkedIn at `limit_profile_detected_at`,
24
+ whether or not the value changed; `operator` means it was set through the
25
+ PATCH. `limit_profile_detected_at` is an ISO date-time, or `null` until the
26
+ value has been read at least once. Both fields appear on the PATCH response
27
+ too.
28
+
29
+ - **`PATCH /v1/{account_id}/safety-policy` accepts `limit_profile_source`.**
30
+ Sending `"default"` is a real write: it releases an operator override, and the
31
+ value is re-read from LinkedIn on the account's next account read or connect.
32
+ Sending `"detected"` or `"operator"` is accepted and ignored, so a document
33
+ read from the GET round-trips unchanged. `limit_profile_detected_at` is
34
+ read-only and likewise accepted and ignored on a write.
35
+
36
+ ### Changed
37
+
38
+ - Description-only: the PATCH `limit_profile` description now states that
39
+ Curviate re-reads the profile at most once a day on an account read (not only
40
+ on connect and reconnect), and that the override is released by sending
41
+ `limit_profile_source: "default"` rather than by writing `basic` back.
42
+
43
+ ### Notes
44
+
45
+ - The server also removed `POST /v1/billing/seats/:seat_id/tier` in the same
46
+ wave. `/v1/billing/*` has never been part of the served OpenAPI document, so
47
+ there is no SDK surface change from it: the path count is unchanged at 125 and
48
+ the generated types are untouched by that removal.
49
+ - `safety-policy` has no hand-written resource method in this SDK; it is reached
50
+ through the generated `operations` types, which is where these two fields
51
+ land.
52
+
53
+ ---
54
+
55
+ ## [0.28.0] - 2026-09-07
56
+
57
+ Fixture regenerated against the deployed staging document
58
+ (`https://api.staging.curviate.com`, `server_git_sha 14fa8234...`, 125 paths).
59
+ **BREAKING**: `messaging.searchChats` now rejects a `query` shorter than 3
60
+ characters server-side. Pre-1.0 a breaking change ships as a minor, so a caret
61
+ range on `0.27.x` will not pick this up.
62
+
63
+ ### BREAKING
64
+
65
+ - **`searchChats` requires a `query` of at least 3 characters.** A 1-2 character
66
+ term previously returned `200`; it now returns `400 INVALID_REQUEST` with a
67
+ message naming the minimum. The search is served from an index built out of
68
+ overlapping 3-character sequences, so a shorter term cannot use it at all and
69
+ falls back to a scan: measured at 238-292 ms against 4.5 ms for the same term
70
+ one character longer. Returning `200` after 60x the work, with no way for a
71
+ caller to learn why, was the worse contract.
72
+
73
+ The bound is now expressed in the OpenAPI schema (`minLength: 3`) and in the
74
+ generated parameter description. **TypeScript cannot express a string minimum
75
+ length**, so the generated type is still `string`: the constraint is
76
+ documented and enforced at the wire, not by the compiler. Debounce a
77
+ type-ahead to 3 characters rather than relying on types to catch it.
78
+
79
+ `companies.searchChats` is a different endpoint and is unaffected; its
80
+ `query` stays optional and unbounded.
81
+
82
+ ### Added
83
+
84
+ - **`accounts.retrieve` gains `event_log`**: `{ retained, row_cap, at_row_cap }`.
85
+ Reports where the account stands against the retained event log's per-account
86
+ row budget. While `at_row_cap` is true, events are still delivered to any
87
+ registered webhook but are no longer retained, so the events resource has
88
+ stopped being a complete record of what arrived. Additive and always present.
89
+
90
+ - Every string parameter and body field on the surface now publishes its real
91
+ length bounds (`minLength` / `maxLength`) where the server enforces one. This
92
+ was previously emitted only for numbers, so string constraints existed in
93
+ descriptions and nowhere a generated client could read them.
94
+
95
+ ---
96
+
10
97
  ## [0.27.0] - 2026-09-06
11
98
 
12
99
  Fixture regenerated against the deployed production document
package/dist/index.d.ts CHANGED
@@ -2689,7 +2689,7 @@ interface paths {
2689
2689
  head?: never;
2690
2690
  /**
2691
2691
  * Update an account's safety policy
2692
- * @description The single safety-policy write. Send only the fields you are changing: everything you omit is left byte-identical, and sending `null` for a field clears your override and restores the Curviate default. Any limit may be set to any value: Curviate does not clamp, cap or refuse a change on the grounds that the number is unsafe. A value above the Curviate default is reported back in the response, and on every later read, with the default and its source class attached; there is no way to acknowledge it away, and setting the value back to the default removes it. A value that is not a value, such as a negative ceiling or an unknown row name, is a 400. Every field whose value CHANGES is appended to the account's action ledger with the previous value, the new value, the Curviate default, the actor and the time; a field you send whose value already equals its current effective value is not ledgered and is not stored as an override, so reading the policy and sending the document back changes nothing. Six fields on the read are derived rather than configured: `effective_ceiling`, `interface_ceiling`, `effective_interface_ceiling`, `posture_source`, `over_default` and each row's `warm_up_state`. Send them and they are accepted and ignored, never stored and never ledgered, so the document you read can go straight back. To pin a row's ramp, set `warm_up_factor`. `activity_window` is not accepted: send its settable subfields instead, the top-level `timezone` for the zone, and per row `activity_window_start`, `activity_window_end`, `activity_window_timezone` and `activity_window_applies_to`. The 400 this produces names those subfields, not just the refused object. And read `changes nothing` as `changes no limit`: the `posture` fields are OVERRIDES at every scope, so writing back a posture a scope merely inherits PINS it there and appends one ledger entry per pinned scope. That is what stops a later account- or tenant-wide change from moving it. A pin already in place is a no-op. An agent-authenticated write is accepted on exactly the same footing as an operator's. There is no bulk import, file upload or policy template: configuring many rows or many accounts is a loop over this operation. One field is not account-scoped: `tenant_default_posture` sets the default for EVERY account in the tenant, not just the one in the path, which is what makes it a one-call change for a whole workspace of personas. It is reported back at the top of every policy response, so a change to it is visible even on an account that overrides it. One other field is not row-scoped: `limit_profile` selects which of the four Curviate default sets this account's rows resolve from, so it moves the default under every row at once while leaving every value you have configured explicitly exactly as you set it. It is the platform product active on the account, not your Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it when the account connects and re-detects it on every reconnect; setting it here is an operator override of that result, and it holds until the next connect. When the seat and the detected product disagree, the detected product wins the numbers and the read reports the disagreement as `seat_tier_mismatch`.
2692
+ * @description The single safety-policy write. Send only the fields you are changing: everything you omit is left byte-identical, and sending `null` for a field clears your override and restores the Curviate default. Any limit may be set to any value: Curviate does not clamp, cap or refuse a change on the grounds that the number is unsafe. A value above the Curviate default is reported back in the response, and on every later read, with the default and its source class attached; there is no way to acknowledge it away, and setting the value back to the default removes it. A value that is not a value, such as a negative ceiling or an unknown row name, is a 400. Every field whose value CHANGES is appended to the account's action ledger with the previous value, the new value, the Curviate default, the actor and the time; a field you send whose value already equals its current effective value is not ledgered and is not stored as an override, so reading the policy and sending the document back changes nothing. Six fields on the read are derived rather than configured: `effective_ceiling`, `interface_ceiling`, `effective_interface_ceiling`, `posture_source`, `over_default` and each row's `warm_up_state`. Send them and they are accepted and ignored, never stored and never ledgered, so the document you read can go straight back. To pin a row's ramp, set `warm_up_factor`. `activity_window` is not accepted: send its settable subfields instead, the top-level `timezone` for the zone, and per row `activity_window_start`, `activity_window_end`, `activity_window_timezone` and `activity_window_applies_to`. The 400 this produces names those subfields, not just the refused object. And read `changes nothing` as `changes no limit`: the `posture` fields are OVERRIDES at every scope, so writing back a posture a scope merely inherits PINS it there and appends one ledger entry per pinned scope. That is what stops a later account- or tenant-wide change from moving it. A pin already in place is a no-op. An agent-authenticated write is accepted on exactly the same footing as an operator's. There is no bulk import, file upload or policy template: configuring many rows or many accounts is a loop over this operation. One field is not account-scoped: `tenant_default_posture` sets the default for EVERY account in the tenant, not just the one in the path, which is what makes it a one-call change for a whole workspace of personas. It is reported back at the top of every policy response, so a change to it is visible even on an account that overrides it. One other field is not row-scoped: `limit_profile` selects which of the four Curviate default sets this account's rows resolve from, so it moves the default under every row at once while leaving every value you have configured explicitly exactly as you set it. It is the platform product active on the account, not your Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it when the account connects and re-detects it on every reconnect; setting it here is an operator override of that result, and it holds until the next connect. When the seat and the detected product disagree, the detected product wins the numbers and the read reports the disagreement as `seat_tier_mismatch`. One field on this operation does not configure anything: `clear_halts` lifts the platform pause on the action types you name. When the platform refuses a call on an account, Curviate pauses that action type for the platform's own retry-after or a seeded cooldown, and every later call on it is refused locally without reaching the platform; the account resource reports the live pauses as `quotas[].halt`. Naming an action type here lifts its pause immediately, so the next call goes out. An action type with no active pause is accepted and does nothing, which makes it safe to send for a set you have not checked, and only the ones actually lifted are ledgered, each with the expiry it cut short, the actor and the time, readable afterwards on `GET /v1/{account_id}/safety-events` with `reason: "halt_cleared"`. Clearing disarms nothing: if the platform refuses the account again the pause returns, and a 429 or a refused write returns it for the full cooldown. Clear one when you have reason to believe the refusal was a transient upstream fault rather than the platform pushing back on this account.
2693
2693
  */
2694
2694
  patch: operations["patchV1AccountIdSafetyPolicy"];
2695
2695
  trace?: never;
@@ -2703,7 +2703,7 @@ interface paths {
2703
2703
  };
2704
2704
  /**
2705
2705
  * List what Curviate refused or warned about
2706
- * @description Returns this account's safety events, newest first: every action Curviate refused, and every action it let through while flagging that a limit had been crossed. Under the default posture nothing is refused, so everything here is a warning and `blocked` is how you tell the two apart. Each event names the budget row that breached, why, the operation that hit it and when. Filter by `budget_row` for one action type and by `since` / `until` for a period, and page until `cursor` is null. This is a read of what already happened, so it never changes a limit or a count: to change what is refused, update the safety policy. The account resource is the other half of the picture, reporting where each row stands right now rather than what has already been flagged.
2706
+ * @description Returns this account's safety events, newest first: every action Curviate refused, and every action it let through while flagging that a limit had been crossed. Under the default posture nothing is refused, so everything here is a warning and `blocked` is how you tell the two apart. Each event names the budget row that breached, why, the operation that hit it and when. Filter by `budget_row` for one action type and by `since` / `until` for a period, and page until `cursor` is null. This is a read of what already happened, so it never changes a limit or a count: to change what is refused, update the safety policy. The account resource is the other half of the picture, reporting where each row stands right now rather than what has already been flagged. One entry here is not a refusal: `reason: "halt_cleared"` with `blocked: false` records somebody lifting a platform pause early through `clear_halts` on the safety policy, and it sits beside the `rate_limited` entry it lifted so the pause and its end read as one thread.
2707
2707
  */
2708
2708
  get: operations["getV1AccountIdSafetyEvents"];
2709
2709
  put?: never;
@@ -12096,7 +12096,7 @@ interface operations {
12096
12096
  getV1AccountIdChatsSearch: {
12097
12097
  parameters: {
12098
12098
  query: {
12099
- /** @description The search term: matches both participant names and message content (e.g. 'sophie keller'). Terms of 3 or more characters are fast; a 1-2 character term is slower. */
12099
+ /** @description The search term: matches both participant names and message content (e.g. 'sophie keller'). Minimum 3 characters. A shorter term cannot use the search index and is rejected with INVALID_REQUEST. */
12100
12100
  query: string;
12101
12101
  /** @description Maximum chats to return per page (1-100, default 20). The server may walk a few internal pages to fill one page. */
12102
12102
  limit?: number;
@@ -27899,7 +27899,7 @@ interface operations {
27899
27899
  };
27900
27900
  requestBody?: never;
27901
27901
  responses: {
27902
- /** @description The account's metadata and current state, including the quotas[] budget view (one entry per counted action row for this account, for the current window of that row's grain) account_states[], the platform conditions the account is currently in, and activity_window, the hours in which live writes are reported or refused. */
27902
+ /** @description The account's metadata and current state, including the quotas[] budget view (one entry per counted action row for this account, for the current window of that row's grain) account_states[], the platform conditions the account is currently in, activity_window, the hours in which live writes are reported or refused, and event_log, where the account stands against the retained event log's row budget. */
27903
27903
  200: {
27904
27904
  headers: {
27905
27905
  "RateLimit-Policy": components["headers"]["RateLimit-Policy"];
@@ -27972,7 +27972,7 @@ interface operations {
27972
27972
  posture: "warn" | "enforce";
27973
27973
  /** @description Whether something on this row is configured above Curviate's own default. The safety policy carries the detail: which field, the value, the default and how well calibrated that default is. */
27974
27974
  over_default: boolean;
27975
- /** @description An active pause on this row because the platform refused a recent request on it, or `null`. A halt is not a ceiling: no limit you raise lifts it, and it clears itself when it expires. */
27975
+ /** @description An active pause on this row because the platform refused a recent request on it, or `null`. A halt is not a ceiling: no limit you raise lifts it, and it clears itself when it expires. To lift one before it expires, send this row in `clear_halts` on `PATCH /v1/{account_id}/safety-policy`; that is recorded, and the pause returns if the platform refuses the account again. */
27976
27976
  halt: {
27977
27977
  /** @description What the platform did: `rate_limited`, `platform_fault`, or `resend_cooldown` (the same invitation was sent too recently). */
27978
27978
  reason?: string;
@@ -27999,6 +27999,15 @@ interface operations {
27999
27999
  /** @description True when no zone is set, which DISABLES the activity window. Curviate never guesses a zone: a guess eight hours wrong inverts the window, refusing every daytime write and permitting every nocturnal one. If you configured a window and it never fires, this is why. */
28000
28000
  timezone_unset?: boolean;
28001
28001
  };
28002
+ /** @description Where this account stands against the retained event log's row budget. Events are retained for 24 hours so you can read what the platform sent without running a listener; an account may hold at most row_cap of them at once. While at_row_cap is true, further events for this account are still delivered to any webhook you registered but are NOT retained, so the events resource stops being a complete record of what arrived. The budget is a backstop against a misbehaving upstream, sized so ordinary use never approaches it. */
28003
+ event_log?: {
28004
+ /** @description Events currently held for this account. A level, not a total: retention is 24 hours, so this falls on its own as events age out. */
28005
+ retained?: number;
28006
+ /** @description The maximum this account may hold at once. */
28007
+ row_cap?: number;
28008
+ /** @description True while the cap is biting and incoming events are not being retained. Delivery to a registered webhook is unaffected. */
28009
+ at_row_cap?: boolean;
28010
+ };
28002
28011
  safety_warning?: components["schemas"]["SafetyWarning"];
28003
28012
  };
28004
28013
  };
@@ -30440,6 +30449,16 @@ interface operations {
30440
30449
  * @enum {string|null}
30441
30450
  */
30442
30451
  seat_tier_mismatch?: "core" | "sn" | "recruiter" | null;
30452
+ /**
30453
+ * @description Where `limit_profile` came from. `detected`: Curviate read it from LinkedIn, at `limit_profile_detected_at`. `operator`: it was set through this operation and holds until the account next connects, or until you send `limit_profile_source: "default"` on this operation, which releases it. `default`: it has never been observed, so the value is the seeded `basic` and says nothing about the account; Curviate detects it on the account's next read or connect.
30454
+ * @enum {string}
30455
+ */
30456
+ limit_profile_source?: "default" | "detected" | "operator";
30457
+ /**
30458
+ * Format: date-time
30459
+ * @description When Curviate last read `limit_profile` from LinkedIn, whether or not the value changed. Null until it has. Curviate re-reads it on connect and, at most once a day, on an account read, so a subscription change reaches the numbers without a reconnect.
30460
+ */
30461
+ limit_profile_detected_at?: string | null;
30443
30462
  /**
30444
30463
  * @description The account's default posture, before any per-row override.
30445
30464
  * @enum {string}
@@ -30514,7 +30533,7 @@ interface operations {
30514
30533
  /** @description IANA zone name the activity window is read in. Null disables the window entirely. */
30515
30534
  activity_window_timezone?: string | null;
30516
30535
  /**
30517
- * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either.
30536
+ * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either. One action set to `all` reaches the whole account's live reads that spend no budget of their own (notifications, profile analytics, SSI, profile visitors, the invitation and saved-list listings): those calls debit no action type, so they have none to read this setting from and take the account-wide answer instead. Reads that DO debit an action type read that action type's own setting.
30518
30537
  * @enum {string}
30519
30538
  */
30520
30539
  activity_window_applies_to?: "writes" | "all";
@@ -30601,7 +30620,7 @@ interface operations {
30601
30620
  content: {
30602
30621
  "application/json": {
30603
30622
  /**
30604
- * @description Which set of Curviate defaults this account's rows resolve from: `basic`, `premium`, `sales_navigator` or `recruiter`. Each inherits the one before it and raises a few rows. This is the platform product active on the account, NOT the Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it from the platform when the account connects and re-detects it on every reconnect; setting it here is an operator override of that result and holds until the next connect. Set it back to `basic` to undo. The change is recorded in the ledger and it moves the defaults under every row at once, so any row you have configured explicitly keeps your value. The elevated Sales Navigator and Recruiter figures apply only to calls made through the matching interface, the `/v1/{account_id}/sales-navigator/...` and `/v1/{account_id}/recruiter/...` operations, and Curviate holds each call to the figure for the interface it goes through. The numbers here are therefore the ones a standard-interface call is held to, on every profile; a call through one of those operations resolves from the elevated set instead, and each row publishes that figure as `interface_ceiling`.
30623
+ * @description Which set of Curviate defaults this account's rows resolve from: `basic`, `premium`, `sales_navigator` or `recruiter`. Each inherits the one before it and raises a few rows. This is the platform product active on the account, NOT the Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it from the platform when the account connects, re-detects it on every reconnect and, at most once a day, on an account read; setting it here is an operator override of that result and holds until the next connect. To undo the override, send `limit_profile_source: "default"`: the pin is released, and Curviate re-reads the value from LinkedIn on the account's next read or connect. Sending the value the account already holds changes nothing and pins nothing. The change is recorded in the ledger and it moves the defaults under every row at once, so any row you have configured explicitly keeps your value. The elevated Sales Navigator and Recruiter figures apply only to calls made through the matching interface, the `/v1/{account_id}/sales-navigator/...` and `/v1/{account_id}/recruiter/...` operations, and Curviate holds each call to the figure for the interface it goes through. The numbers here are therefore the ones a standard-interface call is held to, on every profile; a call through one of those operations resolves from the elevated set instead, and each row publishes that figure as `interface_ceiling`.
30605
30624
  * @enum {string}
30606
30625
  */
30607
30626
  limit_profile?: "basic" | "premium" | "sales_navigator" | "recruiter";
@@ -30612,6 +30631,8 @@ interface operations {
30612
30631
  * @enum {string|null}
30613
30632
  */
30614
30633
  tenant_default_posture?: "warn" | "enforce" | null;
30634
+ /** @description Lift the platform pause on these action types now, instead of waiting for it to expire. When the platform refuses a call on an account (a rate limit, a server error on a budgeted route, an invitation resend cooldown), Curviate pauses that action type for the platform's own retry-after or a seeded cooldown, and every later call on it is refused locally without reaching the platform. Naming an action type here clears that pause: the next call goes out. Read `quotas[].halt` on the account to see which action types are paused and for how long. A type with no active pause is accepted and does nothing, so this is safe to send for a set you have not checked. Clearing is recorded in the account's ledger with the expiry it cut short, who cleared it and when, and it appears on the safety-events surface. It does not disarm anything: if the platform refuses the account again, the pause comes straight back, longer than the one you cleared where the platform says so. Clear a pause when you have reason to believe the refusal was a transient upstream fault rather than the platform pushing back on this account; clearing one and calling straight back into a real rate limit is itself how accounts get restricted. */
30635
+ clear_halts?: ("connection_requests_with_note" | "connection_requests_no_note" | "messages_first_degree" | "inmail" | "open_profile_inmail" | "profile_views" | "search" | "comments" | "post_likes" | "follows" | "content_poll" | "job_poll" | "posts" | "endorsements" | "invite_responses" | "withdrawal_sweep")[];
30615
30636
  /**
30616
30637
  * @description This account's posture default, overriding the tenant default for this account only. Null clears the override.
30617
30638
  * @enum {string|null}
@@ -30651,7 +30672,7 @@ interface operations {
30651
30672
  /** @description IANA zone name the activity window is read in, e.g. `Europe/Berlin`. Must be a real zone; anything else is a 400. Null DISABLES the activity window: a guessed zone inverts it, which is worse than not having one. */
30652
30673
  activity_window_timezone?: string | null;
30653
30674
  /**
30654
- * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads. A read Curviate answers from its own store never reaches LinkedIn and so is never covered; the same read IS covered when the store cannot answer it, or when you ask for it live. Stored reads and inbound events are never subject to the window under either setting, at any hour.
30675
+ * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads. A read Curviate answers from its own store never reaches LinkedIn and so is never covered; the same read IS covered when the store cannot answer it, or when you ask for it live. Stored reads and inbound events are never subject to the window under either setting, at any hour. One action set to `all` reaches the whole account's live reads that spend no budget of their own (notifications, profile analytics, SSI, profile visitors, the invitation and saved-list listings): those calls debit no action type, so they have none to read this setting from and take the account-wide answer instead. Reads that DO debit an action type read that action type's own setting.
30655
30676
  * @enum {string|null}
30656
30677
  */
30657
30678
  activity_window_applies_to?: "writes" | "all" | null;
@@ -30690,6 +30711,13 @@ interface operations {
30690
30711
  account_id?: unknown;
30691
30712
  /** @description READ-ONLY. The Curviate seat tier that disagrees with `limit_profile`. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
30692
30713
  seat_tier_mismatch?: unknown;
30714
+ /**
30715
+ * @description Where `limit_profile` came from: `default` (never observed), `detected` (read from LinkedIn) or `operator` (set through this operation). Sending `default` releases an operator override: the value is re-read from LinkedIn on the account's next read or connect. Sending `detected` or `operator` is accepted and ignored, so the whole document round-trips.
30716
+ * @enum {string}
30717
+ */
30718
+ limit_profile_source?: "default" | "detected" | "operator";
30719
+ /** @description READ-ONLY. When `limit_profile` was last read from LinkedIn, or null if it never has been. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
30720
+ limit_profile_detected_at?: unknown;
30693
30721
  /** @description READ-ONLY. Which scope supplied this account's effective `posture`. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
30694
30722
  posture_source?: unknown;
30695
30723
  /** @description READ-ONLY. The account's DERIVED warm-up ramp, recomputed on every read. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
@@ -30726,6 +30754,16 @@ interface operations {
30726
30754
  * @enum {string|null}
30727
30755
  */
30728
30756
  seat_tier_mismatch?: "core" | "sn" | "recruiter" | null;
30757
+ /**
30758
+ * @description Where `limit_profile` came from. `detected`: Curviate read it from LinkedIn, at `limit_profile_detected_at`. `operator`: it was set through this operation and holds until the account next connects, or until you send `limit_profile_source: "default"` on this operation, which releases it. `default`: it has never been observed, so the value is the seeded `basic` and says nothing about the account; Curviate detects it on the account's next read or connect.
30759
+ * @enum {string}
30760
+ */
30761
+ limit_profile_source?: "default" | "detected" | "operator";
30762
+ /**
30763
+ * Format: date-time
30764
+ * @description When Curviate last read `limit_profile` from LinkedIn, whether or not the value changed. Null until it has. Curviate re-reads it on connect and, at most once a day, on an account read, so a subscription change reaches the numbers without a reconnect.
30765
+ */
30766
+ limit_profile_detected_at?: string | null;
30729
30767
  /**
30730
30768
  * @description The account's default posture, before any per-row override.
30731
30769
  * @enum {string}
@@ -30800,7 +30838,7 @@ interface operations {
30800
30838
  /** @description IANA zone name the activity window is read in. Null disables the window entirely. */
30801
30839
  activity_window_timezone?: string | null;
30802
30840
  /**
30803
- * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either.
30841
+ * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either. One action set to `all` reaches the whole account's live reads that spend no budget of their own (notifications, profile analytics, SSI, profile visitors, the invitation and saved-list listings): those calls debit no action type, so they have none to read this setting from and take the account-wide answer instead. Reads that DO debit an action type read that action type's own setting.
30804
30842
  * @enum {string}
30805
30843
  */
30806
30844
  activity_window_applies_to?: "writes" | "all";
@@ -30943,11 +30981,11 @@ interface operations {
30943
30981
  */
30944
30982
  budget_row: "connection_requests_with_note" | "connection_requests_no_note" | "messages_first_degree" | "inmail" | "open_profile_inmail" | "profile_views" | "search" | "comments" | "post_likes" | "follows" | "content_poll" | "job_poll" | "posts" | "endorsements" | "invite_responses" | "withdrawal_sweep" | "total_actions" | "pending_invites" | "*";
30945
30983
  /**
30946
- * @description Which rule flagged it, and each one calls for a different response. `budget_exhausted`: the account reached the ceiling configured for that row, which you can raise on the safety policy. `activity_window`: the account was acting outside its configured activity window, so no ceiling was the constraint. Wait for the window to open, move `activity_window_start` or `activity_window_end`, or check `activity_window_timezone` is the zone you meant. `rate_limited`: the platform itself refused this account on that row recently and Curviate is holding the row until the pause expires, which no configuration change lifts.
30984
+ * @description Which rule flagged it, and each one calls for a different response. `budget_exhausted`: the account reached the ceiling configured for that row, which you can raise on the safety policy. `activity_window`: the account was acting outside its configured activity window, so no ceiling was the constraint. Wait for the window to open, move `activity_window_start` or `activity_window_end`, or check `activity_window_timezone` is the zone you meant. `rate_limited`: the platform itself refused this account on that row recently and Curviate is holding the row until the pause expires. No ceiling you raise lifts it; the one thing that does is `clear_halts` on the safety policy, which lifts it deliberately and records that it was lifted. `halt_cleared` is the odd one out and the only entry that is not a refusal: somebody lifted one of those platform pauses early through `clear_halts` on the safety policy, so the row was open again from that instant. It carries `blocked: false` and its `method` is `update_safety_policy` rather than the operation that breached. Read it beside the `rate_limited` entry it lifts: the pause and its lifting are one thread, and an entry list that showed only the first would report a row as still paused.
30947
30985
  * @enum {string}
30948
30986
  */
30949
- reason: "budget_exhausted" | "activity_window" | "rate_limited";
30950
- /** @description Whether the action was stopped. `false` is a warning: the action went through, and this record is the only lasting trace that it crossed a limit. Under the default `warn` posture every event is a warning, which is what makes reading this surface the point of running it. */
30987
+ reason: "budget_exhausted" | "activity_window" | "rate_limited" | "halt_cleared";
30988
+ /** @description Whether the action was stopped. `false` is a warning: the action went through, and this record is the only lasting trace that it crossed a limit. Under the default `warn` posture every event is a warning, which is what makes reading this surface the point of running it. A `halt_cleared` entry is also `false`, for a different reason: it stopped nothing, it lifted a stop. */
30951
30989
  blocked: boolean;
30952
30990
  /** @description The operation the caller was running when it breached. */
30953
30991
  method: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curviate/sdk",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "private": false,
5
5
  "description": "TypeScript SDK for the Curviate API.",
6
6
  "license": "MIT",
@@ -2591,7 +2591,7 @@ export interface paths {
2591
2591
  head?: never;
2592
2592
  /**
2593
2593
  * Update an account's safety policy
2594
- * @description The single safety-policy write. Send only the fields you are changing: everything you omit is left byte-identical, and sending `null` for a field clears your override and restores the Curviate default. Any limit may be set to any value: Curviate does not clamp, cap or refuse a change on the grounds that the number is unsafe. A value above the Curviate default is reported back in the response, and on every later read, with the default and its source class attached; there is no way to acknowledge it away, and setting the value back to the default removes it. A value that is not a value, such as a negative ceiling or an unknown row name, is a 400. Every field whose value CHANGES is appended to the account's action ledger with the previous value, the new value, the Curviate default, the actor and the time; a field you send whose value already equals its current effective value is not ledgered and is not stored as an override, so reading the policy and sending the document back changes nothing. Six fields on the read are derived rather than configured: `effective_ceiling`, `interface_ceiling`, `effective_interface_ceiling`, `posture_source`, `over_default` and each row's `warm_up_state`. Send them and they are accepted and ignored, never stored and never ledgered, so the document you read can go straight back. To pin a row's ramp, set `warm_up_factor`. `activity_window` is not accepted: send its settable subfields instead, the top-level `timezone` for the zone, and per row `activity_window_start`, `activity_window_end`, `activity_window_timezone` and `activity_window_applies_to`. The 400 this produces names those subfields, not just the refused object. And read `changes nothing` as `changes no limit`: the `posture` fields are OVERRIDES at every scope, so writing back a posture a scope merely inherits PINS it there and appends one ledger entry per pinned scope. That is what stops a later account- or tenant-wide change from moving it. A pin already in place is a no-op. An agent-authenticated write is accepted on exactly the same footing as an operator's. There is no bulk import, file upload or policy template: configuring many rows or many accounts is a loop over this operation. One field is not account-scoped: `tenant_default_posture` sets the default for EVERY account in the tenant, not just the one in the path, which is what makes it a one-call change for a whole workspace of personas. It is reported back at the top of every policy response, so a change to it is visible even on an account that overrides it. One other field is not row-scoped: `limit_profile` selects which of the four Curviate default sets this account's rows resolve from, so it moves the default under every row at once while leaving every value you have configured explicitly exactly as you set it. It is the platform product active on the account, not your Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it when the account connects and re-detects it on every reconnect; setting it here is an operator override of that result, and it holds until the next connect. When the seat and the detected product disagree, the detected product wins the numbers and the read reports the disagreement as `seat_tier_mismatch`.
2594
+ * @description The single safety-policy write. Send only the fields you are changing: everything you omit is left byte-identical, and sending `null` for a field clears your override and restores the Curviate default. Any limit may be set to any value: Curviate does not clamp, cap or refuse a change on the grounds that the number is unsafe. A value above the Curviate default is reported back in the response, and on every later read, with the default and its source class attached; there is no way to acknowledge it away, and setting the value back to the default removes it. A value that is not a value, such as a negative ceiling or an unknown row name, is a 400. Every field whose value CHANGES is appended to the account's action ledger with the previous value, the new value, the Curviate default, the actor and the time; a field you send whose value already equals its current effective value is not ledgered and is not stored as an override, so reading the policy and sending the document back changes nothing. Six fields on the read are derived rather than configured: `effective_ceiling`, `interface_ceiling`, `effective_interface_ceiling`, `posture_source`, `over_default` and each row's `warm_up_state`. Send them and they are accepted and ignored, never stored and never ledgered, so the document you read can go straight back. To pin a row's ramp, set `warm_up_factor`. `activity_window` is not accepted: send its settable subfields instead, the top-level `timezone` for the zone, and per row `activity_window_start`, `activity_window_end`, `activity_window_timezone` and `activity_window_applies_to`. The 400 this produces names those subfields, not just the refused object. And read `changes nothing` as `changes no limit`: the `posture` fields are OVERRIDES at every scope, so writing back a posture a scope merely inherits PINS it there and appends one ledger entry per pinned scope. That is what stops a later account- or tenant-wide change from moving it. A pin already in place is a no-op. An agent-authenticated write is accepted on exactly the same footing as an operator's. There is no bulk import, file upload or policy template: configuring many rows or many accounts is a loop over this operation. One field is not account-scoped: `tenant_default_posture` sets the default for EVERY account in the tenant, not just the one in the path, which is what makes it a one-call change for a whole workspace of personas. It is reported back at the top of every policy response, so a change to it is visible even on an account that overrides it. One other field is not row-scoped: `limit_profile` selects which of the four Curviate default sets this account's rows resolve from, so it moves the default under every row at once while leaving every value you have configured explicitly exactly as you set it. It is the platform product active on the account, not your Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it when the account connects and re-detects it on every reconnect; setting it here is an operator override of that result, and it holds until the next connect. When the seat and the detected product disagree, the detected product wins the numbers and the read reports the disagreement as `seat_tier_mismatch`. One field on this operation does not configure anything: `clear_halts` lifts the platform pause on the action types you name. When the platform refuses a call on an account, Curviate pauses that action type for the platform's own retry-after or a seeded cooldown, and every later call on it is refused locally without reaching the platform; the account resource reports the live pauses as `quotas[].halt`. Naming an action type here lifts its pause immediately, so the next call goes out. An action type with no active pause is accepted and does nothing, which makes it safe to send for a set you have not checked, and only the ones actually lifted are ledgered, each with the expiry it cut short, the actor and the time, readable afterwards on `GET /v1/{account_id}/safety-events` with `reason: "halt_cleared"`. Clearing disarms nothing: if the platform refuses the account again the pause returns, and a 429 or a refused write returns it for the full cooldown. Clear one when you have reason to believe the refusal was a transient upstream fault rather than the platform pushing back on this account.
2595
2595
  */
2596
2596
  patch: operations["patchV1AccountIdSafetyPolicy"];
2597
2597
  trace?: never;
@@ -2605,7 +2605,7 @@ export interface paths {
2605
2605
  };
2606
2606
  /**
2607
2607
  * List what Curviate refused or warned about
2608
- * @description Returns this account's safety events, newest first: every action Curviate refused, and every action it let through while flagging that a limit had been crossed. Under the default posture nothing is refused, so everything here is a warning and `blocked` is how you tell the two apart. Each event names the budget row that breached, why, the operation that hit it and when. Filter by `budget_row` for one action type and by `since` / `until` for a period, and page until `cursor` is null. This is a read of what already happened, so it never changes a limit or a count: to change what is refused, update the safety policy. The account resource is the other half of the picture, reporting where each row stands right now rather than what has already been flagged.
2608
+ * @description Returns this account's safety events, newest first: every action Curviate refused, and every action it let through while flagging that a limit had been crossed. Under the default posture nothing is refused, so everything here is a warning and `blocked` is how you tell the two apart. Each event names the budget row that breached, why, the operation that hit it and when. Filter by `budget_row` for one action type and by `since` / `until` for a period, and page until `cursor` is null. This is a read of what already happened, so it never changes a limit or a count: to change what is refused, update the safety policy. The account resource is the other half of the picture, reporting where each row stands right now rather than what has already been flagged. One entry here is not a refusal: `reason: "halt_cleared"` with `blocked: false` records somebody lifting a platform pause early through `clear_halts` on the safety policy, and it sits beside the `rate_limited` entry it lifted so the pause and its end read as one thread.
2609
2609
  */
2610
2610
  get: operations["getV1AccountIdSafetyEvents"];
2611
2611
  put?: never;
@@ -12000,7 +12000,7 @@ export interface operations {
12000
12000
  getV1AccountIdChatsSearch: {
12001
12001
  parameters: {
12002
12002
  query: {
12003
- /** @description The search term: matches both participant names and message content (e.g. 'sophie keller'). Terms of 3 or more characters are fast; a 1-2 character term is slower. */
12003
+ /** @description The search term: matches both participant names and message content (e.g. 'sophie keller'). Minimum 3 characters. A shorter term cannot use the search index and is rejected with INVALID_REQUEST. */
12004
12004
  query: string;
12005
12005
  /** @description Maximum chats to return per page (1-100, default 20). The server may walk a few internal pages to fill one page. */
12006
12006
  limit?: number;
@@ -27803,7 +27803,7 @@ export interface operations {
27803
27803
  };
27804
27804
  requestBody?: never;
27805
27805
  responses: {
27806
- /** @description The account's metadata and current state, including the quotas[] budget view (one entry per counted action row for this account, for the current window of that row's grain) account_states[], the platform conditions the account is currently in, and activity_window, the hours in which live writes are reported or refused. */
27806
+ /** @description The account's metadata and current state, including the quotas[] budget view (one entry per counted action row for this account, for the current window of that row's grain) account_states[], the platform conditions the account is currently in, activity_window, the hours in which live writes are reported or refused, and event_log, where the account stands against the retained event log's row budget. */
27807
27807
  200: {
27808
27808
  headers: {
27809
27809
  "RateLimit-Policy": components["headers"]["RateLimit-Policy"];
@@ -27876,7 +27876,7 @@ export interface operations {
27876
27876
  posture: "warn" | "enforce";
27877
27877
  /** @description Whether something on this row is configured above Curviate's own default. The safety policy carries the detail: which field, the value, the default and how well calibrated that default is. */
27878
27878
  over_default: boolean;
27879
- /** @description An active pause on this row because the platform refused a recent request on it, or `null`. A halt is not a ceiling: no limit you raise lifts it, and it clears itself when it expires. */
27879
+ /** @description An active pause on this row because the platform refused a recent request on it, or `null`. A halt is not a ceiling: no limit you raise lifts it, and it clears itself when it expires. To lift one before it expires, send this row in `clear_halts` on `PATCH /v1/{account_id}/safety-policy`; that is recorded, and the pause returns if the platform refuses the account again. */
27880
27880
  halt: {
27881
27881
  /** @description What the platform did: `rate_limited`, `platform_fault`, or `resend_cooldown` (the same invitation was sent too recently). */
27882
27882
  reason?: string;
@@ -27903,6 +27903,15 @@ export interface operations {
27903
27903
  /** @description True when no zone is set, which DISABLES the activity window. Curviate never guesses a zone: a guess eight hours wrong inverts the window, refusing every daytime write and permitting every nocturnal one. If you configured a window and it never fires, this is why. */
27904
27904
  timezone_unset?: boolean;
27905
27905
  };
27906
+ /** @description Where this account stands against the retained event log's row budget. Events are retained for 24 hours so you can read what the platform sent without running a listener; an account may hold at most row_cap of them at once. While at_row_cap is true, further events for this account are still delivered to any webhook you registered but are NOT retained, so the events resource stops being a complete record of what arrived. The budget is a backstop against a misbehaving upstream, sized so ordinary use never approaches it. */
27907
+ event_log?: {
27908
+ /** @description Events currently held for this account. A level, not a total: retention is 24 hours, so this falls on its own as events age out. */
27909
+ retained?: number;
27910
+ /** @description The maximum this account may hold at once. */
27911
+ row_cap?: number;
27912
+ /** @description True while the cap is biting and incoming events are not being retained. Delivery to a registered webhook is unaffected. */
27913
+ at_row_cap?: boolean;
27914
+ };
27906
27915
  safety_warning?: components["schemas"]["SafetyWarning"];
27907
27916
  };
27908
27917
  };
@@ -30344,6 +30353,16 @@ export interface operations {
30344
30353
  * @enum {string|null}
30345
30354
  */
30346
30355
  seat_tier_mismatch?: "core" | "sn" | "recruiter" | null;
30356
+ /**
30357
+ * @description Where `limit_profile` came from. `detected`: Curviate read it from LinkedIn, at `limit_profile_detected_at`. `operator`: it was set through this operation and holds until the account next connects, or until you send `limit_profile_source: "default"` on this operation, which releases it. `default`: it has never been observed, so the value is the seeded `basic` and says nothing about the account; Curviate detects it on the account's next read or connect.
30358
+ * @enum {string}
30359
+ */
30360
+ limit_profile_source?: "default" | "detected" | "operator";
30361
+ /**
30362
+ * Format: date-time
30363
+ * @description When Curviate last read `limit_profile` from LinkedIn, whether or not the value changed. Null until it has. Curviate re-reads it on connect and, at most once a day, on an account read, so a subscription change reaches the numbers without a reconnect.
30364
+ */
30365
+ limit_profile_detected_at?: string | null;
30347
30366
  /**
30348
30367
  * @description The account's default posture, before any per-row override.
30349
30368
  * @enum {string}
@@ -30418,7 +30437,7 @@ export interface operations {
30418
30437
  /** @description IANA zone name the activity window is read in. Null disables the window entirely. */
30419
30438
  activity_window_timezone?: string | null;
30420
30439
  /**
30421
- * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either.
30440
+ * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either. One action set to `all` reaches the whole account's live reads that spend no budget of their own (notifications, profile analytics, SSI, profile visitors, the invitation and saved-list listings): those calls debit no action type, so they have none to read this setting from and take the account-wide answer instead. Reads that DO debit an action type read that action type's own setting.
30422
30441
  * @enum {string}
30423
30442
  */
30424
30443
  activity_window_applies_to?: "writes" | "all";
@@ -30505,7 +30524,7 @@ export interface operations {
30505
30524
  content: {
30506
30525
  "application/json": {
30507
30526
  /**
30508
- * @description Which set of Curviate defaults this account's rows resolve from: `basic`, `premium`, `sales_navigator` or `recruiter`. Each inherits the one before it and raises a few rows. This is the platform product active on the account, NOT the Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it from the platform when the account connects and re-detects it on every reconnect; setting it here is an operator override of that result and holds until the next connect. Set it back to `basic` to undo. The change is recorded in the ledger and it moves the defaults under every row at once, so any row you have configured explicitly keeps your value. The elevated Sales Navigator and Recruiter figures apply only to calls made through the matching interface, the `/v1/{account_id}/sales-navigator/...` and `/v1/{account_id}/recruiter/...` operations, and Curviate holds each call to the figure for the interface it goes through. The numbers here are therefore the ones a standard-interface call is held to, on every profile; a call through one of those operations resolves from the elevated set instead, and each row publishes that figure as `interface_ceiling`.
30527
+ * @description Which set of Curviate defaults this account's rows resolve from: `basic`, `premium`, `sales_navigator` or `recruiter`. Each inherits the one before it and raises a few rows. This is the platform product active on the account, NOT the Curviate seat entitlement, and the two can disagree: a lapsed subscription leaves the seat untouched. Curviate detects it from the platform when the account connects, re-detects it on every reconnect and, at most once a day, on an account read; setting it here is an operator override of that result and holds until the next connect. To undo the override, send `limit_profile_source: "default"`: the pin is released, and Curviate re-reads the value from LinkedIn on the account's next read or connect. Sending the value the account already holds changes nothing and pins nothing. The change is recorded in the ledger and it moves the defaults under every row at once, so any row you have configured explicitly keeps your value. The elevated Sales Navigator and Recruiter figures apply only to calls made through the matching interface, the `/v1/{account_id}/sales-navigator/...` and `/v1/{account_id}/recruiter/...` operations, and Curviate holds each call to the figure for the interface it goes through. The numbers here are therefore the ones a standard-interface call is held to, on every profile; a call through one of those operations resolves from the elevated set instead, and each row publishes that figure as `interface_ceiling`.
30509
30528
  * @enum {string}
30510
30529
  */
30511
30530
  limit_profile?: "basic" | "premium" | "sales_navigator" | "recruiter";
@@ -30516,6 +30535,8 @@ export interface operations {
30516
30535
  * @enum {string|null}
30517
30536
  */
30518
30537
  tenant_default_posture?: "warn" | "enforce" | null;
30538
+ /** @description Lift the platform pause on these action types now, instead of waiting for it to expire. When the platform refuses a call on an account (a rate limit, a server error on a budgeted route, an invitation resend cooldown), Curviate pauses that action type for the platform's own retry-after or a seeded cooldown, and every later call on it is refused locally without reaching the platform. Naming an action type here clears that pause: the next call goes out. Read `quotas[].halt` on the account to see which action types are paused and for how long. A type with no active pause is accepted and does nothing, so this is safe to send for a set you have not checked. Clearing is recorded in the account's ledger with the expiry it cut short, who cleared it and when, and it appears on the safety-events surface. It does not disarm anything: if the platform refuses the account again, the pause comes straight back, longer than the one you cleared where the platform says so. Clear a pause when you have reason to believe the refusal was a transient upstream fault rather than the platform pushing back on this account; clearing one and calling straight back into a real rate limit is itself how accounts get restricted. */
30539
+ clear_halts?: ("connection_requests_with_note" | "connection_requests_no_note" | "messages_first_degree" | "inmail" | "open_profile_inmail" | "profile_views" | "search" | "comments" | "post_likes" | "follows" | "content_poll" | "job_poll" | "posts" | "endorsements" | "invite_responses" | "withdrawal_sweep")[];
30519
30540
  /**
30520
30541
  * @description This account's posture default, overriding the tenant default for this account only. Null clears the override.
30521
30542
  * @enum {string|null}
@@ -30555,7 +30576,7 @@ export interface operations {
30555
30576
  /** @description IANA zone name the activity window is read in, e.g. `Europe/Berlin`. Must be a real zone; anything else is a 400. Null DISABLES the activity window: a guessed zone inverts it, which is worse than not having one. */
30556
30577
  activity_window_timezone?: string | null;
30557
30578
  /**
30558
- * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads. A read Curviate answers from its own store never reaches LinkedIn and so is never covered; the same read IS covered when the store cannot answer it, or when you ask for it live. Stored reads and inbound events are never subject to the window under either setting, at any hour.
30579
+ * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads. A read Curviate answers from its own store never reaches LinkedIn and so is never covered; the same read IS covered when the store cannot answer it, or when you ask for it live. Stored reads and inbound events are never subject to the window under either setting, at any hour. One action set to `all` reaches the whole account's live reads that spend no budget of their own (notifications, profile analytics, SSI, profile visitors, the invitation and saved-list listings): those calls debit no action type, so they have none to read this setting from and take the account-wide answer instead. Reads that DO debit an action type read that action type's own setting.
30559
30580
  * @enum {string|null}
30560
30581
  */
30561
30582
  activity_window_applies_to?: "writes" | "all" | null;
@@ -30594,6 +30615,13 @@ export interface operations {
30594
30615
  account_id?: unknown;
30595
30616
  /** @description READ-ONLY. The Curviate seat tier that disagrees with `limit_profile`. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
30596
30617
  seat_tier_mismatch?: unknown;
30618
+ /**
30619
+ * @description Where `limit_profile` came from: `default` (never observed), `detected` (read from LinkedIn) or `operator` (set through this operation). Sending `default` releases an operator override: the value is re-read from LinkedIn on the account's next read or connect. Sending `detected` or `operator` is accepted and ignored, so the whole document round-trips.
30620
+ * @enum {string}
30621
+ */
30622
+ limit_profile_source?: "default" | "detected" | "operator";
30623
+ /** @description READ-ONLY. When `limit_profile` was last read from LinkedIn, or null if it never has been. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
30624
+ limit_profile_detected_at?: unknown;
30597
30625
  /** @description READ-ONLY. Which scope supplied this account's effective `posture`. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
30598
30626
  posture_source?: unknown;
30599
30627
  /** @description READ-ONLY. The account's DERIVED warm-up ramp, recomputed on every read. Sent back on a write it is accepted and ignored, so the whole document round-trips; it is never stored and never ledgered. */
@@ -30630,6 +30658,16 @@ export interface operations {
30630
30658
  * @enum {string|null}
30631
30659
  */
30632
30660
  seat_tier_mismatch?: "core" | "sn" | "recruiter" | null;
30661
+ /**
30662
+ * @description Where `limit_profile` came from. `detected`: Curviate read it from LinkedIn, at `limit_profile_detected_at`. `operator`: it was set through this operation and holds until the account next connects, or until you send `limit_profile_source: "default"` on this operation, which releases it. `default`: it has never been observed, so the value is the seeded `basic` and says nothing about the account; Curviate detects it on the account's next read or connect.
30663
+ * @enum {string}
30664
+ */
30665
+ limit_profile_source?: "default" | "detected" | "operator";
30666
+ /**
30667
+ * Format: date-time
30668
+ * @description When Curviate last read `limit_profile` from LinkedIn, whether or not the value changed. Null until it has. Curviate re-reads it on connect and, at most once a day, on an account read, so a subscription change reaches the numbers without a reconnect.
30669
+ */
30670
+ limit_profile_detected_at?: string | null;
30633
30671
  /**
30634
30672
  * @description The account's default posture, before any per-row override.
30635
30673
  * @enum {string}
@@ -30704,7 +30742,7 @@ export interface operations {
30704
30742
  /** @description IANA zone name the activity window is read in. Null disables the window entirely. */
30705
30743
  activity_window_timezone?: string | null;
30706
30744
  /**
30707
- * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either.
30745
+ * @description What the activity window governs. `writes` (the default) covers live writes to LinkedIn; `all` widens it to live reads as well, which is the setting for a persona that should not be seen browsing at 03:00. It is broader than it sounds: `all` covers every live read, including the ones about the account itself, so a nightly analytics or invitation-backlog poll is refused at 03:00 too. A read Curviate answers from its own store never reaches LinkedIn, so it is never subject to the window under either setting, at any hour; the same read IS covered when the store cannot answer it, or when you ask for it live. Inbound events are never subject to it either. One action set to `all` reaches the whole account's live reads that spend no budget of their own (notifications, profile analytics, SSI, profile visitors, the invitation and saved-list listings): those calls debit no action type, so they have none to read this setting from and take the account-wide answer instead. Reads that DO debit an action type read that action type's own setting.
30708
30746
  * @enum {string}
30709
30747
  */
30710
30748
  activity_window_applies_to?: "writes" | "all";
@@ -30847,11 +30885,11 @@ export interface operations {
30847
30885
  */
30848
30886
  budget_row: "connection_requests_with_note" | "connection_requests_no_note" | "messages_first_degree" | "inmail" | "open_profile_inmail" | "profile_views" | "search" | "comments" | "post_likes" | "follows" | "content_poll" | "job_poll" | "posts" | "endorsements" | "invite_responses" | "withdrawal_sweep" | "total_actions" | "pending_invites" | "*";
30849
30887
  /**
30850
- * @description Which rule flagged it, and each one calls for a different response. `budget_exhausted`: the account reached the ceiling configured for that row, which you can raise on the safety policy. `activity_window`: the account was acting outside its configured activity window, so no ceiling was the constraint. Wait for the window to open, move `activity_window_start` or `activity_window_end`, or check `activity_window_timezone` is the zone you meant. `rate_limited`: the platform itself refused this account on that row recently and Curviate is holding the row until the pause expires, which no configuration change lifts.
30888
+ * @description Which rule flagged it, and each one calls for a different response. `budget_exhausted`: the account reached the ceiling configured for that row, which you can raise on the safety policy. `activity_window`: the account was acting outside its configured activity window, so no ceiling was the constraint. Wait for the window to open, move `activity_window_start` or `activity_window_end`, or check `activity_window_timezone` is the zone you meant. `rate_limited`: the platform itself refused this account on that row recently and Curviate is holding the row until the pause expires. No ceiling you raise lifts it; the one thing that does is `clear_halts` on the safety policy, which lifts it deliberately and records that it was lifted. `halt_cleared` is the odd one out and the only entry that is not a refusal: somebody lifted one of those platform pauses early through `clear_halts` on the safety policy, so the row was open again from that instant. It carries `blocked: false` and its `method` is `update_safety_policy` rather than the operation that breached. Read it beside the `rate_limited` entry it lifts: the pause and its lifting are one thread, and an entry list that showed only the first would report a row as still paused.
30851
30889
  * @enum {string}
30852
30890
  */
30853
- reason: "budget_exhausted" | "activity_window" | "rate_limited";
30854
- /** @description Whether the action was stopped. `false` is a warning: the action went through, and this record is the only lasting trace that it crossed a limit. Under the default `warn` posture every event is a warning, which is what makes reading this surface the point of running it. */
30891
+ reason: "budget_exhausted" | "activity_window" | "rate_limited" | "halt_cleared";
30892
+ /** @description Whether the action was stopped. `false` is a warning: the action went through, and this record is the only lasting trace that it crossed a limit. Under the default `warn` posture every event is a warning, which is what makes reading this surface the point of running it. A `halt_cleared` entry is also `false`, for a different reason: it stopped nothing, it lifted a stop. */
30855
30893
  blocked: boolean;
30856
30894
  /** @description The operation the caller was running when it breached. */
30857
30895
  method: string;