VCP data model
Envelope structure, payload schemas, enums, and identifier conventions for every VCP message on the vcp exchange.
Overview
VCP (Voke Communication Protocol) is the message contract between a partner ESM adapter and Voke. Every message on the vcp exchange — inbound commands, config, and schedules published by the partner, and outbound events published by Voke — uses the same JSON envelope. The payload field carries the concrete data; its shape is determined by the routing key.
The envelope
Every VCP message, regardless of direction or routing key, must conform to VcpMessageEnvelopeSchema:
interface VcpMessage<T> {
version: '1.1';
messageId: string;
correlationId?: string;
timestamp: string; // ISO 8601 UTC, validated with z.string().datetime()
source: string;
siteId: string;
payload: T;
}| Field | Required | Description |
|---|---|---|
version | Yes | Literal '1.1'. Voke rejects envelopes with any other value. Future breaking changes will increment this string. |
messageId | Yes | Unique message identifier. Use UUIDv4. Exists for partner-side bookkeeping and deduplication (see VCP message integrity). |
correlationId | No | Ties an outbound event back to the inbound command that triggered it. Set this on every command you want to track; Voke echoes it in the resulting event.command.ack and event.execution messages. |
timestamp | Yes | ISO 8601 UTC (2024-05-01T12:00:00.000Z). Voke validates parseability via Zod .datetime(); keep your clock NTP-synced. |
source | Yes | Identifies the sender. Voke-originated events set this to 'voke'. Partners should use a stable identifier such as their ESM code (e.g. 'cpi-energo', 'obzor'). |
siteId | Yes | The plant's externalPlantId — the partner-facing identifier the Voke operator sets on the plant. Maps the message to a specific managed site. |
payload | Yes | The message body. Shape depends on routing key (see below). |
signatureAlgo | Conditional | Optional algorithm marker. Use HMAC-SHA256 when signing. |
signature | Conditional | Base64url HMAC over the canonicalized envelope excluding signature. Required for command.device, command.mode, and schedule.*. |
Inbound payload shapes (partner → Voke)
Command payloads
Published to vcp.{slug}.command, routing key {slug}.command.{subtype}.
SiteSetpointPayload — {slug}.command.site-setpoint
| Field | Type | Required | Description |
|---|---|---|---|
type | 'POWER' | 'ENERGY' | Yes | Whether targetValueKw is a power or energy target. |
targetValueKw | finite number | Conditional | Required when type = POWER. |
targetValueKwh | finite number | Conditional | Required when type = ENERGY. |
intervalMinutes | number | Conditional | Required when type = ENERGY; optional for POWER. |
direction | 'IMPORT' | 'EXPORT' | Yes | Whether the target applies to import or export direction. |
includeConsumption | boolean | Yes | Whether local consumption is included in the site-level target calculation. |
priority | SchedulePriority | Yes | NORMAL, HIGH, or EMERGENCY. |
validFrom | ISO 8601 UTC | Yes | Earliest activation time. |
validUntil | ISO 8601 UTC | No | Expiry time. |
BatchDeviceCommandPayload — {slug}.command.device
VCP v1.1 accepts device commands as a batch. A single device command should be wrapped as a one-item commands array.
| Field | Type | Required | Description |
|---|---|---|---|
commands | DeviceCommand[] | Yes | 1-32 device commands. |
DeviceCommand fields:
| Field | Type | Required | Description |
|---|---|---|---|
deviceId | string | Yes | Target device's externalId (partner-facing). |
assetType | AssetType | Yes | Asset class of the target device (see Enum references). |
command | DeviceCommandType | Yes | The command to execute (see Enum references). |
params.powerKw | number | No | Power level for charge/discharge commands. |
params.percent | number | No | Percent reduction for FVE curtailment. |
params.respectLimits | boolean | No | Whether to honour configured SOC and power limits. |
deviceId in this payload maps to the sub-device's externalId on the Voke plant — it does not use the Voke UUID. Device-level commands are the only inbound payloads that carry a device-scoped identifier; all other inbound payloads address the site as a whole via siteId in the envelope.
EmergencyCommandPayload — {slug}.command.emergency
| Field | Type | Required | Description |
|---|---|---|---|
type | 'STOP' | 'HOLD' | Yes | STOP shuts down all controllable assets; HOLD freezes at current output. |
reason | string (max 500) | No | Human-readable reason for logging. |
OperatingModePayload — {slug}.command.mode
| Field | Type | Required | Description |
|---|---|---|---|
mode | OperatingMode | Yes | Target operating mode (see Enum references). |
reason | string (max 500) | No | Reason for the mode change. |
validUntil | ISO 8601 UTC | No | Expiry time for the override. |
Config payloads
Published to vcp.{slug}.config, routing key {slug}.config.{subtype}.
SiteConstraintsPayload — {slug}.config.site-constraints
All fields are optional / nullable. Omit to leave a constraint unchanged.
| Field | Type | Description |
|---|---|---|
maxExportKw | number | null | Maximum grid export power. |
maxImportKw | number | null | Maximum grid import power. |
batteryMinSocPercent | number | null | Minimum allowed battery SOC. |
batteryMaxSocPercent | number | null | Maximum allowed battery SOC. |
maxChargeRateKw | number | null | Maximum battery charge rate. |
maxDischargeRateKw | number | null | Maximum battery discharge rate. |
maxFvePowerKw | number | null | FVE output cap. |
rampRateKwPerSec | number | null | Maximum ramp rate. |
fveOnlyCharging | boolean | If true, only FVE surplus may charge the battery. |
maxBatteryCyclesPerDay | number | null | Daily cycle limit. |
energyFlowPriority | EnergyFlowPriority | Priority order for energy routing (see Enum references). |
FallbackConfigPayload — {slug}.config.fallback-config
| Field | Type | Required | Description |
|---|---|---|---|
mode | 'STOP' | 'HOLD' | 'LAST_PLAN' | Yes | Behaviour when the connection to the ESM is lost. |
timeoutSeconds | finite number | Yes | Inactivity duration before fallback activates. |
SiteTopologyPayload — {slug}.config.site-topology
| Field | Type | Required | Description |
|---|---|---|---|
assets | SiteAssetDeclaration[] | Yes | Array of declared assets (see below). |
hasLocalConsumption | boolean | Yes | Whether the site has uncontrolled local load. |
isLds | boolean | Yes | Whether the site participates in local distribution support. |
pdsConnectionPointId | string | No | PDS connection point reference. |
SiteAssetDeclaration fields:
| Field | Type | Required | Description |
|---|---|---|---|
deviceId | string | Yes | Asset's externalId. |
assetType | AssetType | Yes | Asset class. |
nominalPowerKw | finite number | Yes | Rated power in kW. |
nominalCapacityKwh | finite number | No | Rated capacity (batteries). |
Schedule payloads
Published to vcp.{slug}.schedule, routing key {slug}.schedule.{subtype}.
ScheduleCreatePayload — {slug}.schedule.create
| Field | Type | Required | Description |
|---|---|---|---|
scheduleId | string | Yes | Partner-assigned unique ID for idempotent upserts. |
startTime | ISO 8601 UTC | Yes | Schedule start. |
endTime | ISO 8601 UTC | Yes | Schedule end. |
slotDurationMinutes | positive integer | No | Slot size. Defaults to 15. |
slots | ScheduleSlotPayload[] | Yes | Ordered time slots covering the schedule window. |
priority | 'NORMAL' | 'HIGH' | Yes | Schedule execution priority. |
ScheduleSlotPayload fields:
| Field | Type | Required | Description |
|---|---|---|---|
slotIndex | non-negative integer | Yes | Zero-based slot index within the schedule. |
setpoint | Setpoint | Yes | Slot target. Same shape as site setpoint's base fields (type, target value, direction, includeConsumption, optional interval). |
mode | OperatingMode | Yes | Operating mode that should be active for this slot. |
controlMode | ControlMode | Yes | IDLE=0, CHARGE=1, DISCHARGE=2, AUTO=3. |
maxChargePowerKw | finite number | null | Yes | Slot-level charge cap (null = use site constraint). |
maxDischargePowerKw | finite number | null | Yes | Slot-level discharge cap (null = use site constraint). |
ScheduleCancelPayload — {slug}.schedule.cancel
| Field | Type | Required | Description |
|---|---|---|---|
scheduleId | string | Yes | ID of the schedule to cancel. |
siteId | string | Yes | Site the schedule belongs to. |
reason | string (max 500) | No | Reason for cancellation. |
Outbound payload shapes (Voke → partner)
Published by Voke on the vcp exchange; your consumers receive these.
CanonicalTelemetry — {slug}.event.telemetry.realtime
Published by publishTelemetry. All power/energy fields are nullable (null = data unavailable).
| Field | Type | Description |
|---|---|---|
gridPowerKw | number | null | Net grid power (positive = import). |
fvePowerKw | number | null | FVE generation. |
batteryPowerKw | number | null | Battery power (positive = charging). |
consumptionPowerKw | number | null | Site consumption. |
socPercent | number | null | Battery state of charge (%). |
availableBatteryEnergyKwh | number | null | Available battery energy in kWh. |
batteryTemperatureCelsius | number | null | Battery temperature. |
currentOperatingMode | OperatingMode | Active operating mode. |
dataQuality | 'GOOD' | 'INTERPOLATED' | 'STALE' | 'MISSING' | Quality indicator for the measurement. |
devices | DeviceTelemetry[] | Optional per-device breakdown. |
DeviceTelemetry fields:
| Field | Type | Description |
|---|---|---|
deviceId | string | Device's externalId. |
assetType | AssetType | Asset class. |
powerKw | finite number | Device power output/input. |
socPercent | number | Battery SOC (if applicable). |
temperatureCelsius | number | Device temperature (if applicable). |
availableCapacityKwh | number | Available energy capacity (if applicable). |
regulationPercent | number | Regulation setpoint tracking (if applicable). |
MeterReadingPayload — {slug}.event.telemetry.meter
Published by publishMeterReading. VCP v1.1 uses 1-minute absolute meter-register snapshots instead of the older 15-minute interval aggregate.
| Field | Type | Description |
|---|---|---|
readingAt | ISO 8601 UTC | Clock-aligned minute timestamp. |
meters | MeterEntry[] | One or more meter register snapshots. |
dataQuality | 'GOOD' | 'DEGRADED' | 'ESTIMATED' | Quality indicator. |
MeterEntry fields:
| Field | Type | Description |
|---|---|---|
meterId | string | Meter sub-device externalId. |
role | 'GRID' | 'FVE' | 'BESS' | 'CONSUMPTION' | Meter role. |
importRegisterKwh | number | Grid import register. |
exportRegisterKwh | number | Grid export register. |
productionRegisterKwh | number | FVE production register. |
chargeRegisterKwh | number | BESS charge register. |
dischargeRegisterKwh | number | BESS discharge register. |
consumptionRegisterKwh | number | Consumption register. |
AlarmPayload — {slug}.event.alarm
Published by publishAlarm. Represents a PLC-originated condition.
| Field | Type | Required | Description |
|---|---|---|---|
alarmId | string | Yes | Unique alarm instance ID. |
severity | VcpAlarmSeverity | Yes | P1_CRITICAL, P2_MAJOR, P3_MINOR, or P4_INFO. |
code | VcpAlarmCode | Yes | Alarm type code (see Enum references). |
message | string | Yes | Human-readable description. |
deviceId | string | No | Device externalId if the alarm is device-scoped. |
raisedAt | ISO 8601 UTC | Yes | When the alarm was raised. |
clearedAt | ISO 8601 UTC | No | Set when the alarm clears (update message). |
actualValue | string | number | No | Actual measured value for machine-readable alarms. |
expectedValue | string | number | No | Expected target or threshold value. |
breachedField | string | No | Field that breached a constraint. |
tolerancePercent | number | No | Tolerance used when evaluating the alarm. |
metadata | record | No | Arbitrary key–value context. |
VCP uses "alarm" for PLC-originated conditions forwarded upstream. "Alert" refers to Voke rule-generated notifications and is unrelated to this payload.
CommandAckPayload — {slug}.event.command.ack
Published by publishCommandAck. Confirms or rejects receipt of an inbound command.
| Field | Type | Required | Description |
|---|---|---|---|
status | 'ACCEPTED' | 'REJECTED' | 'PARTIAL' | 'QUEUED' | Yes | Outcome of command receipt. |
commandType | string | Yes | Echoes the command type from the inbound routing key. |
message | string | No | Human-readable explanation (required on rejection). |
rejectionCode | RejectionCode | No | Structured rejection reason (see Enum references). |
results | CommandResult[] | Required for PARTIAL | Per-command result array for batch device commands. |
CommandStatusPayload — {slug}.event.execution
Published by publishCommandStatus. Execution updates after a command has been accepted.
| Field | Type | Required | Description |
|---|---|---|---|
commandType | string | Yes | The command type being reported on. |
status | 'EXECUTING' | 'COMPLETED' | 'DEVIATED' | 'FAILED' | Yes | Current execution state. |
actualValueKw | number | No | Observed output (for setpoint commands). |
targetValueKw | number | No | The requested value for comparison. |
deviationKw | number | No | Difference between actual and target. |
reason | string | No | Reason for deviation or failure. |
ScheduleAckPayload — {slug}.event.schedule.*
Acknowledges a schedule create or cancel operation.
| Field | Type | Required | Description |
|---|---|---|---|
scheduleId | string | Yes | Echoes the scheduleId from the inbound payload. |
siteId | string | Yes | Site the schedule was applied to. |
accepted | boolean | Yes | Whether Voke accepted the schedule. |
status | ScheduleStatus | Yes | Schedule lifecycle state (see Enum references). |
rejectionReason | string | null | Yes | Human-readable reason if accepted = false. |
validatedAt | ISO 8601 UTC | Yes | When Voke validated the schedule. |
Enum references
AssetType
enum AssetType {
BESS = 'BESS',
FVE = 'FVE',
METER = 'METER',
HEAT_PUMP = 'HEAT_PUMP',
EV_CHARGER= 'EV_CHARGER',
THERMOSTAT= 'THERMOSTAT',
INVERTER = 'INVERTER',
GENERIC = 'GENERIC',
}OperatingMode
enum OperatingMode {
STANDARD = 'STANDARD',
ZERO_EXPORT = 'ZERO_EXPORT',
MAX_EXPORT = 'MAX_EXPORT',
PEAK_SHAVING = 'PEAK_SHAVING',
LOCAL_OPTIMIZATION = 'LOCAL_OPTIMIZATION',
GRID_TARGET = 'GRID_TARGET',
LDS_SUPPORT = 'LDS_SUPPORT',
}DeviceCommandType
enum DeviceCommandType {
FVE_PRODUCE_MAX = 'FVE_PRODUCE_MAX',
FVE_REDUCE_PERCENT = 'FVE_REDUCE_PERCENT',
FVE_REDUCE_POWER = 'FVE_REDUCE_POWER',
FVE_STOP = 'FVE_STOP',
BESS_CHARGE = 'BESS_CHARGE',
BESS_DISCHARGE = 'BESS_DISCHARGE',
BESS_STOP = 'BESS_STOP',
BESS_CHARGE_ONLY = 'BESS_CHARGE_ONLY',
BESS_DISCHARGE_ONLY = 'BESS_DISCHARGE_ONLY',
BESS_CONTINUOUS_CHARGE = 'BESS_CONTINUOUS_CHARGE',
}ControlMode
enum ControlMode {
IDLE = 0,
CHARGE = 1,
DISCHARGE = 2,
AUTO = 3,
}EnergyFlowPriority
enum EnergyFlowPriority {
FVE_BESS_GRID = 'FVE_BESS_GRID',
FVE_CONSUMPTION_BESS_GRID = 'FVE_CONSUMPTION_BESS_GRID',
FVE_CONSUMPTION_GRID_BESS = 'FVE_CONSUMPTION_GRID_BESS',
}VcpAlarmSeverity
enum VcpAlarmSeverity {
P1_CRITICAL = 'P1_CRITICAL',
P2_MAJOR = 'P2_MAJOR',
P3_MINOR = 'P3_MINOR',
P4_INFO = 'P4_INFO',
}VcpAlarmCode
enum VcpAlarmCode {
COMM_LOSS = 'COMM_LOSS',
COMM_DEGRADED = 'COMM_DEGRADED',
BESS_SOC_LOW = 'BESS_SOC_LOW',
BESS_SOC_HIGH = 'BESS_SOC_HIGH',
BESS_TEMP_HIGH = 'BESS_TEMP_HIGH',
BESS_FAULT = 'BESS_FAULT',
FVE_CURTAILED = 'FVE_CURTAILED',
FVE_INVERTER_FAULT = 'FVE_INVERTER_FAULT',
GRID_EXPORT_LIMIT_REACHED = 'GRID_EXPORT_LIMIT_REACHED',
GRID_IMPORT_LIMIT_REACHED = 'GRID_IMPORT_LIMIT_REACHED',
SETPOINT_DEVIATION = 'SETPOINT_DEVIATION',
SETPOINT_UNACHIEVABLE = 'SETPOINT_UNACHIEVABLE',
FALLBACK_ACTIVATED = 'FALLBACK_ACTIVATED',
OPERATING_MODE_NOT_HONORED = 'OPERATING_MODE_NOT_HONORED',
SCHEDULE_SLOT_MISSED = 'SCHEDULE_SLOT_MISSED',
CONSTRAINT_VIOLATION = 'CONSTRAINT_VIOLATION',
}RejectionCode
enum RejectionCode {
CONSTRAINT_VIOLATION = 'CONSTRAINT_VIOLATION',
INVALID_COMMAND = 'INVALID_COMMAND',
INVALID_PAYLOAD = 'INVALID_PAYLOAD',
DEVICE_OFFLINE = 'DEVICE_OFFLINE',
DEVICE_FAULT = 'DEVICE_FAULT',
UNSUPPORTED_FOR_TOPOLOGY = 'UNSUPPORTED_FOR_TOPOLOGY',
}ScheduleStatus
enum ScheduleStatus {
PENDING = 'PENDING',
ACCEPTED = 'ACCEPTED',
ACTIVE = 'ACTIVE',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
REJECTED = 'REJECTED',
PARTIALLY_EXECUTED = 'PARTIALLY_EXECUTED',
EXPIRED = 'EXPIRED',
FAILED = 'FAILED',
}Plant / site / device identifier conventions
| Identifier | Where used | Maps to |
|---|---|---|
siteId (envelope) | All messages | Plant's externalPlantId — the partner-facing ID the Voke operator assigns to the plant. |
deviceId (payload) | DeviceCommandPayload, SiteAssetDeclaration, SiteTopologyPayload, DeviceTelemetry, AlarmPayload | Sub-device's externalId — the short operator-assigned identifier for a physical component (R1, M1, etc.). |
Only DeviceCommandPayload and SiteAssetDeclaration payloads carry device-scoped identifiers. All other commands and events address the site as a whole through the envelope's siteId.
Versioning
The version field is a literal string, currently '1.1'. Partners should check this field on every incoming message and reject (nack with requeue = false) any envelope with an unrecognised version. Voke will increment the version string for any breaking change to the envelope or a payload schema.
Integrity and ordering guarantees live in VCP message integrity.