API de control de acceso e iluminación — Integración con Apoing

La API de control de acceso de Apoing permite integrar puertas automáticas e iluminación de tus instalaciones directamente con el sistema de reservas. Cuando hay una reserva activa, la puerta puede abrirse con un código PIN o QR, y la luz puede encenderse automáticamente. Sin reserva, el acceso y la iluminación permanecen bloqueados.

Es una funcionalidad pensada para comunidades que ya tienen o quieren instalar hardware de domótica básico (relés WiFi, Raspberry Pi) y quieren conectarlo con el calendario de reservas sin desarrollar un sistema propio.

Cómo funciona

Apertura de puertas

  1. El usuario tiene una reserva próxima. En la app o web de Apoing, 15 minutos antes de la reserva aparece el botón Abrir puerta.
  2. Al pulsarlo, Apoing genera un código PIN de 6 dígitos (y código QR) válido hasta 15 minutos después de que termine la reserva.
  3. El usuario introduce el PIN en el teclado de la puerta o escanea el QR con el lector. El hardware llama a Apoing para validar el token.
  4. Si el token es válido y corresponde a la instalación correcta, Apoing lo marca como usado y el hardware activa el relé para abrir la puerta.

Control de iluminación

  1. Un dispositivo Raspberry Pi (o similar) consulta periódicamente el endpoint de estado del calendario (GET /api/ext/v1/courts/{id}/status).
  2. Si hay una reserva activa, la respuesta indica "occupied": true y el script activa el relé de la luz (Shelly, Sonoff…).
  3. Cuando la reserva termina, la respuesta cambia a "occupied": false y el script apaga la luz.

Activar la API en tu comunidad

  1. Entra en tu comunidad como administrador.
  2. Ve a Gestión de la comunidad → Integración (API).
  3. Pulsa Generar clave API. Se muestra una sola vez: guárdala en un lugar seguro. Esta clave identifica a tu comunidad.
  4. En cada calendario (Gestión de la comunidad → Editar calendario → bloque "API de integración"), introduce el ID de puerta que usará tu hardware (por ejemplo: puerta-padel-1). Puedes escribir el valor que quieras — el mismo que pondrás en DOOR_ID del script.

Referencia de la API

Endpoints que debe configurar en tu hardware
GET
/api/ext/v1/courts/{id}/status
Consulta si el calendario está ocupado en este momento. Úsalo para encender/apagar la iluminación. Llamar cada 60 segundos desde la Raspberry Pi.
POST
/api/ext/v1/access/validate
Valida el PIN o QR que introduce el usuario en el lector de la puerta. Úsalo para abrir o denegar el acceso. Llamar cuando el usuario presenta un código.
Cabecera requerida en ambos: X-Ext-Api-Key: <tu-clave> · Base URL: https://www.apoing.com/api/ext/v1/

El código PIN lo genera Apoing automáticamente cuando el usuario pulsa Abrir puerta en la app o web — tu hardware no necesita solicitarlo ni conocer ese flujo interno.

Endpoint 1 — Estado del calendario (iluminación)

Consulta si hay una reserva activa ahora mismo. Lo llama tu Raspberry Pi periódicamente para decidir si encender o apagar la luz.

GET /api/ext/v1/courts/{id}/status
X-Ext-Api-Key: apx-xxxxxxxxxxxxxxxx

Sustituye {id} por el ID de calendario. Puedes consultarlo en Gestión de la comunidad → Editar calendario → bloque "API de integración", donde aparece el ID como valor de solo lectura.

Respuesta:

{
  "courtId": 42,
  "occupied": true,
  "until": "19:00",
  "nextAt": null
}
  • occupied: true si hay una reserva en curso ahora mismo.
  • until: hora de fin de la reserva activa (null si no hay ninguna).
  • nextAt: hora de inicio de la próxima reserva de hoy (null si no hay más).

Endpoint 2 — Validar token de acceso (puerta)

Lo llama tu hardware cuando el usuario introduce el PIN en el teclado o escanea el QR. Verifica que el código es válido para esa puerta y lo marca como usado.

POST /api/ext/v1/access/validate
X-Ext-Api-Key: apx-xxxxxxxxxxxxxxxx
Content-Type: application/json

{
  "token": "384719",
  "doorId": "puerta-padel-1"
}

