Sensor protocol
The open JSON protocol spoken by the Koban agent.
The Koban agent is open source and can sync to any backend that implements the Koban Sensor Protocol v1. Koban Fleet is the commercial control plane that implements this protocol, but the protocol is not Fleet-specific.
This page documents the v1 JSON contract the current agent speaks.
Scope
The protocol lets a backend:
- Enroll a Mac and issue a device identity
- Return a remote configuration bundle
- Receive check-ins
- Receive inventory and finding events
- Acknowledge accepted and rejected events
It does not let a backend run commands on a Mac, block installs, edit files, or request arbitrary filesystem reads.
Base URL
The agent reads the backend URL from local YAML:
sync:
endpoint: https://fleet.example.comThe endpoint is the origin. The agent appends the v1 route paths listed below.
Format
All v1 HTTP requests use:
Content-Type: application/jsonField names are camelCase. Time values are RFC 3339 strings. JSON byte fields are base64-encoded strings because they are encoded from Swift Data and Go []byte.
The current protocol schema version is 1.
Routes
| Method | Path | Purpose |
|---|---|---|
POST | /api/sensor/v1/enroll | Register a new device and return identity material. |
POST | /api/sensor/v1/config | Return the latest canonical JSON config bundle. |
POST | /api/sensor/v1/check-in | Record heartbeat and return control hints. |
POST | /api/sensor/v1/sync | Upload inventory and finding event batches. |
The agent treats any 2xx HTTP status as transport success and decodes the response body as JSON. Non-2xx statuses are treated as server errors by the agent.
Authentication
Enrollment uses a bootstrap token in the JSON request body. Protected sensor routes should use mTLS in production.
For development or simple self-hosted backends without mTLS, the agent can send:
X-Koban-Sensor-Token: <token>When mTLS is enabled, token fallback should be disabled. See Authentication.
Enroll
POST /api/sensor/v1/enroll
Request:
{
"token": "enrollment-token",
"publicKey": "base64-pem-public-key",
"hostname": "dev-macbook-07",
"osVersion": "macOS 15.5",
"hardwareModel": "MacBookPro18,3",
"sensorVersion": "koban-agent"
}publicKey is a base64-encoded PEM public key. The current agent generates a P-256 key locally and stores the private key in the macOS Keychain.
Response:
{
"tenantId": "tenant_123",
"deviceId": "dev_123",
"clientCertificate": "base64-pem-client-certificate",
"certificateExpiresAt": "2026-07-01T12:00:00Z"
}clientCertificate is a base64-encoded PEM certificate. The agent pairs it with the private key it generated before enrollment.
Get config
POST /api/sensor/v1/config
Request:
{
"tenantId": "tenant_123",
"deviceId": "dev_123",
"currentGeneration": "2026-06-01"
}Response:
{
"generation": "2026-06-02",
"configJson": "base64-canonical-json",
"signature": ""
}configJson is a base64-encoded canonical JSON configuration bundle. Humans may author config as YAML, but the remote sensor protocol serves JSON.
signature is reserved and empty in v1. The agent does not require signed config today.
Check in
POST /api/sensor/v1/check-in
Request:
{
"tenantId": "tenant_123",
"deviceId": "dev_123",
"sensorVersion": "koban-agent",
"osVersion": "macOS 15.5",
"activeConfigGeneration": "2026-06-02",
"health": {
"syncBacklogEvents": 12,
"syncBacklogBytes": 4096
}
}Response:
{
"serverTime": "2026-06-02T12:00:00Z",
"configGeneration": "2026-06-02",
"configUpdateAvailable": false,
"certificateRenewalRequired": false,
"fullResnapshotRequested": false
}If configUpdateAvailable is true, the agent fetches /api/sensor/v1/config. If fullResnapshotRequested is true, the backend is asking the agent to rebuild and resend current inventory.
Sync
POST /api/sensor/v1/sync
Request:
{
"tenantId": "tenant_123",
"deviceId": "dev_123",
"sensorVersion": "koban-agent",
"schemaVersion": 1,
"lastAckedLocalSequence": 41,
"events": [
{
"eventId": "evt_123",
"deviceId": "dev_123",
"localSequence": 42,
"surface": "homebrew",
"kind": "added",
"observedAt": "2026-06-02T12:00:00Z",
"collectedAt": "2026-06-02T12:00:01Z",
"payloadHash": "lowercase-sha256-hex",
"payload": "base64-protobuf-sensor-event-payload"
}
],
"health": {
"syncBacklogEvents": 1,
"syncBacklogBytes": 1024
}
}Response:
{
"acceptedThroughLocalSequence": 42,
"acceptedEvents": [
{
"eventId": "evt_123",
"localSequence": 42,
"duplicate": false
}
],
"rejectedEvents": [],
"serverTime": "2026-06-02T12:00:02Z",
"configGeneration": "2026-06-02",
"configUpdateAvailable": false,
"fullResnapshotRequested": false,
"retryAfterSeconds": 15,
"maxBatchBytes": 524288,
"maxBatchEvents": 500
}Event envelope
Each sync event has a JSON envelope:
| Field | Type | Notes |
|---|---|---|
eventId | string | Stable event id. Used for deduplication. |
deviceId | string | Must match the request deviceId. |
localSequence | integer | Positive, monotonically increasing per device. |
surface | string | Opaque surface identifier, e.g. homebrew or claudeConfig. |
kind | string | added, modified, or removed. |
observedAt | string | RFC 3339 timestamp for when the change was observed. |
collectedAt | string | RFC 3339 timestamp for when the snapshot was collected. |
payloadHash | string | Lowercase hex SHA-256 of the decoded payload bytes. |
payload | string | Base64-encoded protobuf SensorEventPayload bytes. |
Valid surface identifiers are 1 to 128 characters and may contain letters, digits, ., _, :, and -.
Event payload
The payload field is not JSON. It is a base64-encoded protobuf message named SensorEventPayload.
message SensorEventPayload {
uint32 payload_version = 1;
oneof body {
InventoryItemPayload inventory_item = 2;
FindingPayload finding = 3;
}
}The agent currently emits payload_version = 1.
Inventory item payload
message InventoryItemPayload {
string item_id = 1;
string surface = 2;
string kind = 3;
string name = 4;
string version = 5;
string path = 6;
ProvenancePayload provenance = 7;
map<string, string> attributes = 8;
}
message ProvenancePayload {
string origin = 1;
optional bool installed_on_request = 2;
string detail = 3;
}The backend requires item_id, surface, kind, name, and path. The payload surface must match the event envelope surface.
The current agent may omit attributes.
Finding payload
message FindingPayload {
string id = 1;
string surface = 2;
string item_id = 3;
string rule_id = 4;
string title = 5;
string rationale = 6;
string severity = 7;
string item_name = 8;
FindingEvidencePayload evidence = 9;
string observed_at = 10;
}
message FindingEvidencePayload {
string path = 1;
string detail = 2;
string matched_field = 3;
string matched_value = 4;
}The backend requires id, item_id, rule_id, title, rationale, item_name, valid severity, and valid observed_at. The payload surface must match the event envelope surface.
Valid severities are info, notable, and suspicious.
Rejections
Rejected events appear in rejectedEvents.
Known rejection codes:
| Code | Meaning |
|---|---|
invalid_tenant | Request tenant id is missing or invalid. |
invalid_device | Device id is missing, mismatched, unknown, or revoked. |
invalid_schema_version | schemaVersion is unsupported. |
invalid_event_id | Event id is missing. |
invalid_sequence | localSequence is not positive. |
invalid_surface | Surface identifier is malformed. |
invalid_kind | Event kind is not added, modified, or removed. |
invalid_timestamp | Event timestamps are missing or invalid. |
invalid_payload_hash | payloadHash does not match decoded payload bytes. |
invalid_payload | Payload bytes are not a valid v1 sensor event payload. |
empty_payload | Payload is empty. |
batch_too_large | Event count or body size exceeds backend limits. |
duplicate_conflict | The same eventId was already used for different content. |
rate_limited | Backend is asking the device to slow down. |
Compatibility checklist
A backend is compatible with the current open-source agent if it can:
- Accept JSON requests at the four v1 route paths
- Issue a
tenantId,deviceId, and PEM client certificate during enrollment - Authenticate protected sensor routes with mTLS, or explicitly configure the agent with a development
sensorToken - Return config as base64-encoded canonical JSON
- Accept sync event envelopes with base64 protobuf payloads
- Validate payload hash and schema version
1 - Return accepted and rejected event decisions
- Return
maxBatchBytes,maxBatchEvents, andretryAfterSeconds
Koban Fleet adds commercial control-plane features on top of this contract: enrollment UI, policy publishing, review workflows, alert routing, audit history, and hosted operations.