Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Rotation

Static credentials age into liabilities. ScaiVault has first-class rotation: define a policy once, attach it to a set of secrets, and ScaiVault drives the schedule, keeps an old version readable during cutover, and emits events so your systems can respond.

The model#

A rotation policy is a schedule plus behavior:

  • Interval. How often (90d, 7d, 24h).
  • Grace period. How long the previous version stays readable after rotation (48h by default).
  • Warn-before. When to emit rotation.due events (["7d", "1d"]).
  • Auto-generate. Whether ScaiVault generates the new value itself (random string) or just fires an event and waits for you to PUT a new value.
  • Webhook IDs. Where to route rotation events.

Attach a rotation policy to one or more secrets. The schedule runs independently per secret, based on its last-rotated timestamp.

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "id": "rot_quarterly",
  "name": "quarterly",
  "description": "90-day rotation with 48h grace",
  "interval": "90d",
  "interval_seconds": 7776000,
  "grace_period": "48h",
  "warn_before": "7d,1d",
  "auto_generate": false,
  "webhook_ids": ["wh_rotation_alerts"],
  "secrets_count": 42,
  "is_active": true
}

Lifecycle of a rotation#

Take a secret with a 90-day policy, currently at version 3, last rotated on day 0.

sequenceDiagram participant SV as ScaiVault participant Auto as Your Automation participant Src as Source System participant Consumers Note over SV: Day 0 — v3 written Note over SV: Day 83 (T-7d) SV->>Auto: rotation.due (threshold=7d) Note over SV: Day 89 (T-1d) SV->>Auto: rotation.due (threshold=1d) Note over SV: Day 90 — rotation fires SV->>Auto: rotation.due (due_now=true) Auto->>Src: mint new credential Src-->>Auto: new value Auto->>SV: PUT new value (v4) SV->>Consumers: secret.rotated (old=3, new=4) Note over SV: v3 still readable (grace) Note over SV: Day 92 — grace ends Note over SV: v4 is the only current version
  • Day 83 (7 days before rotation). Webhook fires with rotation.due, warn_before: "7d". Your automation can do anything — prepare the new value, tell a human, open a ticket.
  • Day 89 (1 day before). Second rotation.due webhook, warn_before: "1d".
  • Day 90. Rotation fires:
    • If auto_generate: true, ScaiVault writes a new version (v4) with a random value.
    • If auto_generate: false, ScaiVault emits rotation.due with due_now: true and waits. Your automation must PUT /v1/secrets/{path} with the new value. If it doesn't within the grace period, rotation.overdue fires.
  • Day 90.5. secret.rotated webhook fires with old_version: 3, new_version: 4. Consumers can pick up the new value.
  • Day 92 (48h grace period ends). Version 3 is no longer reachable; v4 is the only readable version. Rotation complete.

Auto-generate vs event-driven#

auto_generate: true works for secrets whose value is just a random string — database passwords where you also control the database, internal service tokens. ScaiVault generates a cryptographically random value (configurable length and alphabet via a linked secret policy) and writes it as the new version. You then need to propagate the value to the downstream system.

auto_generate: false works when the value comes from somewhere else — an external OAuth provider, a third-party API key you request through a portal, a human picking a value. ScaiVault fires the event; you do the rest. This is the common case for third-party credentials.

The pattern is:

  1. ScaiVault emits rotation.due.
  2. Your automation generates (or obtains) the new credential from the source system.
  3. Your automation writes the new version: PUT /v1/secrets/{path}.
  4. ScaiVault records the rotation and emits secret.rotated.

Grace periods#

The grace period keeps the old version readable after rotation. Without grace, a consumer that just loaded v3 and is mid-request might fail with "this credential is invalid" because the underlying resource has already moved to v4.

gantt title Grace window during rotation dateFormat X axisFormat %s section v3 Active (default current) :0, 90 Readable explicitly (grace) :90, 92 section v4 Active (default current) :90, 130

During grace, a read with no explicit version returns v4. Reads with ?version=3 return v3. After grace, ?version=3 returns the value but consumers treating it as current should use the latest — the audit log will flag reads of stale versions.

Set grace long enough to cover your longest-running job. 24h is usually safe for typical web services; batch pipelines may want 72h or more.

Secrets due for rotation#

Dashboards and automation can ask "what's due soon?":

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     "https://scaivault.scailabs.ai/v1/rotation/due?within_hours=168"
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "secrets": [
    {
      "path": "environments/production/salesforce/oauth",
      "policy_id": "rot_quarterly",
      "policy_name": "quarterly",
      "next_rotation_at": "2026-04-30T00:00:00Z",
      "is_overdue": false
    },
    {
      "path": "environments/staging/db-password",
      "policy_id": "rot_monthly",
      "policy_name": "monthly",
      "next_rotation_at": "2026-04-20T00:00:00Z",
      "is_overdue": true
    }
  ]
}

Use this in CI or a scheduled job to surface overdue rotations.

Triggering an immediate rotation#

Sometimes you need to rotate now — a credential leaked, a new hire left, a third party asked.

bash
1
2
3
4
5
6
7
8
curl -X POST https://scaivault.scailabs.ai/v1/secrets/app/db/password/rotate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Suspected compromise",
    "new_value": {"password": "new-random-value"},
    "grace_period": "1h"
  }'

grace_period: "1h" overrides the default for this one rotation — you probably don't want 48 hours of grace if you're responding to a compromise. For a policy-triggered emergency rotation across many secrets, POST /v1/rotation/policies/{id}/rotate with optional secret_paths filters.

Rotation history#

Every rotation (successful or failed) is recorded:

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/history
json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "data": [
    {
      "id": "rh_abc",
      "policy_id": "rot_quarterly",
      "secret_path": "environments/production/salesforce/oauth",
      "status": "success",
      "old_version": 3,
      "new_version": 4,
      "rotated_at": "2026-04-23T00:00:00Z",
      "rotated_by": "system:rotation-scheduler"
    },
    {
      "id": "rh_def",
      "policy_id": "rot_quarterly",
      "secret_path": "environments/staging/api-token",
      "status": "failed",
      "error_message": "auto_generate disabled, no new value provided within grace period",
      "rotated_at": "2026-04-23T00:00:00Z"
    }
  ],
  "has_more": false
}

Failed rotations are retried on a backoff schedule. After the configured retry limit, they stop and require manual intervention.

Enabling and disabling#

POST /v1/rotation/policies/{id}/disable pauses the policy. Attached secrets stop rotating but are not detached. Re-enable with POST /v1/rotation/policies/{id}/enable. Useful during incidents or infrastructure migrations.

Secret policies (value generation)#

A secret policy (different from an access policy) controls how ScaiVault generates random values when auto_generate: true. Specify length, character classes, and constraints:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "id": "sp_db_passwords",
  "name": "db-passwords",
  "length": 32,
  "charset": "alphanumeric+symbols",
  "require_upper": true,
  "require_lower": true,
  "require_digit": true,
  "require_symbol": true
}

Attach a secret policy to a rotation policy or directly to a secret. POST /v1/secret-policies to create.

What's next#

Updated 2026-05-17 14:30:19 View source (.md) rev 5