El valor de doorId debe coincidir exactamente con el ID de puerta configurado en ese calendario (Gestión de la comunidad → Editar calendario → bloque "API de integración"). Puedes asignarlo tú libremente — por ejemplo puerta-padel-1.

Respuesta válida → abre la puerta:

{
  "valid": true,
  "courtName": "Pista de pádel 1",
  "until": "19:15"
}

Respuesta inválida → denegar acceso:

{
  "valid": false,
  "error": "Token inválido o expirado"
}

Cada token solo puede usarse una vez. Apoing registra el intento (válido o fallido) en el historial de acceso.

Configuración del hardware de iluminación

Elige la opción que mejor se adapte a tu presupuesto y nivel técnico. En todos los casos, necesitarás una Raspberry Pi (o cualquier dispositivo Linux con Python) que consulte la API de Apoing y envíe órdenes al relé por la red local.


Opción A — Shelly Recomendado

La opción más sencilla: los Shelly exponen una API REST local sin firmware adicional. Solo hay que conectarlos a la WiFi de la instalación.

Modelos recomendados:

  • Shelly Plus 1 — 1 canal, ideal para focos o puertas eléctricas sencillas.
  • Shelly Plus 1PM — igual que el anterior, con medición de consumo eléctrico.
  • Shelly 2PM — 2 canales, ideal para motores de puertas de garaje o persianas.

¿Dónde se ejecuta este script? En la Raspberry Pi (o cualquier ordenador con Python en la misma red). El Shelly no ejecuta Python — simplemente recibe órdenes HTTP desde la Raspberry Pi. El flujo es: Raspberry Pi → consulta Apoing API → envía orden ON/OFF al Shelly por red local.

Script Python para Shelly (guardar en la Raspberry Pi como apoing-lights.py):

#!/usr/bin/env python3
# apoing-lights.py — Control automático de iluminación con Shelly
# Requiere: pip install requests

import time, requests

APOING_API  = "https://www.apoing.com/api/ext/v1"
EXT_API_KEY = "apx-TU_CLAVE_AQUI"
COURT_ID    = 42              # ID del calendario en Apoing
SHELLY_IP   = "192.168.1.50"  # IP local del dispositivo Shelly
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"[luz] 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)

Opción B — Sonoff + Tasmota Más barato, más técnico

Los dispositivos Sonoff son más económicos que los Shelly, pero requieren instalar el firmware Tasmota mediante flasheo USB (proceso documentado en tasmota.github.io). Una vez instalado, ofrecen una API HTTP compatible.

Modelos recomendados:

  • Sonoff Basic R2 — 1 canal, el más económico para focos o tomas.
  • Sonoff S31 — enchufe con medición de consumo.

Script Python para Sonoff + Tasmota:

#!/usr/bin/env python3
# apoing-lights.py — Control automático de iluminación con Sonoff/Tasmota
# Requiere: pip install requests

import time, requests

APOING_API  = "https://www.apoing.com/api/ext/v1"
EXT_API_KEY = "apx-TU_CLAVE_AQUI"
COURT_ID    = 42               # ID del calendario en Apoing
SONOFF_IP   = "192.168.1.51"   # IP local del dispositivo Sonoff
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"[luz] 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)

⚠️ Asegúrate de que Tasmota está configurado en modo Wifi AP desactivado y que la IP del dispositivo sea fija (DHCP estático o IP reservada en el router).


Opción C — Otro hardware compatible HTTP

Cualquier relé o controlador que acepte comandos HTTP locales funciona con Apoing. Solo hay que adaptar la función set_relay() del script. El resto del código (consulta a la API de Apoing) es idéntico en las tres opciones.

El script base es:

def set_relay(on: bool):
    # Sustituye esta llamada por la de tu hardware
    # Ejemplo genérico: POST a un endpoint local
    action = "on" if on else "off"
    requests.post(f"http://TU_DISPOSITIVO/api", json={"relay": action}, timeout=5)

Configuración como servicio systemd (válida para las tres opciones):

Crea /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

Hardware para control de puerta

A diferencia de la iluminación (donde el Shelly recibe órdenes del Raspberry Pi), la puerta necesita un dispositivo que lea el PIN o QR, llame a la API de Apoing para validarlo y active el relé de la cerradura eléctrica. Hay tres formas de conseguirlo.


Opción A — Raspberry Pi + teclado matricial Más completo

La opción más cómoda si ya tienes Raspberry Pi para las luces: añades un teclado matricial 4×4 (2–5 €) y un relé. La Pi lee el PIN, llama a Apoing y abre la puerta. También puede leer lectores QR conectados por USB como si fueran un teclado.

