Sync and policies
proWhat team sync has pulled to this machine, how to apply it, and the local policy store behind the policy editor.
Every route here is Pro-gated inside the handler. Without a Pro licence they return
403 FEATURE_GATED - cloud_sync for the sync routes, policy_editor for the policies.
| Method | Path | Feature |
|---|---|---|
| GET | /api/sync/status | cloud_sync |
| GET | /api/sync/configs | cloud_sync |
| POST | /api/sync/apply | cloud_sync |
| GET | /api/policies | policy_editor |
| GET | /api/policies/{name} | policy_editor |
| POST | /api/policies | policy_editor |
| PUT | /api/policies/{name} | policy_editor |
| DELETE | /api/policies/{name} | policy_editor |
GET /api/sync/status sits in the open bucket, so it needs no dashboard token - but it
still refuses on Community. The other seven take the dashboard token, and the three
mutations take x-grith-csrf as well.
These routes describe what is already on disk. The network fetch is
grith pro sync, which pulls team policies, configs and provider keys and
pushes reputation. It uploads no audit records.
GET /api/sync/status
{
"last_synced": "2026-08-24T09:00:00Z",
"policies_count": 3,
"configs_count": 2,
"provider_keys_count": 1,
"has_learned_rules": true
}The counts are files on disk under the config directory, not a remote query. last_synced
is null before the first pull.
GET /api/sync/configs
{configs, total}, where each entry is {name, size_bytes} for one synced JSON file,
sorted by name.
POST /api/sync/apply
Merges every synced JSON config into team-config.toml in the config directory, in sorted
filename order, so the normal config precedence picks it up on the next daemon start.
Unreadable or unparseable files are skipped with a warning rather than failing the merge.
{ "status": "ok", "configs_applied": 2, "team_config_path": "/home/u/.config/grith/team-config.toml" }The route is POST /api/sync/apply. It takes no body.
Policies
A policy is a named JSON file in <config_dir>/policies/. It carries name,
description, version, created_at, updated_at and rules:
{
"name": "tight-prod",
"description": "Hardened for production sessions",
"version": 3,
"created_at": "2026-08-01T10:00:00+00:00",
"updated_at": "2026-08-24T09:00:00+00:00",
"rules": {
"proxy": { "auto_allow_threshold": 2.0, "auto_deny_threshold": 6.0 },
"filters": { "behavioural": false },
"allowlists": { "paths": [], "commands": [], "domains": [] }
}
}GET /api/policies returns {policies, total} sorted by name. POST is 201 with the
stored policy and starts version at 1; PUT bumps version and updated_at; DELETE
is 204.
Validation happens on create and update, and every failure is 400 VALIDATION_ERROR:
namemust be 1-64 characters, ASCII alphanumeric and hyphens only. Checked on create; on update the name comes from the path and an unknown one is404instead.- Both thresholds must be within
0.0-10.0, andauto_allow_thresholdmust be strictly belowauto_deny_threshold. - Every key in
filtersmust be a filter the running proxy actually registered. An unknown id is rejected by name.
A duplicate name on create is 409 ALREADY_EXISTS; an unknown name on read, update or
delete is 404 NOT_FOUND.