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 Policies

Define schedules for automated rotation, attach secrets, subscribe to events, trigger immediate rotations. For the conceptual model, see Rotation.

Base path: /v1/rotation/

Create a rotation policy#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "quarterly",
    "description": "90-day rotation for production credentials",
    "interval": "90d",
    "grace_period": "48h",
    "warn_before": "7d,1d",
    "auto_generate": false,
    "webhook_ids": ["wh_rotation_alerts"]
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
resp = httpx.post(
    "https://scaivault.scailabs.ai/v1/rotation/policies",
    headers={"Authorization": f"Bearer {os.environ['SCAIVAULT_TOKEN']}"},
    json={
        "name": "quarterly",
        "interval": "90d",
        "grace_period": "48h",
        "warn_before": "7d,1d",
        "auto_generate": False,
    },
)
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const resp = await fetch("https://scaivault.scailabs.ai/v1/rotation/policies", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAIVAULT_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "quarterly",
    interval: "90d",
    grace_period: "48h",
    auto_generate: false,
  }),
});

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "id": "rot_quarterly",
  "name": "quarterly",
  "interval": "90d",
  "interval_seconds": 7776000,
  "grace_period": "48h",
  "warn_before": "7d,1d",
  "auto_generate": false,
  "is_active": true,
  "secrets_count": 0,
  "created_at": "2026-04-23T14:00:00Z"
}

Fields#

Field Required Description
name Yes Unique within tenant
interval Yes How often (90d, 7d, 24h)
description No
grace_period No Default 48h. Previous version remains readable during this window
warn_before No Comma-separated durations before due date (7d,1d)
auto_generate No If true, ScaiVault writes a random new value. If false, event-driven — your automation writes the new version
secret_policy_id No Value-generation policy (for auto_generate: true)
webhook_ids No Webhooks that receive rotation events

Assign secrets to a policy#

Rotation policies don't do anything until secrets are attached.

bash
1
2
3
4
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/secrets \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secret_path": "environments/production/salesforce/oauth"}'

Also permitted: assign at write time by setting options.rotation_policy_id:

bash
1
2
3
4
5
6
7
curl -X PUT https://scaivault.scailabs.ai/v1/secrets/environments/production/salesforce/oauth \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {"client_id": "...", "client_secret": "..."},
    "options": {"rotation_policy_id": "rot_quarterly"}
  }'

Detach:

bash
1
2
curl -X DELETE https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/secrets/environments%2Fproduction%2Fsalesforce%2Foauth \
  -H "Authorization: Bearer $TOKEN"

(Path is URL-encoded — the / becomes %2F.)

List policies#

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     "https://scaivault.scailabs.ai/v1/rotation/policies?active_only=true"

Enable / disable#

Pause a policy without detaching secrets:

bash
1
2
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/disable \
  -H "Authorization: Bearer $TOKEN"

Resume:

bash
1
2
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/enable \
  -H "Authorization: Bearer $TOKEN"

Disabled policies don't rotate. Scheduled rotations that fire while disabled are skipped; they don't queue up.

Trigger immediate rotation#

For the whole policy:

bash
1
2
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/rotate \
  -H "Authorization: Bearer $TOKEN"

Or scoped to specific paths:

bash
1
2
3
4
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/rotate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secret_paths": ["environments/production/salesforce/oauth"]}'

Or per-secret (doesn't require a policy attached):

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

List rotation history#

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/history

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "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"
    }
  ],
  "has_more": false
}

Filter by status:

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     "https://scaivault.scailabs.ai/v1/rotation/policies/rot_quarterly/history?status=failed"

Find secrets due for rotation#

Use this in dashboards or scheduled jobs:

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     "https://scaivault.scailabs.ai/v1/rotation/due?within_hours=168&include_overdue=true"

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "secrets": [
    {
      "path": "environments/production/salesforce/oauth",
      "policy_id": "rot_quarterly",
      "next_rotation_at": "2026-04-30T00:00:00Z",
      "is_overdue": false
    },
    {
      "path": "environments/staging/db-password",
      "policy_id": "rot_monthly",
      "next_rotation_at": "2026-04-20T00:00:00Z",
      "is_overdue": true
    }
  ]
}

End-to-end: event-driven rotation for an OAuth credential#

1. Create the rotation policy#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "oauth-90d",
    "interval": "90d",
    "grace_period": "48h",
    "warn_before": "7d,1d",
    "auto_generate": false
  }'

2. Register a webhook#

bash
1
2
3
4
5
6
7
8
9
curl -X POST https://scaivault.scailabs.ai/v1/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "oauth-rotation-handler",
    "url": "https://ops.acme.example/scaivault/rotate-oauth",
    "secret": "whsec_...",
    "events": ["rotation.due"]
  }'

3. Attach the webhook to the policy#

bash
1
2
3
4
curl -X PATCH https://scaivault.scailabs.ai/v1/rotation/policies/rot_oauth-90d \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_ids": ["wh_oauth-rotation-handler"]}'

4. Attach the secret#

bash
1
2
3
4
curl -X POST https://scaivault.scailabs.ai/v1/rotation/policies/rot_oauth-90d/secrets \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"secret_path": "integrations/salesforce/oauth"}'

5. Handle the webhook#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from fastapi import FastAPI, Request
import httpx

app = FastAPI()

@app.post("/scaivault/rotate-oauth")
async def on_rotation(request: Request):
    event = await request.json()
    if event["event_type"] != "rotation.due":
        return {"ok": True}
    if not event["data"].get("due_now"):
        # This is a "7d before" or "1d before" warning — just log it
        return {"ok": True}

    path = event["path"]  # e.g. "integrations/salesforce/oauth"
    # 1. Obtain new OAuth credential from Salesforce admin portal, or via SF API
    new_creds = get_new_salesforce_credentials()
    # 2. Write it back
    httpx.put(
        f"https://scaivault.scailabs.ai/v1/secrets/{path}",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={"data": new_creds, "secret_type": "json"},
    )
    return {"ok": True}

That's the full loop — ScaiVault schedules, your automation generates, ScaiVault records.

Common error codes#

Code When
rotation_policy_not_found
secret_not_found Assigning a non-existent secret
name_conflict Policy name already exists
invalid_duration Interval or grace_period didn't parse

What's next#

Updated 2026-05-17 13:26:50 View source (.md) rev 2