---
title: "Read the tenant safety defaults"
method: GET
path: "/v1/safety-policy"
tags: ["Safety"]
---

# Read the tenant safety defaults

`GET /v1/safety-policy`

## Request

- Base URL: `https://api.curviate.com`
- URL: `https://api.curviate.com/v1/safety-policy`
- Auth: HTTP bearer

Returns the tenant-wide safety defaults per limit profile and budget row, that accounts resolve from unless they override. Works with no accounts connected. Fields, posture and precedence: /reference/safety.

## Response `200`

The tenant safety defaults, every limit profile and every budget row.

- object
  - `object` 'tenant_safety_policy': Response type discriminator.
  - `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.
  - `profiles` object[]: One entry per limit profile, always all of them.
    - `limit_profile` 'basic' | 'premium' | 'sales_navigator' | 'recruiter': The limit profile these defaults apply to. An account resolves from its own profile's defaults.
    - `rows` object[]: Every budget row, always: the tenant default where one is set, the Curviate default otherwise.
      - `budget_row` string: The counted-action row this policy governs.
      - `ceiling` integer, nullable: What `enforce` refuses at and `warn` reports at, in the row's own unit. Null means the row is uncalibrated: Curviate has no figure for it.
      - `green` integer, nullable: Routine band.
      - `amber` integer, nullable: Warn-louder band.
      - `window_kind` 'day' | 'week' | 'month': The calendar grain this row is counted in.
      - `window_span` integer: How many grain units one counting window spans.
      - `window_timezone` string, nullable: IANA zone name the counting window is aligned to. Null counts in UTC.
      - `activity_window_start` string: Local HH:MM the activity window opens.
      - `activity_window_end` string: Local HH:MM the activity window closes.
      - `activity_window_timezone` string, nullable: IANA zone name the activity window is read in. Null disables the window entirely.
      - `activity_window_applies_to` 'writes' | 'all': 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.
      - `warm_up_factor` number, nullable: The tenant default multiplier for this row, or null when none is set. Null means each account follows its own derived warm-up ramp; 1 pins every account on this profile out of it.
      - `character_cap` integer, nullable: Length cap on the row's free-text field, in characters.
      - `subject_character_cap` integer, nullable: Length cap on the row's subject line, in characters.
      - `per_query_cap` integer, nullable: Bound on a single query's result lines. Search rows only.
      - `source_class` 'substrate' | 'linkedin_official' | 'practitioner' | 'inferred' | 'unseeded': How well calibrated this row's number is. `unseeded` means Curviate has no green, amber or ceiling figure for this row on this profile: the row is reported so nothing is silently missing, but nothing has calibrated it, so no value you set can be `over_default`.
      - `interface_ceiling` integer, nullable: What a call through the matching ELEVATED interface is held to on this row: the `/v1/{account_id}/sales-navigator/...` and `/v1/{account_id}/recruiter/...` operations. Null when this row and limit profile have no such figure, which is every row on `basic` and `premium` and most rows everywhere. Everything else on this row (`ceiling`, `effective_ceiling`, `band` and `over_default`) is the STANDARD-interface figure, because one counter serves both interfaces and the standard side is the conservative one to band against. The consequence is worth reading: on a `sales_navigator` or `recruiter` account a row can report `band: "over"` with `posture: "enforce"` while a call through the elevated interface still succeeds; the refusal the band predicts is the one a standard call gets.
      - `over_default` object[]: Empty when nothing on this row is above default. Computed against the STANDARD-interface default, like `band` and for the same reason.
        - `field` 'ceiling' | 'green' | 'amber' | 'character_cap' | 'subject_character_cap' | 'per_query_cap': The field whose configured value is above default.
        - `value` integer: The configured value.
        - `default` integer: The Curviate default it exceeds.
        - `source_class` 'substrate' | 'linkedin_official' | 'practitioner' | 'inferred' | 'unseeded': How well calibrated the DEFAULT is. `substrate` and `linkedin_official` are published figures; `practitioner` is corroborated operator experience; `inferred` is our own reasoning; `unseeded` means we have no figure at all for that row, in which case there is nothing to be above and no entry appears. Read this before deciding whether being above default matters.
  - `safety_warning` SafetyWarning: Present on a successful response when this account crossed an account-safety ceiling while the affected budget row is on the default warn posture. The action still happened. Field-identical to the BUDGET_EXHAUSTED error body except that blocked is false and hint.message describes the outcome, so one branch handles both postures and switching the account to enforce is a configuration change rather than a client rewrite.
    - `row` string, nullable, required: The budget row that breached, for example profile_views or pending_invites. Null when the breach is not about a row: an activity_window warning on an action that spends no budget of its own (editing a message, dismissing a notification, a Recruiter or Sales Navigator admin action) is still governed by the hours the account works in. Read hint.parameter for what to change; whenever this is null it names posture, because the activity window edges are set per action type and this action has none, so the levers that reach it are the account timezone and posture.
    - `reset_at` string, nullable, required: The instant the refusal lifts: the window roll on a ceiling refusal, the next window open on an activity-window one. Null in the two cases no clock frees, the pending_invites gauge and InMail credits (row inmail).
    - `hint` object, required
      - `parameter` string, required: The settable parameter on PATCH /v1/{account_id}/safety-policy.
      - `message` string, required: What the condition is and what changing that parameter would do. This is the one field whose text differs from the BUDGET_EXHAUSTED refusal: here it says the action went through, and on a ceiling breach how far past the ceiling this row now stands, rather than telling you to set posture to warn when it already is. Branch on parameter, reason and blocked, never on this text.
    - `reason` 'ceiling' | 'activity_window', required: Which rule was crossed. ceiling means the row's configured limit is spent; activity_window means the account is outside the hours it works in, which is a different fact with a different remedy. hint.parameter names the right setting for each.
    - `blocked` boolean, required: Always false here. True on the BUDGET_EXHAUSTED refusal carrying the same payload.

## Other responses

- `429`: Rate limited, slow down and retry after the hinted delay.

---

[API](https://skmtc.dev/curviate/apis/curviate-api.md) · [All operations](https://skmtc.dev/curviate/apis/curviate-api/llms.txt) · [OpenAPI document](https://skmtc.dev/curviate/apis/curviate-api/revisions/eab848801d95?raw)
