Safety

Update an account's safety policy

Updates this account's safety policy. Send only the fields you're changing; null clears an override back to the tenant or Curviate default. Any value is accepted, none are clamped. Fields, posture and precedence: /reference/safety.

patch/v1/{account_id}/safety-policy

Request

  • Base URL: https://api.curviate.com
  • URL: https://api.curviate.com/v1/{account_id}/safety-policy
  • Auth: HTTP bearer

Path parameters

account_idstring required

The account ID (acc_...) whose safety policy to update.

Request body

limit_profile'basic' | 'premium' | 'sales_navigator' | 'recruiter'

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 pins it, and the pin wins over detection from then on. None of those paths moves a pinned value, so it holds until you release it. To release the pin, send limit_profile_source: "default" on its own: Curviate re-reads the value from LinkedIn on the account's next read or connect. Changing limit_profile and releasing it in the same request is refused with INVALID_REQUEST, because those are opposite intents. 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 Sales Navigator and 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.

timezonestring nullable

The IANA zone this account's ACTIVITY window is read in, e.g. Europe/Berlin. Must be a real zone; anything else is a 400. Null clears it, which DISABLES the activity window for every row that has not set its own: a guessed zone inverts the window, which is worse than not having one.

tenant_default_posture'warn' | 'enforce' nullable

The tenant-wide posture default, which every account inherits unless it overrides it. Set it once for a whole tenant of personas. Null clears it back to warn.

clear_haltsstring[]

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.

posture'warn' | 'enforce' nullable

This account's posture default, overriding the tenant default for this account only. Null clears the override.

objectunknown
account_idunknown
limit_profile_source'default' | 'detected' | 'operator'

Where limit_profile came from: default (never observed), detected (read from LinkedIn) or operator (pinned through this operation, and nothing but a release moves it). Sending default releases a pin: the value is re-read from LinkedIn on the account's next read or connect. Send it on its own; changing limit_profile in the same request is refused. Sending detected or operator is accepted and ignored, so the whole document round-trips.

limit_profile_detected_atunknown
posture_sourceunknown
warm_upunknown
activity_windowunknown

Response

The account's full safety policy: every configurable field for every budget row, in one response. Any row whose configured value is above the Curviate default carries an over_default entry naming the value, the default and the source class of that default. Two top-level fields are DERIVED rather than configured: warm_up says whether Curviate is currently holding this account below its configured ceilings and by how much, and activity_window says which timezone the window is read in and whether it is switched on at all. This is the same body the read returns, so a value set above default is reported on the write that set it.

object'safety_policy'

Response type discriminator.

account_idstring

The account this policy governs.

limit_profile'basic' | 'premium' | 'sales_navigator' | 'recruiter'

Which set of Curviate defaults every row below is resolved from. Each profile inherits the one before it and raises a few rows: premium adds a daily invitation band and open-profile InMail, sales_navigator raises profile views and search, recruiter raises profile views again. This is the platform product active on the account, not the Curviate seat entitlement. The two can disagree, and this is the one that governs the numbers. 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.

limit_profile_source'default' | 'detected' | 'operator'

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 you release it by sending limit_profile_source: "default" on this operation, on its own (changing limit_profile and releasing it in the same request is refused). Nothing else overrides it: a connect, a reconnect and an account read all leave a pinned value exactly as you set 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.

limit_profile_detected_atstring date-time nullable

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. While limit_profile_source is operator neither the value nor this timestamp moves, until you release the pin.

posture'warn' | 'enforce'

The account's default posture, before any per-row override.

posture_source'account' | 'tenant' | 'default'

Whether that default is the account's own, the tenant's, or the built-in warn.

tenant_default_posture'warn' | 'enforce' | 'null' nullable

The workspace-wide posture default every account in the tenant inherits, or null when none is set. Always present, and separate from posture on purpose: when this account overrides the tenant default, a change to the tenant default does not move posture, and this field is how you can still see it took effect.

Example response

{
  "safety_warning": {
    "row": "profile_views",
    "reset_at": "2026-09-05T00:00:00.000Z",
    "hint": {
      "parameter": "profile_views.ceiling",
      "message": "This account is at its ceiling for profile_views. This action went through because profile_views.posture is warn, and it counted: 101 of 100 this window. Raise profile_views.ceiling, or wait for the window to reset at 2026-09-05T00:00:00.000Z."
    },
    "reason": "ceiling",
    "blocked": false
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.