Skip to main content

Fingerspot Hub API — Agent Skills

A set of Agent Skills that teach AI coding agents (Claude Code, OpenCode, Codex, and other MCP-compatible tools) how to integrate with the Hub API. Each skill packages a complete, self-contained reference for one part of the API — endpoints, request/response shapes, supported device brands, and edge cases — so your agent can generate correct integration code without guessing.

# Install all skills
npx skills add royfingerspot/hub-api-skills --all

# Install a specific skill
npx skills add royfingerspot/hub-api-skills --skill hub-api-getting-started

# Preview available skills before installing
npx skills add royfingerspot/hub-api-skills --list

In Claude Code, every installed skill is also available as a slash command (e.g. /hub-api-webhook), so you can invoke it directly instead of waiting for auto-detection. Other agents (Codex, OpenCode, etc.) use the skill through normal auto-detection.

Source of truth: openapi.yaml in the skills repo is the single source of truth for every endpoint, schema, and auth requirement. Always confirm brand support against the spec's x-supported-brands field before calling an endpoint.


Skills Reference

hub-api-getting-started — Start here

Core integration patterns shared across every other skill: authentication, the async command flow, webhook handling, and error codes.

Authentication

Authorization: Bearer <your-api-token>

The same token is used both for API authentication and for signing/verifying webhook HMAC signatures.

Async command pattern

Most endpoints are asynchronous — they acknowledge immediately and deliver the actual result later via webhook.

  1. Send a request → server responds 200 with an ack:
    {
    "referenceId": "your-tracking-id",
    "status": "queued",
    "deviceStatus": "connected",
    "message": "command queued successfully",
    "webhookUrls": ["https://your-webhook.com/callback"]
    }
  2. Server POSTs the result to your webhook URL once the device responds:
    {
    "referenceId": "your-tracking-id",
    "status": "success",
    "commandType": "setUser",
    "data": { }
    }
  3. Correlate the two using referenceId.

A handful of endpoints are synchronous and return data directly — no webhook involved:

  • GET /v1/{cloud_id} — device info
  • GET /v1/{cloud_id}/activity — device connectivity status
  • GET /v1/{cloud_id}/attendance — attendance logs
  • PUT /v1/{cloud_id}/webhook — set webhook URL

Required headers

HeaderRequiredDescription
AuthorizationYesBearer <token>
X-Reference-IDYes (async)Your tracking ID, echoed back in the webhook callback
X-Webhook-URLNoComma-separated extra webhook URLs (max 3)
Content-TypeYes (POST/PUT)application/json

Error codes

CodeMeaning
ERR_DEVICE_NOT_FOUNDDevice not found in registry
ERR_DEVICE_OFFLINEDevice not connected
ERR_DEVICE_TIMEOUTCommand timeout (default 30 min)
ERR_DEVICE_BUSYDevice processing another command
ERR_INVALID_PARAMSInvalid request body or query params
ERR_DATE_RANGEInvalid date range
ERR_UNSUPPORTEDDevice doesn't support this feature
ERR_INTERNALUnexpected server error
ERR_UNAUTHORIZEDInvalid or missing token

If a command is sent to an offline device, the request still returns 200 with deviceStatus: "offline" — the command queues and executes once the device reconnects.

Supported brands across the API: vivo, vida, revo, vega, zkteco.

hub-api-users — User management

Get, create, update, and delete users on a device. All endpoints are async.

Headers: Authorization (yes), X-Reference-ID (yes), X-Webhook-URL (no)

EndpointMethodSupported brands
/v1/{cloud_id}/usersGET — all usersvida, vivo
/v1/{cloud_id}/user-idsGET — lightweight ID/PIN listrevo, vega, vivo, vida, zkteco
/v1/{cloud_id}/users/{id}GET — single user with credentialsvivo, vida, zkteco
/v1/{cloud_id}/usersPOST — create/update uservivo, vida, revo, vega, zkteco
/v1/{cloud_id}/users/{id}DELETEvida, vivo, zkteco

Create/update request body:

{
"employeeNo": "101",
"name": "John",
"userType": "admin",
"password": "123456",
"fingerprints": [{ "fingerNo": 1, "fingerData": "base64..." }],
"cards": [{ "cardNo": "12345678" }]
}

userType accepts normal, admin, visitor on most brands — Revo additionally supports user, operator, manager, supervisor.

Note: getAllUsers only works on vida and vivo. For other brands, use user-ids instead.

hub-api-credentials — Fingerprint, face, card, password, QR code

Manage biometric and non-biometric credentials. All endpoints are async.

Headers: Authorization (yes), X-Reference-ID (yes), X-Webhook-URL (no)

Fingerprint

EndpointMethodNotes
/v1/{cloud_id}/users/{id}/fingerGETAll fingerprints for a user
/v1/{cloud_id}/users/{id}/finger/{fingerNo}GETOne fingerprint (slot 1–10)
/v1/{cloud_id}/users/{id}/fingerPUTReplace all fingerprints (bulk)
/v1/{cloud_id}/users/{id}/finger/{fingerNo}PUTSet one fingerprint
/v1/{cloud_id}/users/{id}/fingerDELETERemove all
/v1/{cloud_id}/users/{id}/finger/{fingerNo}DELETERemove one

Face

EndpointMethod
/v1/{cloud_id}/users/{id}/faceGET / PUT / DELETE

Supported brands: vida, vivo.

Card

EndpointMethod
/v1/{cloud_id}/users/{id}/cardGET
/v1/{cloud_id}/users/{id}/cardPUT — body: { "cardNo": "...", "cardType": "normalCard" }
/v1/{cloud_id}/users/{id}/card/{cardNo}DELETE

cardType defaults to normalCard; hijackCard is also supported.

Password

