Project Context API
Real-time, project-scoped WebSocket API for live sensor data, variables, events, and bidirectional control.
The Project Context API gives any application — display previews, dashboards, integrations — a standard way to subscribe to a project's live data and send commands back.
Quick Start
1. Create an API Key
Generate a key in the Palpable app under Project Settings > API Keys, or via the API Keys API.
Key format: pk_{8-char}_{32-char}
Example: pk_your_api_keyKeys can be read-only (subscribe to data) or read-write (also set variables and trigger behaviors).
2. Discover the WebSocket URL
const res = await fetch(
`https://palpable.technology/api/projects/${projectId}/connect`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
)
const { wsUrl } = await res.json()Response:
{
"wsUrl": "wss://agent.palpable.technology/session/{userId}/ctx/{projectId}?token=pk_your_api_key",
"projectId": "550e8400-...",
"permissions": ["read"]
}3. Connect and Subscribe
const ws = new WebSocket(wsUrl)
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'subscribe',
channels: ['sensors', 'variables', 'events', 'status']
}))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
console.log(msg.type, msg)
}Channels
| Channel | Description |
|---|---|
sensors | Live sensor readings from connected hardware |
variables | Behavior variable state changes |
events | Behavior execution events (triggered, error) |
status | Device connection status and module list |
Subscribe to any combination of channels. You'll only receive messages for channels you've subscribed to.
WebSocket Protocol
Client to Server
subscribe
Subscribe to one or more channels. Triggers a snapshot of current state.
{ "type": "subscribe", "channels": ["sensors", "variables"] }unsubscribe
Stop receiving messages for specific channels.
{ "type": "unsubscribe", "channels": ["events"] }set_variable
Set a behavior variable on the device. Requires write permission.
{ "type": "set_variable", "name": "score", "value": 42 }trigger_behavior
Manually trigger a behavior. Requires write permission.
{ "type": "trigger_behavior", "behaviorId": "550e8400-..." }ping
Keep the connection alive.
{ "type": "ping" }Server to Client
subscribed
Confirms your current channel subscriptions.
{ "type": "subscribed", "channels": ["sensors", "variables"] }snapshot
Full state snapshot, sent immediately after subscribing.
{
"type": "snapshot",
"sensors": {
"modulino-knob": { "encoder": 42 },
"bme280": { "temperature": 22.5, "humidity": 45 }
},
"variables": { "score": 10, "mode": "active" },
"status": {
"connected": true,
"modules": [
{ "moduleId": "modulino-knob", "name": "Modulino Knob", "address": 60 }
]
}
}sensor_data
Live sensor reading update.
{
"type": "sensor_data",
"channel": "sensors",
"moduleId": "modulino-knob",
"value": { "encoder": 42 },
"timestamp": 1709142000000
}variable_state
Variable values changed.
{
"type": "variable_state",
"channel": "variables",
"variables": { "score": 10 }
}behavior_event
A behavior was triggered or errored.
{
"type": "behavior_event",
"channel": "events",
"event": "behavior_executed",
"behaviorId": "550e8400-...",
"behaviorName": "Turn on lights",
"timestamp": 1709142000000
}device_status
Device connected/disconnected or modules changed.
{
"type": "device_status",
"channel": "status",
"connected": true,
"modules": [
{ "moduleId": "bme280", "name": "BME280", "address": 118 }
]
}error
Something went wrong.
{ "type": "error", "message": "Permission denied: write access required", "code": "FORBIDDEN" }Error codes: FORBIDDEN, INVALID_MESSAGE, NOT_FOUND, INTERNAL.
Authentication
API Key Format
pk_{prefix}_{secret}- prefix (8 chars): displayed in the dashboard for identification
- secret (32 chars): the actual secret, never stored — only its SHA-256 hash is in the database
Permissions
| Permission | Capabilities |
|---|---|
read | Subscribe to all channels, receive snapshots and live data |
write | Everything in read, plus set_variable and trigger_behavior |
Key Expiry
Keys can optionally have an expires_at timestamp. Expired keys are rejected on connect and on the discovery endpoint.
Examples
Live Dashboard
async function connectToProject(projectId, apiKey) {
const res = await fetch(
`https://palpable.technology/api/projects/${projectId}/connect`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
)
const { wsUrl } = await res.json()
const ws = new WebSocket(wsUrl)
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'subscribe',
channels: ['sensors', 'variables']
}))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.type) {
case 'snapshot':
console.log('Initial state:', msg.sensors, msg.variables)
break
case 'sensor_data':
console.log(`${msg.moduleId}:`, msg.value)
break
case 'variable_state':
console.log('Variables changed:', msg.variables)
break
}
}
return ws
}Setting Variables from an External App
// Requires a key with 'write' permission
ws.send(JSON.stringify({
type: 'set_variable',
name: 'target_temperature',
value: 25
}))Advanced Examples
Dashboard with Multiple Channels
Subscribe to sensors, variables, and events simultaneously to build a real-time dashboard:
const ws = new WebSocket(wsUrl)
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'subscribe',
channels: ['sensors', 'variables', 'events', 'status']
}))
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
switch (msg.type) {
case 'sensor_data':
updateSensorChart(msg.moduleId, msg.capability, msg.value)
break
case 'variable_update':
updateVariableDisplay(msg.name, msg.value)
break
case 'behavior_event':
addToEventLog(msg.behaviorName, msg.action, msg.timestamp)
break
case 'device_status':
updateConnectionBadge(msg.status) // 'online' | 'offline'
break
}
}Bidirectional Control Panel
Build a control panel that reads sensor data AND sends commands back:
// Subscribe to sensors
ws.send(JSON.stringify({ type: 'subscribe', channels: ['sensors', 'variables'] }))
// Toggle a variable (e.g., light switch)
function toggleLight() {
ws.send(JSON.stringify({
type: 'set_variable',
name: 'light_on',
value: !currentLightState
}))
}
// Trigger a behavior manually
function runBehavior(behaviorId) {
ws.send(JSON.stringify({
type: 'trigger_behavior',
behaviorId
}))
}Display Preview Integration
Connect a custom display preview to live project data:
<!-- Embed in an iframe or standalone page -->
<iframe
src="https://your-container.palpable.technology/display/i2c:0x3c/preview?projectId=YOUR_PROJECT_ID&token=pk_your_key"
width="128"
height="64"
style="image-rendering: pixelated; transform: scale(4);"
/>The preview URL automatically connects to the Project Context API and injects live sensor values into the display component's sensors and variables props.