Policies
Create, bind, and test access policies. For the conceptual model, see Policies and Permissions.
Base path: /v1/policies/
Create a policy
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"]
}
]
}'
|
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,
},
},
],
},
)
|
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:
| {
"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:
| 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"
}'
|
| 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"},
)
|
| 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
| curl -H "Authorization: Bearer $TOKEN" \
"https://scaivault.scailabs.ai/v1/policies?limit=50"
|
Response:
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
| curl -H "Authorization: Bearer $TOKEN" \
https://scaivault.scailabs.ai/v1/policies/pol_xyz789
|
Returns the full policy including rules and bindings.
Update a policy
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
| 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.
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:
| {
"allowed": true,
"matching_policies": [
{
"id": "pol_xyz789",
"name": "production-read-only",
"matching_rule_index": 0
}
]
}
|
When denied, the response tells you why:
| {
"allowed": false,
"reason": "no matching rule",
"evaluated_policies": ["pol_default", "pol_developer_read"]
}
|
or
| {
"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.
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
| curl -H "Authorization: Bearer $TOKEN" \
https://scaivault.scailabs.ai/v1/policies/pol_xyz789/bindings
|
Remove a binding
| 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:
| {
"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