GET / PUT /v1/{cloud_id}/users/{id}/password — body: { "password": "newpassword123" }

QR Code

EndpointMethod
/v1/{cloud_id}/qrcodeGET — all QR codes on device
/v1/{cloud_id}/qrcode/{recno}GET / DELETE
/v1/{cloud_id}/users/{id}/qrcodeGET / POST / PUT / DELETE

Fingerprint templates and face data are base64-encoded; format for face data varies by brand.

hub-api-attendance — Attendance log queries

The only fully synchronous skill — returns data directly, no webhook.

Headers: Authorization (yes), X-Reference-ID (no, but recommended for tracking)

Endpoint

GET /v1/{cloud_id}/attendance

Query params

ParamTypeRequiredNotes
startDatestringyesYYYY-MM-DD
endDatestringyesYYYY-MM-DD
pageintnoDefault 1
limitintnoDefault 100, max 100

Supported brands: vivo, vida, revo, vega, zkteco.

Response

{
"records": [
{
"employeeNo": "101",
"name": "John",
"cardNo": "12345678",
"eventTime": "2026-06-15T08:00:00+07:00",
"verifyMode": "fingerprint",
"attendanceStatus": "check_in",
"userType": "admin",
"serialNumber": "ABC123",
"cloudId": "GQ5778408"
}
],
"total": 50,
"page": 1,
"limit": 100
}

verifyMode: fingerprint, password, card, face, vein. attendanceStatus: check_in, check_out.

hub-api-device — Device info, time, reboot, timezone, capture

A mix of sync and async endpoints — check each one individually.

Headers: Authorization (yes), X-Reference-ID (yes, async endpoints), X-Webhook-URL (no)

Device info & status (sync)

EndpointReturns
GET /v1/{cloud_id}brand, serial, capabilities
GET /v1/{cloud_id}/activityconnectivity status, last-seen timestamp

Time

PUT /v1/{cloud_id}/time — body: { "dateTime": "...", "timezone": "Asia/Jakarta", "mode": "ntp" }. At least one field required. Timezone accepts IANA names or ISO 8601 offsets.

Reboot

POST /v1/{cloud_id}/reboot — no body. Vida only.

Timezone

GET / PUT /v1/{cloud_id}/timezone, plus PUT /v1/{cloud_id}/timezone/lock to lock/unlock physical-device changes. Supported: vivo, vida, zkteco.

Valid date

GET / PUT /v1/{cloud_id}/users/{id}/valid — sets a user's active period:

{
"employeeNo": "101",
"beginDate": "2026-01-01",
"endDate": "2026-12-31",
"weekTimeZone": [1, 1, 1, 1, 1, 1, 1]
}

weekTimeZone is 7 values (Mon–Sun), 1 = active, 0 = inactive.

Live capture & enrollment

EndpointPurposeSupported brands
POST /v1/{cloud_id}/capture/fingerTrigger live fingerprint capturevivo, vida, revo, vega, zkteco
POST /v1/{cloud_id}/capture/cardTrigger live card capturevivo, vida, revo, vega, zkteco
POST /v1/{cloud_id}/enroll/fingerDevice-initiated finger enrollmentrevo, vega
POST /v1/{cloud_id}/enroll/faceDevice-initiated face enrollmentrevo, vega
POST /v1/{cloud_id}/enroll/veinDevice-initiated vein enrollmentrevo, vega

Enrollment commands put the device into enrollment mode — the user must physically interact with the device afterward.

hub-api-door — Door open/close/status

Async endpoints, currently Vida-only.

Headers: Authorization (yes), X-Reference-ID (yes), X-Webhook-URL (no)

EndpointMethod
/v1/{cloud_id}/door/openPOST — body optional: { "channel": 1 }
/v1/{cloud_id}/door/closePOST — body optional: { "channel": 1 }
/v1/{cloud_id}/door/statusGET

channel defaults to 1 and is only relevant for devices with multiple door controllers. Door open/close/alarm events are also pushed as realtime webhook events (see hub-api-webhook).

hub-api-webhook — Configuration, signature verification, realtime events

Configuration

PUT /v1/{cloud_id}/webhook (sync) — sets the device's default webhook URL:

{ "url": "https://your-server.com/webhook" }

POST /v1/{cloud_id}/webhook/test (sync) — checks whether the configured URL is reachable.

Webhook URL resolution order: X-Webhook-URL header → per-device default → V2_DEFAULT_WEBHOOK_URL env var fallback.

Signature verification

Every webhook includes HMAC-SHA256 signatures — always verify before trusting a callback.

HeaderDescription
X-Hub-API-TimestampUnix timestamp (seconds, UTC)
X-Hub-API-Signature-256sha256=<hex-digest>

Steps: reject if now - timestamp > 300s → compute HMAC-SHA256(body, api_token) → compare using constant-time comparison → accept only on match.

const crypto = require('crypto');

function verifyWebhook(body, timestamp, signature, secret) {
const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (age > 300) return false;

const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

Official SDKs (PHP) expose a ready-made verifier: new WebhookVerifier($apiToken)->verify($body, $_SERVER, 300).

Retry behavior: failed deliveries retry with exponential backoff (1s, 2s, 4s, 8s, 16s across 5 attempts), then stop — no dead-letter queue.

Realtime events

Device-initiated pushes, independent of any command you sent:

EventDescription
attendanceUser scanned (check-in/out)
verificationFailedVerification attempt rejected
doorEventDoor state changed
doorbellDoorbell pressed
tamperPhysical tamper detected
alarmDevice alarm triggered
exceptionDevice exception
operationDevice operation event
userChangeUser data changed directly on device
deviceActivityConnectivity status changed (online/offline)

Every event payload includes event and cloudId for routing, and is signed the same way as command-result webhooks.


Next Steps