Koban

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.com

The endpoint is the origin. The agent appends the v1 route paths listed below.

Format

All v1 HTTP requests use:

Content-Type: application/json

Field 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

MethodPathPurpose
POST/api/sensor/v1/enrollRegister a new device and return identity material.
POST/api/sensor/v1/configReturn the latest canonical JSON config bundle.
POST/api/sensor/v1/check-inRecord heartbeat and return control hints.
POST/api/sensor/v1/syncUpload 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:

FieldTypeNotes
eventIdstringStable event id. Used for deduplication.
deviceIdstringMust match the request deviceId.
localSequenceintegerPositive, monotonically increasing per device.
surfacestringOpaque surface identifier, e.g. homebrew or claudeConfig.
kindstringadded, modified, or removed.
observedAtstringRFC 3339 timestamp for when the change was observed.
collectedAtstringRFC 3339 timestamp for when the snapshot was collected.
payloadHashstringLowercase hex SHA-256 of the decoded payload bytes.
payloadstringBase64-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:

CodeMeaning
invalid_tenantRequest tenant id is missing or invalid.
invalid_deviceDevice id is missing, mismatched, unknown, or revoked.
invalid_schema_versionschemaVersion is unsupported.
invalid_event_idEvent id is missing.
invalid_sequencelocalSequence is not positive.
invalid_surfaceSurface identifier is malformed.
invalid_kindEvent kind is not added, modified, or removed.
invalid_timestampEvent timestamps are missing or invalid.
invalid_payload_hashpayloadHash does not match decoded payload bytes.
invalid_payloadPayload bytes are not a valid v1 sensor event payload.
empty_payloadPayload is empty.
batch_too_largeEvent count or body size exceeds backend limits.
duplicate_conflictThe same eventId was already used for different content.
rate_limitedBackend 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, and retryAfterSeconds

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.