grithdocs

Sync and policies

pro

What team sync has pulled to this machine, how to apply it, and the local policy store behind the policy editor.

Pro

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.

MethodPathFeature
GET/api/sync/statuscloud_sync
GET/api/sync/configscloud_sync
POST/api/sync/applycloud_sync
GET/api/policiespolicy_editor
GET/api/policies/{name}policy_editor
POST/api/policiespolicy_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

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.

POST /api/sync/apply
{ "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:

A policy
{
"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:

  • name must be 1-64 characters, ASCII alphanumeric and hyphens only. Checked on create; on update the name comes from the path and an unknown one is 404 instead.
  • Both thresholds must be within 0.0-10.0, and auto_allow_threshold must be strictly below auto_deny_threshold.
  • Every key in filters must 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.

See also

Last updated: 2026-08-24Edit this page on GitHub →