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

Policies

Create, bind, and test access policies. For the conceptual model, see Policies and Permissions.

Base path: /v1/policies/

Create a policy#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
curl -X POST https://scaivault.scailabs.ai/v1/policies \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-read-only",
    "description": "Developers can read production secrets from VPN with MFA",
    "rules": [
      {
        "path_pattern": "environments/production/**",
        "permissions": ["read", "list"],
        "conditions": {
          "ip_ranges": ["10.0.0.0/8"],
          "require_mfa": true
        }
      },
      {
        "path_pattern": "shared/certificates/*",
        "permissions": ["read"]
      }
    ]
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
resp = httpx.post(
    "https://scaivault.scailabs.ai/v1/policies",
    headers={"Authorization": f"Bearer {os.environ['SCAIVAULT_TOKEN']}"},
    json={
        "name": "production-read-only",
        "rules": [
            {
                "path_pattern": "environments/production/**",
                "permissions": ["read", "list"],
                "conditions": {
                    "ip_ranges": ["10.0.0.0/8"],
                    "require_mfa": True,
                },
            },
        ],
    },
)
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const resp = await fetch("https://scaivault.scailabs.ai/v1/policies", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAIVAULT_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "production-read-only",
    rules: [
      {
        path_pattern: "environments/production/**",
        permissions: ["read", "list"],
        conditions: { ip_ranges: ["10.0.0.0/8"], require_mfa: true },
      },
    ],
  }),
});

Response:

json
1
2
3
4
5
6
7
8
{
  "id": "pol_xyz789",
  "name": "production-read-only",
  "description": "...",
  "rules": [...],
  "is_active": true,
  "created_at": "2026-04-23T14:00:00Z"
}

Rule fields#

Field Type Required Description
path_pattern string Yes Glob pattern (app/**, secrets/*.key)
permissions array Yes One or more of read, write, delete, list, rotate, admin
conditions.ip_ranges array No CIDR ranges the caller IP must be in
conditions.require_mfa boolean No Token must have fresh MFA
conditions.time_window.start string No HH:MM UTC, inclusive
conditions.time_window.end string No HH:MM UTC, exclusive

Bind an identity#

A policy without bindings has no effect. Bind it to a group, service account, or user:

bash
1
2
3
4
5
6
7
curl -X POST https://scaivault.scailabs.ai/v1/policies/pol_xyz789/bindings \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "group",
    "identity_id": "group:developers"
  }'
python
1
2
3
4
5
httpx.post(
    f"https://scaivault.scailabs.ai/v1/policies/pol_xyz789/bindings",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={"identity_type": "group", "identity_id": "group:developers"},
)
typescript
1
2
3
4
5
await fetch(`https://scaivault.scailabs.ai/v1/policies/pol_xyz789/bindings`, {
  method: "POST",
  headers: { "Authorization": `Bearer ${TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ identity_type: "group", identity_id: "group:developers" }),
});

Binding fields#

Field Values
identity_type user, service_account, group
identity_id ScaiKey identifier (user:alice@acme.example, sa:reporting-service, group:developers)
expires_at (optional) Auto-expire the binding at this ISO timestamp

List policies#

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

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "policies": [
    {
      "id": "pol_xyz789",
      "name": "production-read-only",
      "rule_count": 2,
      "binding_count": 1,
      "is_active": true
    }
  ],
  "cursor": null,
  "has_more": false
}

Get a policy#

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

Returns the full policy including rules and bindings.

Update a policy#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
curl -X PUT https://scaivault.scailabs.ai/v1/policies/pol_xyz789 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "production-read-only",
    "rules": [
      {
        "path_pattern": "environments/production/**",
        "permissions": ["read", "list"],
        "conditions": {"require_mfa": true}
      }
    ]
  }'

PUT replaces the whole policy body. Rules and metadata not included are dropped. Bindings are preserved.

Delete a policy#

bash
1
2
curl -X DELETE https://scaivault.scailabs.ai/v1/policies/pol_xyz789 \
  -H "Authorization: Bearer $TOKEN"

If the policy has active bindings, you get 409 policy_in_use. Add ?force=true to remove bindings and delete.

Test access#

Dry-run access checks — useful before deploying a policy change, or from CI.

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curl -X POST https://scaivault.scailabs.ai/v1/policies/test \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "identity_id": "user:alice@acme.example",
    "path": "environments/production/salesforce/api-credentials",
    "permission": "read",
    "context": {
      "source_ip": "10.0.1.50",
      "mfa_verified": true
    }
  }'

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "allowed": true,
  "matching_policies": [
    {
      "id": "pol_xyz789",
      "name": "production-read-only",
      "matching_rule_index": 0
    }
  ]
}

When denied, the response tells you why:

json
1
2
3
4
5
{
  "allowed": false,
  "reason": "no matching rule",
  "evaluated_policies": ["pol_default", "pol_developer_read"]
}

or

json
1
2
3
4
5
6
{
  "allowed": false,
  "reason": "condition_failed",
  "failed_condition": "require_mfa",
  "matching_rule": {"policy_id": "pol_xyz789", "rule_index": 0}
}

CI pattern#

Assert that critical accesses stay allowed after policy edits.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
assertions = [
    ("sa:reporting-service", "integrations/salesforce/oauth", "read", True),
    ("sa:reporting-service", "infra/db/primary/root", "read", False),
    ("user:alice@acme.example", "environments/production/**", "read", True),
]

for identity, path, permission, expected in assertions:
    resp = httpx.post(
        "https://scaivault.scailabs.ai/v1/policies/test",
        headers={"Authorization": f"Bearer {CI_TOKEN}"},
        json={"identity_id": identity, "path": path, "permission": permission},
    )
    result = resp.json()
    assert result["allowed"] == expected, f"{identity} {permission} on {path}: expected {expected}, got {result}"

Run this in CI to catch access regressions before they merge.

List bindings for a policy#

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

Remove a binding#

bash
1
2
curl -X DELETE https://scaivault.scailabs.ai/v1/policies/pol_xyz789/bindings/bind_abc \
  -H "Authorization: Bearer $TOKEN"

Partner-scoped policies#

Partner admins can create policies that apply to partner-scoped paths:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "name": "partner-shared-read",
  "scope": "partner",
  "rules": [
    {
      "path_pattern": "shared/**",
      "permissions": ["read"]
    }
  ]
}

Bindings on partner-scoped policies bind to partner identities (e.g., partner-admin).

What's next#

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