Skip to main content

Tutorial: Build Your First Adapter

Build a working adapter from scratch that integrates a simulated door lock with SpaceOS.

What You'll Build

A Node.js Express server that implements all 7 adapter protocol endpoints for a virtual door lock.

Prerequisites

  • Node.js 18+
  • A SpaceOS sandbox account
  • Basic knowledge of REST APIs

Step 1: Project Setup

mkdir my-adapter && cd my-adapter
npm init -y
npm install express

Step 2: Implement the Server

Create server.js:

const express = require('express');
const app = express();
app.use(express.json());

const API_KEY = process.env.API_KEY || 'my-adapter-secret';
let doorLocked = true;

// Auth middleware
function auth(req, res, next) {
if (req.headers['x-api-key'] !== API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}

// 1. GET /ping — Health check
app.get('/ping', auth, (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString(), version: '2.0' });
});

// 2. GET /capabilities — Declare features
app.get('/capabilities', auth, (req, res) => {
res.json({
adapter: { name: 'My First Adapter', version: '1.0.0', vendor: 'Tutorial', protocol: '2.0' },
devices: [{
device_id: 'demo-lock-001',
name: 'Demo Door Lock',
type: 'lock',
model: 'Virtual Lock',
brand: 'Tutorial',
criticality: 'CRITICAL',
actions: ['unlock', 'lock', 'status'],
webhook_capable: false,
html_embed: true
}]
});
});

// 3. GET /status — Device state
app.get('/status', auth, (req, res) => {
res.json({
device_id: req.query.device_id,
online: true,
state: { locked: doorLocked, last_activity: new Date().toISOString() }
});
});

// 4. POST /action — Execute command
app.post('/action', auth, (req, res) => {
const { action } = req.body;
if (action === 'unlock') { doorLocked = false; setTimeout(() => doorLocked = true, 5000); }
if (action === 'lock') doorLocked = true;
res.json({ success: true, action, device_id: req.query.device_id, result: { locked: doorLocked } });
});

// 5. GET /ui — HTML embed
app.get('/ui', auth, (req, res) => {
res.type('html').send(`
<div style="text-align:center;padding:20px;font-family:sans-serif">
<h3>Demo Lock</h3>
<p>Status: ${doorLocked ? 'Locked' : 'Unlocked'}</p>
<button onclick="fetch('/action?device_id=${req.query.device_id}',{method:'POST',headers:{'Content-Type':'application/json','x-api-key':'${API_KEY}'},body:JSON.stringify({action:'unlock'})}).then(()=>location.reload())"
style="padding:12px 24px;background:#4CAF50;color:white;border:none;border-radius:8px;cursor:pointer">
Unlock
</button>
</div>
`);
});

// 6. POST /webhook/register — Register for events
app.post('/webhook/register', auth, (req, res) => {
res.json({ registered: true, events: req.body.events || [] });
});

// 7. POST /webhook/:event — Receive events
app.post('/webhook/:event', auth, (req, res) => {
console.log(`Webhook event: ${req.params.event}`, req.body);
res.json({ received: true });
});

app.listen(3000, () => console.log('Adapter running on port 3000'));

Step 3: Run It

API_KEY=my-secret node server.js

Step 4: Test Locally

# Health check
curl -H "x-api-key: my-secret" http://localhost:3000/ping

# Capabilities
curl -H "x-api-key: my-secret" http://localhost:3000/capabilities

# Status
curl -H "x-api-key: my-secret" "http://localhost:3000/status?device_id=demo-lock-001"

# Unlock
curl -X POST -H "x-api-key: my-secret" -H "Content-Type: application/json" \
"http://localhost:3000/action?device_id=demo-lock-001" \
-d '{"action":"unlock"}'

Step 5: Register in SpaceOS

  1. Make your adapter reachable (Tailscale, ngrok, or public deployment)
  2. In the admin dashboard, navigate to a physical space → Adapters tab
  3. Click Add Adapter
  4. Enter: https://your-adapter-url?device_id=demo-lock-001
  5. Enter your API key
  6. Click Preview Capabilities
  7. Confirm and onboard

What You Built

A fully functional SpaceOS adapter that:

  • Responds to health checks
  • Declares its capabilities
  • Reports device status
  • Executes lock/unlock commands
  • Serves a guest-facing UI tile
  • Supports webhook registration

Next Steps