Access Control & Lighting API — Hardware Integration for Communities
The Apoing Access Control API lets you integrate automatic doors and facility lighting directly with the booking system. When there is an active booking, the door can be opened with a PIN or QR code, and the lights can turn on automatically. Without a booking, access and lighting remain locked.
This feature is designed for communities that already have — or want to install — basic home-automation hardware (Wi-Fi relays, Raspberry Pi) and want to connect it to the booking calendar without building a custom system.
How it works
Door access
- The user has an upcoming booking. In the Apoing app or website, the Open door button appears 15 minutes before the booking starts.
- When tapped, Apoing generates a 6-digit PIN (and QR code) valid until 15 minutes after the booking ends.
- The user enters the PIN on the door keypad or scans the QR with the reader. The hardware calls Apoing to validate the token.
- If the token is valid and matches the correct facility, Apoing marks it as used and the hardware activates the relay to open the door.
Lighting control
- A Raspberry Pi (or similar) periodically polls the calendar status endpoint (
GET /api/ext/v1/courts/{id}/status).
- If there is an active booking, the response contains
"occupied": true and the script activates the light relay (Shelly, Sonoff…).
- When the booking ends, the response changes to
"occupied": false and the script turns the light off.
Activating the API in your community
- Log in to your community as administrator.
- Go to Manage community → Integration (API).
- Click Generate API key. It is shown only once — store it in a safe place. This key identifies your community.
- In each calendar (Manage community → Edit calendar → "API Integration" block), enter the door ID your hardware will use (e.g.
door-padel-1). You can choose any value — use the same string you set as DOOR_ID in your script.
API reference
Endpoints to configure in your hardware
GET
/api/ext/v1/courts/{id}/status
Returns whether the calendar is currently occupied. Use it to turn lighting on or off. Poll every 60 seconds from the Raspberry Pi.
POST
/api/ext/v1/access/validate
Validates the PIN or QR the user presents at the door reader. Use it to grant or deny access. Call when the user submits a code.
Required header on both: X-Ext-Api-Key: <your-key> · Base URL: https://www.apoing.com/api/ext/v1/
The PIN code is generated automatically by Apoing when the user taps Open door in the app or website — your hardware does not need to request it or know that internal flow.
Endpoint 1 — Calendar status (lighting)
Returns whether a booking is currently active. Your Raspberry Pi calls this periodically to decide whether to turn the lights on or off.
GET /api/ext/v1/courts/{id}/status
X-Ext-Api-Key: apx-xxxxxxxxxxxxxxxx
Replace {id} with your calendar ID. You can find it under Manage community → Edit calendar → "API Integration" block, where it is shown as a read-only value.
Response:
{
"courtId": 42,
"occupied": true,
"until": "19:00",
"nextAt": null
}
occupied: true if a booking is currently in progress.
until: end time of the active booking (null if none).
nextAt: start time of the next booking today (null if none).
Endpoint 2 — Validate access token (door)
Called by your hardware when the user enters the PIN on the keypad or scans the QR code. Verifies the code is valid for that door and marks it as used.
POST /api/ext/v1/access/validate
X-Ext-Api-Key: apx-xxxxxxxxxxxxxxxx
Content-Type: application/json
{
"token": "384719",
"doorId": "door-padel-1"
}
The doorId value must match exactly the door ID configured for that calendar (Manage community → Edit calendar → "API Integration" block). You assign it freely — for example door-padel-1.
Valid response → open the door:
{
"valid": true,
"courtName": "Padel Court 1",
"until": "19:15"
}
Invalid response → deny access:
{
"valid": false,
"error": "Invalid or expired token"
}
Each token can only be used once. Apoing logs every attempt (valid or failed) in the access history.
Lighting hardware setup
Choose the option that best fits your budget and technical level. In all cases, you need a Raspberry Pi (or any Linux device with Python) that polls the Apoing API and sends commands to the relay over the local network.
Option A — Shelly Recommended
The easiest option: Shelly devices expose a local REST API without any additional firmware. Simply connect them to the facility's Wi-Fi.
Recommended models:
- Shelly Plus 1 — 1 channel, ideal for lights or simple electric doors.
- Shelly Plus 1PM — same as above, with power consumption monitoring.
- Shelly 2PM — 2 channels, ideal for garage door motors or shutters.
Where does this script run? On the Raspberry Pi (or any computer with Python on the same network). The Shelly device does not run Python — it simply receives HTTP commands from the Raspberry Pi. The flow is: Raspberry Pi → queries Apoing API → sends ON/OFF command to the Shelly over the local network.
Python script for Shelly (save on the Raspberry Pi as apoing-lights.py):
#!/usr/bin/env python3
# apoing-lights.py — Automatic lighting with Shelly
# Requires: pip install requests
import time, requests
APOING_API = "https://www.apoing.com/api/ext/v1"
EXT_API_KEY = "apx-YOUR_KEY_HERE"
COURT_ID = 42 # Calendar ID in Apoing
SHELLY_IP = "192.168.1.50" # Local IP of the Shelly device
HEADERS = {"X-Ext-Api-Key": EXT_API_KEY}
def set_relay(on: bool):
action = "on" if on else "off"
requests.get(f"http://{SHELLY_IP}/relay/0", params={"turn": action}, timeout=5)
print(f"[light] Shelly → {action.upper()}")
def loop():
r = requests.get(f"{APOING_API}/courts/{COURT_ID}/status", headers=HEADERS, timeout=10)
set_relay(r.json().get("occupied", False))
if __name__ == "__main__":
while True:
try: loop()
except Exception as e: print(f"[error] {e}")
time.sleep(60)
Option B — Sonoff + Tasmota Cheaper, more technical
Sonoff devices are more affordable than Shelly, but require flashing the Tasmota firmware via USB (documented at tasmota.github.io). Once installed, they offer a compatible HTTP API.
Recommended models:
- Sonoff Basic R2 — 1 channel, the most affordable option for lights or sockets.
- Sonoff S31 — plug socket with power consumption monitoring.
Python script for Sonoff + Tasmota:
#!/usr/bin/env python3
# apoing-lights.py — Automatic lighting with Sonoff/Tasmota
# Requires: pip install requests
import time, requests
APOING_API = "https://www.apoing.com/api/ext/v1"
EXT_API_KEY = "apx-YOUR_KEY_HERE"
COURT_ID = 42 # Calendar ID in Apoing
SONOFF_IP = "192.168.1.51" # Local IP of the Sonoff device
HEADERS = {"X-Ext-Api-Key": EXT_API_KEY}
def set_relay(on: bool):
cmd = "Power On" if on else "Power Off"
requests.get(f"http://{SONOFF_IP}/cm", params={"cmnd": cmd}, timeout=5)
print(f"[light] Sonoff → {cmd}")
def loop():
r = requests.get(f"{APOING_API}/courts/{COURT_ID}/status", headers=HEADERS, timeout=10)
set_relay(r.json().get("occupied", False))
if __name__ == "__main__":
while True:
try: loop()
except Exception as e: print(f"[error] {e}")
time.sleep(60)
⚠️ Make sure Tasmota is configured with a static IP (set a DHCP reservation in your router) and that the Wi-Fi AP mode is disabled.
Option C — Other HTTP-compatible hardware
Any relay or controller that accepts local HTTP commands works with Apoing. Simply adapt the set_relay() function in the script. The rest of the code (polling the Apoing API) is identical across all three options.
def set_relay(on: bool):
# Replace this call with your hardware's command
# Generic example: POST to a local endpoint
action = "on" if on else "off"
requests.post(f"http://YOUR_DEVICE/api", json={"relay": action}, timeout=5)
Running as a systemd service (applies to all three options):
Create /etc/systemd/system/apoing-lights.service:
[Unit]
Description=Apoing Lights Controller
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/apoing-lights.py
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable apoing-lights
sudo systemctl start apoing-lights
Door control hardware
Unlike lighting (where the Shelly receives commands from the Raspberry Pi), the door needs a device that reads the PIN or QR, calls the Apoing API to validate it, and triggers the relay on the electric strike. There are three ways to achieve this.
Option A — Raspberry Pi + matrix keypad Most capable
The most convenient option if you already have a Raspberry Pi for lighting: add a 4×4 matrix keypad (€2–5) and a relay. The Pi reads the PIN, calls Apoing and opens the door. It can also read USB QR readers that act as a keyboard input.
Python script (save on the Raspberry Pi as apoing-door.py):
#!/usr/bin/env python3
# apoing-door.py — PIN-based door access validation
# Requires: pip install requests
import requests, time
APOING_API = "https://www.apoing.com/api/ext/v1"
EXT_API_KEY = "apx-YOUR_KEY_HERE"
DOOR_ID = "door-padel-1"
SHELLY_IP = "192.168.1.51"
HEADERS = {
"X-Ext-Api-Key": EXT_API_KEY,
"Content-Type": "application/json"
}
def open_door():
requests.get(f"http://{SHELLY_IP}/relay/0", params={"turn": "on"}, timeout=5)
time.sleep(1)
requests.get(f"http://{SHELLY_IP}/relay/0", params={"turn": "off"}, timeout=5)
print("[door] Opened")
def validate_pin(pin: str) -> bool:
try:
r = requests.post(
f"{APOING_API}/access/validate",
headers=HEADERS,
json={"token": pin, "doorId": DOOR_ID},
timeout=10
)
return r.json().get("valid", False)
except Exception as e:
print(f"[apoing] Error: {e}")
return False
if __name__ == "__main__":
print("Apoing access system ready. Enter PIN:")
while True:
pin = input("> ").strip()
if len(pin) == 6 and pin.isdigit():
if validate_pin(pin):
print("✓ Access granted")
open_door()
else:
print("✗ Invalid or expired PIN")
else:
print("6-digit PIN required")
Option B — ESP32 + keypad or Wiegand QR reader Most affordable
If you only need door control (no lighting), an ESP32 (~€5) is enough: it has built-in Wi-Fi, can read a matrix keypad directly, or receive Wiegand output from generic QR readers (the kind found on AliExpress/Amazon without their own IP connectivity). No operating system required.
Arduino sketch for ESP32 (4×4 matrix keypad):
// apoing-door.ino — ESP32 + 4x4 keypad + relay
// Libraries: Keypad, HTTPClient (included in ESP32 Arduino core)
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Keypad.h>
const char* SSID = "YourWiFi";
const char* PASS = "YourPassword";
const char* API_URL = "https://www.apoing.com/api/ext/v1/access/validate";
const char* API_KEY = "apx-YOUR_KEY_HERE";
const char* DOOR_ID = "door-padel-1";
const int RELAY_PIN = 26;
char keys[4][4] = {{'1','2','3','A'},{'4','5','6','B'},{'7','8','9','C'},{'*','0','#','D'}};
byte rowPins[4] = {13,12,14,27}, colPins[4] = {26,25,33,32};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, 4, 4);
String pinBuffer = "";
bool validatePin(String pin) {
HTTPClient http;
http.begin(API_URL);
http.addHeader("Content-Type", "application/json");
http.addHeader("X-Ext-Api-Key", API_KEY);
String body = "{\"token\":\"" + pin + "\",\"doorId\":\"" + DOOR_ID + "\"}";
int code = http.POST(body);
if (code == 200) {
String resp = http.getString();
return resp.indexOf("\"valid\":true") >= 0;
}
return false;
}
void openDoor() {
digitalWrite(RELAY_PIN, HIGH);
delay(1000);
digitalWrite(RELAY_PIN, LOW);
}
void setup() {
pinMode(RELAY_PIN, OUTPUT);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
char key = keypad.getKey();
if (!key) return;
if (key == '#') {
if (pinBuffer.length() == 6 && validatePin(pinBuffer)) openDoor();
pinBuffer = "";
} else if (key == '*') {
pinBuffer = "";
} else if (pinBuffer.length() < 6) {
pinBuffer += key;
}
}
⚠️ Wiegand QR readers require an additional library (Wiegand for ESP32) to read the Wiegand signal and convert it to the PIN string before calling the API.
Option C — QR reader with native HTTP client No intermediary
Some QR access control readers have IP connectivity and can call a URL directly when they scan a code, with no Raspberry Pi or ESP32 needed. Before buying, verify that the device supports:
- HTTP POST calls to a configurable external URL
- JSON request body
- Custom HTTP header (
X-Ext-Api-Key)
If the reader only supports GET requests (no JSON body), it is not directly compatible with the Apoing API and will require an ESP32 as an intermediary.
Readers with their own management software (such as Kimaldi Kapri and similar devices) typically work with an access list stored in the device, not real-time API validation. To integrate them with Apoing, check the device manual for a webhook or HTTP event option that supports POST+JSON to an external URL with custom headers.
Access history
All validation attempts (valid and failed) are logged in the community admin panel (Manage community → Integration (API) → Access history). You can see the time, whether access was granted, and the IP address of the requesting device.
Frequently asked questions
Is the API free?
Yes. The access control API is free for all Apoing communities, with no call limits.
Can I have multiple doors or multiple calendars?
Yes. Each calendar has its own configurable door ID. You can have as many doors as your community has calendars, all managed with the same API key.
Does the PIN expire if unused?
Yes. The PIN is valid until 15 minutes after the booking ends. It is also single-use: once validated successfully, it is invalidated even if it has not expired.
Does it work with hardware not listed here?
Yes. Any device capable of making HTTP calls (GET/POST) can be integrated. The API is standard REST with JSON responses. The scripts on this page are examples you can adapt to your hardware.
Does the Raspberry Pi need internet access?
Yes, it needs internet access to call the Apoing API. The Shelly/Sonoff devices only need to be on the same local network as the Raspberry Pi — they do not require internet access.