Script Python (guardar en la Raspberry Pi como apoing-door.py):

#!/usr/bin/env python3
# apoing-door.py — Validación de PIN de acceso para puerta
# Requiere: pip install requests

import requests, time

APOING_API  = "https://www.apoing.com/api/ext/v1"
EXT_API_KEY = "apx-TU_CLAVE_AQUI"
DOOR_ID     = "puerta-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("[puerta] Abierta")

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("Sistema de acceso Apoing listo. Introduce PIN:")
    while True:
        pin = input("> ").strip()
        if len(pin) == 6 and pin.isdigit():
            if validate_pin(pin):
                print("✓ Acceso concedido")
                open_door()
            else:
                print("✗ PIN inválido o expirado")
        else:
            print("PIN de 6 dígitos requerido")

Opción B — ESP32 + teclado o lector QR Wiegand Más económico

Si solo necesitas control de puerta (sin iluminación), el ESP32 (~5 €) es suficiente: tiene WiFi integrado, puede leer un teclado matricial directamente o recibir la salida Wiegand de lectores QR genéricos (los que encuentras en AliExpress/Amazon sin conectividad IP propia). No necesita sistema operativo.

Sketch Arduino para ESP32 (teclado matricial 4×4):

// apoing-door.ino — ESP32 + teclado 4x4 + relé
// Librerías: Keypad, HTTPClient (incluidas en ESP32 Arduino core)

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Keypad.h>

const char* SSID       = "TuWiFi";
const char* PASS       = "TuPassword";
const char* API_URL    = "https://www.apoing.com/api/ext/v1/access/validate";
const char* API_KEY    = "apx-TU_CLAVE_AQUI";
const char* DOOR_ID    = "puerta-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;
    }
}

⚠️ Los lectores QR con salida Wiegand requieren una librería adicional (Wiegand para ESP32) para leer el dato Wiegand y convertirlo al PIN antes de llamar a la API.


Opción C — Lector QR con cliente HTTP nativo Sin intermediario

Algunos lectores QR de control de acceso tienen conectividad IP y pueden llamar directamente a una URL cuando leen un código, sin necesitar Raspberry Pi ni ESP32. Antes de comprar, verifica que el dispositivo soporte:

  • Llamadas HTTP POST a URL externa configurable
  • Cuerpo en formato JSON
  • Cabecera HTTP personalizada (X-Ext-Api-Key)

Si el lector solo soporta peticiones GET (sin cuerpo JSON), no es compatible directamente con la API de Apoing y necesitará un ESP32 de intermediario.

Los lectores con gestión propia (como Kimaldi Kapri y similares) suelen trabajar con una lista de accesos programada en el dispositivo, no con validación en tiempo real contra una API externa. Para integrarlos con Apoing debes comprobar en su manual si soportan webhook o HTTP event con POST+JSON a URL externa con cabeceras personalizadas.

Historial de accesos

Todos los intentos de validación (válidos y fallidos) quedan registrados en el panel de administración de la comunidad (Gestión de la comunidad → Integración (API) → Historial de accesos). Puedes ver la hora, si el acceso fue concedido y la IP del dispositivo que lo solicitó.

Preguntas frecuentes

¿La API tiene coste adicional?

No. La API de control de acceso es gratuita para todas las comunidades de Apoing, sin límite de llamadas.

¿Puedo tener varias puertas o varios calendarios?

Sí. Cada calendario tiene su propio door_id configurable. Puedes tener tantas puertas como calendarios tenga tu comunidad, todas gestionadas con la misma clave API.

¿El PIN caduca si no se usa?

Sí. El PIN es válido hasta 15 minutos después de que termine la reserva. Además, es de un solo uso: una vez validado correctamente, queda invalidado aunque no haya caducado.

¿Funciona con hardware diferente al listado?

Sí. Cualquier dispositivo que pueda hacer llamadas HTTP (GET/POST) puede integrarse. La API es estándar REST con respuestas JSON. Los scripts de esta página son ejemplos; puedes adaptarlos a tu hardware.

¿La Raspberry Pi necesita estar conectada a Internet?

Sí, necesita acceso a internet para consultar la API de Apoing. Los dispositivos Shelly/Sonoff solo necesitan estar en la misma red local que la Raspberry Pi; no requieren acceso a internet.