Teqlogy API / TeqAccess

TeqAccess

Door access for schools, offices and workshops. Manage people, cards, PINs and fingerprints, lock and unlock doors, read the event history and get every tap live — from your own software. The same API runs in the Teqlogy cloud and on a TeqAccess Server on a PC in your building, with no internet.

Base URL (cloud)https://teqhome.teqlogy.com/api/access
Any key a read-only key and a full key may call it Full key only a full key only35 calls · API v1

Where it runs

One API, three places. The calls and the answers are the same in all of them; only the address and the keys change. A reader is connected to one server at a time — the Teqlogy cloud or a TeqAccess Server in your building — and you talk to that server.

Base addressWhat it is
Cloudhttps://teqhome.teqlogy.com/api/accessReaders connected to the Teqlogy cloud. Make a key in the dashboard: API Keys.
School server (no internet)http://teqaccess-<name>.local:36500/api/accessA TeqAccess Server on a PC in your building (the address is on the Server page of the Manager). Make a key there: Server → API for your own software. A key works only on the server that made it.
The reader itselfhttp://<reader ip>/api/v1One reader on your network, with its own key, even with no server at all. Eight calls: see "Talking to a reader".

Quick start

1
Make a key
Dashboard → API Keys → Generate Key (on a school server: the Manager → Server). Copy it: it is shown once.
2
List your doors
One request shows every door of your site, with its settings and whether it is online.
3
Open one
POST to the door's open call. The reader unlocks for its normal unlock time.
bash
export KEY=tk_live_…

curl "https://teqhome.teqlogy.com/api/access/doors" \
  -H "Authorization: Bearer $KEY"
json · the answer (shortened)
{
  "ok": true,
  "doors": [
    {
      "deviceId": "a1b2c3d4e5f6",
      "name": "Main gate",
      "override": 0,
      "levelMask": 0,
      "device": { "status": true, "firmware": "0.5.1" }
    }
  ]
}
bash · open the door
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/open" \
  -H "Authorization: Bearer $KEY"

Authentication

Every call carries a personal API key in the Authorization header, exactly as for the other Teqlogy devices: Authorization: Bearer tk_live_…. Keys are made in the dashboard (API Keys), or on a school server in the Manager. A key made on one server works only on that server. On the cloud, API access is switched on per account: Get an API key says how.

Read-only key

Reads everything: the site, people, doors, events, attendance, webhooks, and listens to the live stream. Every other call answers 403 API_KEY_READONLY.

Full key

Also changes things: people, credentials, door settings, opening, locking, webhooks. It acts on your own site only.

Keys never manage keys. Creating or revoking an API key, and everything else of your account, needs a login. The general rules (rate limits, numbering, errors) are on the Teqlogy API page.

People, levels and doors

Cards, PINs and fingerprints

A person can have any number of them. The doors treat them the same: levels, schedules, validity, blocking, lockdown and the event log all apply.

kindYou sendId in listsRules
card{ "uid": "04A1B2C3" }the card number, e.g. 04A1B2C3Hexadecimal, 4 to 10 bytes. A card belongs to one person (CARD_IN_USE).
pin{ "kind": "pin", "pin": "482910" }PIN-<12 hex> (a salted hash)4 to 8 digits, typed on the reader's keypad. Never stored and never shown again: to change one, delete it and add a new one. Too easy (1111, 1234) is refused. Five wrong tries make the reader wait one minute.
finger{ "kind": "finger", "fingerId": 7 }FP-7The number the finger was enrolled under on the reader's fingerprint sensor, 1 to 65535.
A person
seq
integerThe number of the person in your site. Use it in /people/{seq}.
name
stringFull name.
code
stringYour own number (roll number, staff id).
dept
stringClass or department.
phone
stringPhone number.
groups
integerThe levels the person has, as a bit mask: bit i = level i. 0 = no level: no schedule limits the person, but a door limited to some levels refuses them (see Levels).
validFrom / validTo
stringFirst and last day the person may enter, YYYY-MM-DD. Empty = no limit.
status
string"active" or "blocked". A blocked person is refused everywhere (reason blocked).
master
booleanA master passes every level, schedule and holiday, but not a lockdown.
cards[]
object[]In GET /people: { uid, kind: "card" | "pin" | "finger", label, status } for every credential of the person. uid is the id to use in DELETE /people/{seq}/cards/{uid}.

Levels: who may enter which door

A site has up to 16 levels (PUT /site → groups). A level has a name and a time zone — a weekly schedule — or “always”. A person has levels (groups, a bit mask: bit i = level i). A door can be limited to some levels (levelMask; 0 = no limit).

  • A door without a levelMask lets in every active person whose own schedule allows it.
  • A door with a levelMask refuses a person who has none of its levels (reason level), also a person without any level. A master always passes.
  • Of a person who has a level of the door, only that level's schedule counts at that door: a “Lab 08:00-12:00” level does not let them in at 14:00, even if another level of theirs is always open elsewhere.
  • The reader gets every change at once and keeps it through power cuts and time offline.

Opening, locking, unlocking

CallThe doorCardsExit button
openUnlocks once, for the normal unlock time.As normal.Works.
lockLocked down.All refused, also masters (reason lockdown).Still works: people can always leave.
unlockHeld unlocked.As normal.Works.
releaseBack to normal.As normal.Works.
  • minutes (1 to 1440) makes lock and unlock timed. The reader counts it down itself, so it ends on time even when the network is gone.
  • A lockdown survives a power cut. A held-unlocked door falls back to normal when the reader restarts (fail secure). A fire input always releases the door.
  • open still works during a lockdown: somebody with an API key did it on purpose. POST /site/lockdown, /site/unlock and /site/release do the same to every door at once.
A door
deviceId
stringThe id of the reader: use it in /doors/{deviceId}.
name
stringDoor name.
mode
integer0 = the reader decides (standalone), 1 = reader only (Wiegand out to a controller), 2 = both.
pulseMs / heldSec
integerHow long the lock is released (ms), and how long the door may stay open before door.held_open (seconds).
doorSensor, doorNC, fire, tamper, relayActiveLow
booleanWhat is wired to the reader and how.
wgBits, wgFacility, wgIn
integer / booleanWiegand settings: output format (0, 26 or 34 bits), facility code, a second reader on the IN wires.
levelMask
integerWhich levels may enter this door (bit i = level i). 0 = no limit.
direction
string"in", "out" or "" — the way people pass this reader, for anti-passback.
override
integer0 normal, 1 held unlocked, 2 locked down.
device
objectWhether the reader is connected: { deviceId, name, status, firmware, rssi, lastSeen }. status true = online.
lastStatus
objectThe last heartbeat of the reader: { rssi, up, ip, door, fire, tamper, relay, cards, heap, ver, at }. Absent until the reader has reported.

Endpoints

35 calls. All of them take the base address of the server you use (https://teqhome.teqlogy.com/api/access on the cloud) and your key in the Authorization header. Answers are JSON with ok; errors carry a code.

Meta
GET/meta Any key
GET/openapi.json Any key
Site
GET/site Any key
PUT/site Full key only
POST/site/lockdown Full key only
POST/site/unlock Full key only
POST/site/release Full key only
People and credentials
GET/people Any key
POST/people Full key only
POST/people/import Full key only
PATCH/people/{seq} Full key only
DELETE/people/{seq} Full key only
POST/people/{seq}/cards Full key only
DELETE/people/{seq}/cards/{uid} Full key only
Doors
GET/doors Any key
GET/doors/{deviceId} Any key
PATCH/doors/{deviceId} Full key only
POST/doors/{deviceId}/open Full key only
POST/doors/{deviceId}/lock Full key only
POST/doors/{deviceId}/unlock Full key only
POST/doors/{deviceId}/release Full key only
GET/doors/{deviceId}/local-key Full key only
POST/doors/{deviceId}/local-key/rotate Full key only
POST/doors/{deviceId}/reboot Full key only
POST/doors/{deviceId}/enroll Full key only
Events and attendance
GET/events Any key
GET/events/stream Any key
GET/unknown-cards Any key
GET/attendance Any key
Webhooks
GET/webhooks Any key
POST/webhooks Full key only
PATCH/webhooks/{id} Full key only
POST/webhooks/{id}/rotate Full key only
POST/webhooks/{id}/test Full key only
DELETE/webhooks/{id} Full key only

Meta

What the API knows about itself.

GET/api/access/meta Any key

Event types, deny reasons, overrides and limits

Request
bash
curl "https://teqhome.teqlogy.com/api/access/meta" \
  -H "Authorization: Bearer $KEY"
Response 200what an integrator needs to know before writing code
json
{
  "ok": true,
  "apiVersion": 1,
  "events": [
    "access.granted",
    "access.denied",
    "access.exit_button",
    "access.remote_open",
    "access.enrolled",
    "door.forced",
    "door.held_open",
    "door.closed",
    "door.tamper",
    "door.fire_on",
    "door.fire_off",
    "door.boot",
    "door.online",
    "door.offline",
    "door.override",
    "access.apb_violation",
    "webhook.test"
  ],
  "deviceEvents": {
    "1": "boot",
    "2": "granted",
    "3": "denied",
    "4": "exit_button",
    "5": "door_forced",
    "6": "door_held",
    "7": "door_closed",
    "8": "tamper",
    "9": "fire_on",
    "10": "fire_off",
    "11": "remote_open",
    "12": "enrolled"
  },
  "denyReasons": {
    "1": "unknown",
    "2": "blocked",
    "3": "expired",
    "4": "not_yet",
    "5": "schedule",
    "6": "lockdown",
    "7": "level",
    "8": "too_many"
  },
  "overrides": { "0": "normal", "1": "unlocked", "2": "locked" },
  "limits": {
    "people": 3400,
    "webhooks": 10,
    "importRows": 500,
    "attendanceDays": 62,
    "overrideMinutes": 1440
  }
}
GET/api/access/openapi.json Any key

This description

  • The whole API as an OpenAPI 3.0 description. Import it into Postman, Insomnia or a code generator.
Request
bash
curl "https://teqhome.teqlogy.com/api/access/openapi.json" \
  -H "Authorization: Bearer $KEY"
Response 200shortened
json
{
  "openapi": "3.0.3",
  "info": { "title": "TeqAccess API", "version": "1" },
  "servers": [{ "url": "/api/access" }],
  "paths": { "/people": "…", "/doors/{deviceId}/open": "…" }
}

Site

Your site: its name, clock, holidays, time zones and levels, and one switch for every door.

GET/api/access/site Any key

The site, its schedules and its doors

Request
bash
curl "https://teqhome.teqlogy.com/api/access/site" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "site": {
    "id": "6ae7857a357c0f83cdb060bd",
    "name": "Green School",
    "tzOffsetMin": 300,
    "apb": "off",
    "dbVersion": 2,
    "holidays": ["2026-12-25"],
    "timeZones": [
      {
        "index": 0,
        "name": "School hours",
        "holidayAllowed": false,
        "days": [
          [],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          []
        ]
      }
    ],
    "groups": [
      { "index": 0, "name": "Staff", "tzIndex": -1 },
      { "index": 1, "name": "Students", "tzIndex": 0 }
    ]
  },
  "doors": [
    {
      "deviceId": "a1b2c3d4e5f6",
      "name": "Main gate",
      "mode": 0,
      "pulseMs": 3000,
      "heldSec": 30,
      "doorSensor": false,
      "doorNC": true,
      "fire": false,
      "tamper": false,
      "relayActiveLow": true,
      "wgBits": 0,
      "wgFacility": 0,
      "wgIn": false,
      "levelMask": 0,
      "direction": "",
      "override": 0,
      "ackedVersion": 2,
      "lastHelloAt": "2026-11-01T18:31:22.854Z",
      "lastStatus": {
        "rssi": -52,
        "up": 100,
        "ip": "192.168.18.51",
        "door": 0,
        "fire": 0,
        "tamper": 0,
        "relay": 0,
        "cards": 5,
        "heap": 100000,
        "ver": 2,
        "at": "2026-11-01T18:33:58.106Z"
      },
      "device": {
        "deviceId": "a1b2c3d4e5f6",
        "name": "Main gate",
        "status": true,
        "firmware": "0.5.1",
        "rssi": -52,
        "lastSeen": "2026-11-01T18:31:22.551Z"
      }
    }
  ]
}
PUT/api/access/site Full key only

Change name, time zone offset, holidays, time zones (schedules), groups (levels) and anti-passback (apb: off or soft); sent to every door

Request body (JSON)
name
stringName of the site, up to 80 characters.
tzOffsetMin
integerMinutes east of UTC for the clock of the site, -720 to 840 (Pakistan: 300).
holidays
string[]Dates that count as holidays, YYYY-MM-DD.
timeZones
object[]The weekly schedules: [{ index 0-15, name, days }]. Replaces the whole list.
groups
object[]The levels: [{ index 0-15, name, tzIndex }]. Replaces the whole list.
apb
stringAnti-passback: "off" (default) or "soft" (only reported).
  • Send only what you change. timeZones and groups replace the whole list; leave them out to keep them.
  • A time zone is { index 0-15, name, days }. days has 7 entries, Sunday first; each entry is a list of up to 2 intervals [startMinute, endMinute] between 0 and 1440 (480 = 08:00).
  • A level (group) is { index 0-15, name, tzIndex }. tzIndex is the index of a time zone, or -1 for "always".
Request
bash
curl -X PUT "https://teqhome.teqlogy.com/api/access/site" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Green School",
    "holidays": ["2026-12-25"],
    "timeZones": [
      {
        "index": 0,
        "name": "School hours",
        "days": [
          [],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          []
        ]
      }
    ],
    "groups": [
      { "index": 0, "name": "Staff", "tzIndex": -1 },
      { "index": 1, "name": "Students", "tzIndex": 0 }
    ]
  }'
Response 200the same as GET /site; every door has the new schedules
json
{
  "ok": true,
  "site": {
    "id": "6ae7857a357c0f83cdb060bd",
    "name": "Green School",
    "tzOffsetMin": 300,
    "apb": "off",
    "dbVersion": 2,
    "holidays": ["2026-12-25"],
    "timeZones": [
      {
        "index": 0,
        "name": "School hours",
        "holidayAllowed": false,
        "days": [
          [],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          [
            [480, 900]
          ],
          []
        ]
      }
    ],
    "groups": [
      { "index": 0, "name": "Staff", "tzIndex": -1 },
      { "index": 1, "name": "Students", "tzIndex": 0 }
    ]
  },
  "doors": [
    {
      "deviceId": "a1b2c3d4e5f6",
      "name": "Main gate",
      "mode": 0,
      "pulseMs": 3000,
      "heldSec": 30,
      "doorSensor": false,
      "doorNC": true,
      "fire": false,
      "tamper": false,
      "relayActiveLow": true,
      "wgBits": 0,
      "wgFacility": 0,
      "wgIn": false,
      "levelMask": 0,
      "direction": "",
      "override": 0,
      "ackedVersion": 2,
      "lastHelloAt": "2026-11-01T18:31:22.854Z",
      "lastStatus": {
        "rssi": -52,
        "up": 100,
        "ip": "192.168.18.51",
        "door": 0,
        "fire": 0,
        "tamper": 0,
        "relay": 0,
        "cards": 5,
        "heap": 100000,
        "ver": 2,
        "at": "2026-11-01T18:33:58.106Z"
      },
      "device": {
        "deviceId": "a1b2c3d4e5f6",
        "name": "Main gate",
        "status": true,
        "firmware": "0.5.1",
        "rssi": -52,
        "lastSeen": "2026-11-01T18:31:22.551Z"
      }
    }
  ]
}
POST/api/access/site/lockdown Full key only

Lock down every door of the site

Request body (JSON)
minutes
integerOptional, 1 to 1440. The reader counts it down itself.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/site/lockdown" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"minutes":15}'
Response 200
json
{
  "ok": true,
  "doors": [{ "deviceId": "a1b2c3d4e5f6", "sent": true }],
  "override": "locked"
}
POST/api/access/site/unlock Full key only

Hold every door unlocked

Request body (JSON)
minutes
integerOptional, 1 to 1440. The reader counts it down itself.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/site/unlock" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"minutes":15}'
Response 200
json
{
  "ok": true,
  "doors": [{ "deviceId": "a1b2c3d4e5f6", "sent": true }],
  "override": "unlocked"
}
POST/api/access/site/release Full key only

Every door back to normal

Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/site/release" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "doors": [{ "deviceId": "a1b2c3d4e5f6", "sent": true }],
  "override": "normal"
}

People and credentials

The people who may enter, and the cards, PINs and fingerprints that identify them.

GET/api/access/people Any key

List / search people (q, dept, limit, skip)

  • q searches name, code and class. Other filters: dept, limit, skip.
Request
bash
curl "https://teqhome.teqlogy.com/api/access/people?q=ayesha&limit=20" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "total": 1,
  "people": [
    {
      "seq": 1,
      "name": "Ayesha Khan",
      "code": "S-101",
      "dept": "Class 5",
      "phone": "0300 1234567",
      "groups": 2,
      "validFrom": "",
      "validTo": "",
      "status": "active",
      "master": false,
      "cards": [
        { "uid": "04A1B2C3", "kind": "card", "label": "", "status": "active" },
        {
          "uid": "PIN-596FF62324BA",
          "kind": "pin",
          "label": "",
          "status": "active"
        }
      ]
    }
  ]
}
POST/api/access/people Full key only

Create a person, optionally with a card (uid). The person is saved even if the card is refused: see cardError

Request body (JSON)
namerequired
stringFull name. Required.
code
stringYour own number for the person (roll number, staff id).
dept
stringClass or department.
phone
stringPhone number.
groups
integerThe levels of the person, as a bit mask: bit i = level i. 0 = no level.
validFrom
stringFirst day the person may enter, YYYY-MM-DD. Empty = no limit.
validTo
stringLast day the person may enter, YYYY-MM-DD. Empty = no limit.
status
string"active" or "blocked".
master
booleantrue = a master: passes every level, schedule and holiday (not a lockdown).
uid
stringCard number in hexadecimal, 4 to 10 bytes, e.g. 04A1B2C3.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/people" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ayesha Khan",
    "code": "S-101",
    "dept": "Class 5",
    "phone": "0300 1234567",
    "groups": 2,
    "uid": "04A1B2C3"
  }'
Response 201
json
{
  "ok": true,
  "person": {
    "seq": 1,
    "name": "Ayesha Khan",
    "code": "S-101",
    "dept": "Class 5",
    "phone": "0300 1234567",
    "groups": 2,
    "validFrom": "",
    "validTo": "",
    "status": "active",
    "master": false
  },
  "card": { "uid": "04A1B2C3", "kind": "card", "label": "", "status": "active" },
  "cardError": ""
}
201 · The card already belongs to somebody else: the person is saved, the card is not
json
{
  "ok": true,
  "person": {
    "seq": 2,
    "name": "Bilal Ahmed",
    "code": "S-102",
    "dept": "Class 5",
    "phone": "",
    "groups": 0,
    "validFrom": "",
    "validTo": "",
    "status": "active",
    "master": false
  },
  "card": null,
  "cardError": "CARD_IN_USE"
}
POST/api/access/people/import Full key only

Create up to 500 people at once (rows: name, code, dept, uid)

Request body (JSON)
rowsrequired
object[]Up to 500 rows, each { name, code, dept, uid } (only name is required).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/people/import" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {
        "name": "Omar Farooq",
        "code": "S-201",
        "dept": "Class 6",
        "uid": "04010203"
      },
      { "name": "Zainab Ali", "code": "S-202", "dept": "Class 6" }
    ]
  }'
Response 200
json
{
  "ok": true,
  "created": 2,
  "results": [
    { "ok": true, "seq": 4, "name": "Omar Farooq", "cardError": "" },
    { "ok": true, "seq": 5, "name": "Zainab Ali", "cardError": "" }
  ]
}
PATCH/api/access/people/{seq} Full key only

Change a person (block, groups, validity ...); the doors get it at once

Path parameters
seqrequired
integerThe seq of the person (from GET /people).
Request body (JSON)
name
stringFull name.
code
stringYour own number for the person (roll number, staff id).
dept
stringClass or department.
phone
stringPhone number.
groups
integerThe levels of the person, as a bit mask: bit i = level i. 0 = no level.
validFrom
stringFirst day the person may enter, YYYY-MM-DD. Empty = no limit.
validTo
stringLast day the person may enter, YYYY-MM-DD. Empty = no limit.
status
string"active" or "blocked".
master
booleantrue = a master: passes every level, schedule and holiday (not a lockdown).
  • Send only what changes. The doors get it at once.
Request
bash
curl -X PATCH "https://teqhome.teqlogy.com/api/access/people/1" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"blocked"}'
Response 200
json
{
  "ok": true,
  "person": {
    "seq": 1,
    "name": "Ayesha Khan",
    "code": "S-101",
    "dept": "Class 5",
    "phone": "0300 1234567",
    "groups": 2,
    "validFrom": "",
    "validTo": "",
    "status": "blocked",
    "master": false
  }
}
DELETE/api/access/people/{seq} Full key only

Delete a person and their cards

Path parameters
seqrequired
integerThe seq of the person (from GET /people).
Request
bash
curl -X DELETE "https://teqhome.teqlogy.com/api/access/people/1" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true }
POST/api/access/people/{seq}/cards Full key only

Give a card, a keypad PIN or a fingerprint id to a person. kind = card (uid = hex card number, the default), pin (pin = 4 to 8 digits; never stored, only a salted hash) or finger (fingerId = 1..65535)

Path parameters
seqrequired
integerThe seq of the person (from GET /people).
Request body (JSON)
kind
string"card" (default), "pin" or "finger".
uid
stringCard number in hexadecimal, 4 to 10 bytes, e.g. 04A1B2C3.
pin
stringFor kind "pin": 4 to 8 digits. Never stored or shown again.
fingerId
integerFor kind "finger": the number the finger was enrolled under on the sensor, 1 to 65535.
label
stringA note shown in lists, up to 40 characters.
  • card: { "uid": "04D4E5F6", "label": "Spare card" } (kind card is the default).
  • finger: { "kind": "finger", "fingerId": 7 }.
  • pin (below): the PIN is never stored. Only a salted hash is kept, and the answer never contains the digits.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/people/1/cards" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"pin","pin":"482910"}'
Response 201
json
{
  "ok": true,
  "card": {
    "personSeq": 1,
    "kind": "pin",
    "uid": "PIN-596FF62324BA",
    "label": "",
    "status": "active"
  }
}
201 · A fingerprint
json
{
  "ok": true,
  "card": {
    "personSeq": 1,
    "kind": "finger",
    "uid": "FP-7",
    "label": "",
    "status": "active"
  }
}
400 · A PIN that is too easy
json
{
  "ok": false,
  "code": "PIN_TOO_SIMPLE",
  "message": "PIN_TOO_SIMPLE",
  "requestId": "req_c978b1acde6575d3"
}
DELETE/api/access/people/{seq}/cards/{uid} Full key only

Take a card, PIN or fingerprint away (uid = the id the person list shows: a card number, PIN-<hash> or FP-<id>)

Path parameters
seqrequired
integerThe seq of the person (from GET /people).
uidrequired
stringThe id of the credential, as GET /people shows it: a card number, PIN-<hash> or FP-<number>.
  • uid is the id the person list shows: a card number, PIN-<hash> or FP-<number>.
Request
bash
curl -X DELETE "https://teqhome.teqlogy.com/api/access/people/1/cards/04D4E5F6" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true }

Doors

Door settings, and opening, locking and unlocking.

GET/api/access/doors Any key

All doors with their status

Request
bash
curl "https://teqhome.teqlogy.com/api/access/doors" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "doors": [
    {
      "deviceId": "a1b2c3d4e5f6",
      "name": "Main gate",
      "mode": 0,
      "pulseMs": 3000,
      "heldSec": 30,
      "doorSensor": false,
      "doorNC": true,
      "fire": false,
      "tamper": false,
      "relayActiveLow": true,
      "wgBits": 0,
      "wgFacility": 0,
      "wgIn": false,
      "levelMask": 0,
      "direction": "",
      "override": 0,
      "ackedVersion": 2,
      "lastHelloAt": "2026-11-01T18:31:22.854Z",
      "lastStatus": {
        "rssi": -52,
        "up": 100,
        "ip": "192.168.18.51",
        "door": 0,
        "fire": 0,
        "tamper": 0,
        "relay": 0,
        "cards": 5,
        "heap": 100000,
        "ver": 2,
        "at": "2026-11-01T18:33:58.106Z"
      },
      "device": {
        "deviceId": "a1b2c3d4e5f6",
        "name": "Main gate",
        "status": true,
        "firmware": "0.5.1",
        "rssi": -52,
        "lastSeen": "2026-11-01T18:31:22.551Z"
      }
    }
  ]
}
GET/api/access/doors/{deviceId} Any key

One door

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request
bash
curl "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "door": {
    "deviceId": "a1b2c3d4e5f6",
    "name": "Main gate",
    "mode": 0,
    "pulseMs": 3000,
    "heldSec": 30,
    "doorSensor": false,
    "doorNC": true,
    "fire": false,
    "tamper": false,
    "relayActiveLow": true,
    "wgBits": 0,
    "wgFacility": 0,
    "wgIn": false,
    "levelMask": 0,
    "direction": "",
    "override": 0,
    "ackedVersion": 2,
    "lastHelloAt": "2026-11-01T18:31:22.854Z",
    "lastStatus": {
      "rssi": -52,
      "up": 100,
      "ip": "192.168.18.51",
      "door": 0,
      "fire": 0,
      "tamper": 0,
      "relay": 0,
      "cards": 5,
      "heap": 100000,
      "ver": 2,
      "at": "2026-11-01T18:33:58.106Z"
    },
    "device": {
      "deviceId": "a1b2c3d4e5f6",
      "name": "Main gate",
      "status": true,
      "firmware": "0.5.1",
      "rssi": -52,
      "lastSeen": "2026-11-01T18:31:22.551Z"
    }
  }
}
PATCH/api/access/doors/{deviceId} Full key only

Door settings (name, mode, unlock time, sensors, Wiegand, levelMask = which levels may enter this door, direction = in or out for anti-passback)

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request body (JSON)
name
stringDoor name, up to 60 characters.
mode
integer0 = the reader decides, 1 = reader only (Wiegand out), 2 = both.
pulseMs
integerHow long the lock is released, 200 to 60000 ms.
heldSec
integerHow long the door may stay open before door.held_open, 5 to 3600 seconds.
doorSensor
booleantrue when a door contact is wired.
doorNC
booleantrue when the door contact is normally closed.
fire
booleantrue when a fire input is wired (it releases the door).
tamper
booleantrue when a tamper switch is wired.
relayActiveLow
booleantrue when the lock relay switches on a low signal.
wgBits
integerWiegand output: 0 (off), 26 or 34 bits.
wgFacility
integerWiegand facility code, 0 to 255.
wgIn
booleantrue when a second reader is wired to the Wiegand IN wires.
levelMask
integerWhich levels may enter this door: bit i = level i. 0 = no limit. 0 to 65535.
direction
string"in", "out" or "" (not part of anti-passback).
  • Send only what changes. The reader gets it at once and keeps it through power cuts.
  • levelMask 3 = only people with level 0 or level 1 may enter this door.
Request
bash
curl -X PATCH "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Main gate","pulseMs":3000,"levelMask":3}'
Response 200
json
{
  "ok": true,
  "door": {
    "deviceId": "a1b2c3d4e5f6",
    "name": "Main gate",
    "mode": 0,
    "pulseMs": 3000,
    "heldSec": 30,
    "doorSensor": false,
    "doorNC": true,
    "fire": false,
    "tamper": false,
    "relayActiveLow": true,
    "wgBits": 0,
    "wgFacility": 0,
    "wgIn": false,
    "levelMask": 3,
    "direction": "",
    "override": 0,
    "ackedVersion": 2,
    "lastHelloAt": "2026-11-01T18:31:22.854Z"
  }
}
POST/api/access/doors/{deviceId}/open Full key only

Unlock once (the normal unlock time)

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/open" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true }
409 · The reader is offline
json
{
  "ok": false,
  "code": "DEVICE_OFFLINE",
  "message": "The door is offline",
  "requestId": "req_a1b2c3d4e5f6g7h8"
}
POST/api/access/doors/{deviceId}/lock Full key only

Lock down: no card opens it; the exit button still works. Optional minutes (1-1440)

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request body (JSON)
minutes
integerOptional, 1 to 1440. The reader counts it down itself.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/lock" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"minutes":30}'
Response 200until is null when no minutes were given
json
{
  "ok": true,
  "override": "locked",
  "until": "2026-11-01T18:58:28.863Z",
  "sent": true
}
POST/api/access/doors/{deviceId}/unlock Full key only

Hold unlocked. Optional minutes (1-1440). A restart of the board ends it (fail secure)

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request body (JSON)
minutes
integerOptional, 1 to 1440. The reader counts it down itself.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/unlock" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"minutes":10}'
Response 200
json
{
  "ok": true,
  "override": "unlocked",
  "until": "2026-11-01T18:38:28.865Z",
  "sent": true
}
POST/api/access/doors/{deviceId}/release Full key only

Back to normal

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/release" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true, "override": "normal", "until": null, "sent": true }
GET/api/access/doors/{deviceId}/local-key Full key only

The key (X-Api-Key) of the reader's own LAN API, and its last known IP. A secret: needs a full key

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
  • The key of the reader's own LAN API (see "Talking to a reader"). A secret: only a full key may read it, and it is in no other answer.
  • ip and url are null until the reader has told its address.
Request
bash
curl "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/local-key" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "localKey": "0123456789abcdef0123456789abcdef",
  "ip": "192.168.18.51",
  "url": "http://192.168.18.51"
}
POST/api/access/doors/{deviceId}/local-key/rotate Full key only

New LAN API key, sent to the reader at once

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/local-key/rotate" \
  -H "Authorization: Bearer $KEY"
Response 200a new key; the old one stops working
json
{ "ok": true, "localKey": "fedcba9876543210fedcba9876543210", "sent": true }
POST/api/access/doors/{deviceId}/reboot Full key only

Restart the reader (409 when it is offline)

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/reboot" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true }
POST/api/access/doors/{deviceId}/enroll Full key only

The next card tapped on this door is given to the person

Path parameters
deviceIdrequired
stringThe id of the door (from GET /doors).
Request body (JSON)
personSeqrequired
integerThe seq of the person who gets the next card.
sec
integerSeconds the reader waits for the card, 10 to 120 (default 30).
  • Give the next card tapped on this door to the person. sec is how long the reader waits (10 to 120, default 30).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/doors/a1b2c3d4e5f6/enroll" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"personSeq":3,"sec":30}'
Response 200
json
{ "ok": true }

Events and attendance

What happened: history, a live stream, unknown cards and attendance.

GET/api/access/events Any key

Event history (deviceId, personSeq, type, from, to, limit, skip, format=csv)

  • Newest first. Filters: deviceId, personSeq, type (a number, see the table below), from and to (YYYY-MM-DD), limit (1 to 500, default 100), skip.
  • Add format=csv to get a CSV file (up to 20,000 rows).
Request
bash
curl "https://teqhome.teqlogy.com/api/access/events?deviceId=a1b2c3d4e5f6&from=2026-11-01&limit=2" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "events": [
    {
      "seq": 1004,
      "at": "2026-11-01T08:21:50.000Z",
      "type": 3,
      "name": "denied",
      "personSeq": 1,
      "reason": 6,
      "reasonName": "lockdown",
      "credential": "pin",
      "uid": "",
      "deviceId": "a1b2c3d4e5f6",
      "door": "Main gate",
      "person": "Ayesha Khan",
      "code": "S-101"
    },
    {
      "seq": 1003,
      "at": "2026-11-01T08:21:47.000Z",
      "type": 2,
      "name": "granted",
      "personSeq": 1,
      "reason": 0,
      "reasonName": "",
      "credential": "card",
      "uid": "",
      "deviceId": "a1b2c3d4e5f6",
      "door": "Main gate",
      "person": "Ayesha Khan",
      "code": "S-101"
    }
  ]
}
GET/api/access/events/stream Any key

Live events: Server-Sent Events, the same envelope as a webhook (max 5 streams per site)

  • Server-Sent Events: the connection stays open and every event arrives as it happens, in the same envelope as a webhook (event: is the type).
  • Up to 5 streams per site. A comment line every 20 seconds keeps the connection alive. A stream does not replay what you missed: ask GET /events for the gap. A read-only key may listen. Behind nginx keep proxy_buffering off.
Request
bash
curl -N "https://teqhome.teqlogy.com/api/access/events/stream" \
  -H "Authorization: Bearer $KEY"
Response 200
text/event-stream
: connected

event: access.granted
data: {"apiVersion":1,"id":"evt_a1b2c3d4e5f6_42","type":"access.granted","createdAt":"2026-10-31T08:21:47.000Z","site":{"id":"6ae7857a357c0f83cdb060bd","name":"Green School"},"data":{"seq":42,"at":"2026-10-31T08:21:47.000Z","door":{"deviceId":"a1b2c3d4e5f6","name":"Main gate"},"event":"granted","uid":"","reason":"","credential":"card","person":{"seq":1,"name":"Ayesha Khan","code":"S-101","dept":"Class 5"}}}

: heartbeat
GET/api/access/unknown-cards Any key

Cards tapped in the last 7 days that nobody owns

  • Cards tapped in the last 7 days that nobody owns: give one to a person with POST /people/{seq}/cards.
Request
bash
curl "https://teqhome.teqlogy.com/api/access/unknown-cards" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "cards": [
    {
      "uid": "04FFEE01",
      "taps": 3,
      "lastAt": "2026-11-01T08:15:02.000Z",
      "deviceId": "a1b2c3d4e5f6"
    }
  ]
}
GET/api/access/attendance Any key

First / last tap per person and day (from, to, format=csv)

  • First and last granted tap of each person and day, in the site's time zone. At most 62 days; format=csv gives a file.
Request
bash
curl "https://teqhome.teqlogy.com/api/access/attendance?from=2026-11-01&to=2026-11-07" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "from": "2026-11-01",
  "to": "2026-11-07",
  "rows": [
    {
      "date": "2026-11-01",
      "personSeq": 1,
      "name": "Ayesha Khan",
      "code": "S-101",
      "dept": "Class 5",
      "first": "08:02",
      "last": "14:31",
      "taps": 4
    }
  ]
}

Webhooks

Let TeqAccess call your software when something happens.

GET/api/access/webhooks Any key

Your webhooks (never shows the secret)

Request
bash
curl "https://teqhome.teqlogy.com/api/access/webhooks" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "webhooks": [
    {
      "id": "6ae784cfd783d8e927593408",
      "url": "https://erp.example.com/teqaccess",
      "events": ["access.granted", "access.denied"],
      "note": "ERP",
      "active": true,
      "failCount": 0,
      "lastStatus": null,
      "lastAt": null,
      "lastError": ""
    }
  ]
}
POST/api/access/webhooks Full key only

Create a webhook. The signing secret is in this answer only

Request body (JSON)
urlrequired
stringWhere to POST the events.
events
string[]The event types to send. [] = all of them.
note
stringYour note, up to 80 characters.
  • The signing secret is in this answer only. Copy it now; POST /webhooks/{id}/rotate makes a new one.
  • events: [] means everything. On the cloud the address must be https and public.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/webhooks" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://erp.example.com/teqaccess",
    "events": ["access.granted", "access.denied"],
    "note": "ERP"
  }'
Response 201
json
{
  "ok": true,
  "webhook": {
    "id": "6ae784cfd783d8e927593408",
    "url": "https://erp.example.com/teqaccess",
    "events": ["access.granted", "access.denied"],
    "note": "ERP",
    "active": true,
    "failCount": 0,
    "lastStatus": null,
    "lastAt": null,
    "lastError": ""
  },
  "secret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef"
}
PATCH/api/access/webhooks/{id} Full key only

Change url / events / note, switch on or off

Path parameters
idrequired
stringThe id of the webhook (from GET /webhooks).
Request body (JSON)
url
stringWhere to POST the events.
events
string[]The event types to send. [] = all of them.
note
stringYour note, up to 80 characters.
active
booleanfalse switches the webhook off, true on.
  • active: true switches a webhook on again after too many failures.
Request
bash
curl -X PATCH "https://teqhome.teqlogy.com/api/access/webhooks/6ae784cfd783d8e927593408" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
Response 200
json
{
  "ok": true,
  "webhook": {
    "id": "6ae784cfd783d8e927593408",
    "url": "https://erp.example.com/teqaccess",
    "events": ["access.granted", "access.denied"],
    "note": "ERP",
    "active": false,
    "failCount": 0,
    "lastStatus": null,
    "lastAt": null,
    "lastError": ""
  }
}
POST/api/access/webhooks/{id}/rotate Full key only

New signing secret (the old one stops working)

Path parameters
idrequired
stringThe id of the webhook (from GET /webhooks).
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/webhooks/6ae784cfd783d8e927593408/rotate" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{
  "ok": true,
  "webhook": {
    "id": "6ae784cfd783d8e927593408",
    "url": "https://erp.example.com/teqaccess",
    "events": ["access.granted", "access.denied"],
    "note": "ERP",
    "active": true,
    "failCount": 0,
    "lastStatus": null,
    "lastAt": null,
    "lastError": ""
  },
  "secret": "whsec_0123456789abcdef0123456789abcdef0123456789abcdef"
}
POST/api/access/webhooks/{id}/test Full key only

Send a webhook.test event now and report what your server answered

Path parameters
idrequired
stringThe id of the webhook (from GET /webhooks).
  • Sends a webhook.test event now and tells what your server answered.
Request
bash
curl -X POST "https://teqhome.teqlogy.com/api/access/webhooks/6ae784cfd783d8e927593408/test" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true, "status": 200, "delivered": true }
200 · Your server could not be reached
json
{ "ok": true, "status": 0, "delivered": false, "error": "ECONNREFUSED" }
DELETE/api/access/webhooks/{id} Full key only

Delete a webhook

Path parameters
idrequired
stringThe id of the webhook (from GET /webhooks).
Request
bash
curl -X DELETE "https://teqhome.teqlogy.com/api/access/webhooks/6ae784cfd783d8e927593408" \
  -H "Authorization: Bearer $KEY"
Response 200
json
{ "ok": true }
The same list as a machine-readable OpenAPI 3.0 file: GET /api/access/openapi.json (it needs a key, like every call). Import it into Postman or a code generator. The dashboard's Door access page and the Manager have a button that saves it.

Events

Everything that happens at a door has one shape — in the history (GET /events), on the live stream and in a webhook.

json · access.granted
{
  "apiVersion": 1,
  "id": "evt_a1b2c3d4e5f6_42",
  "type": "access.granted",
  "createdAt": "2026-10-31T08:21:47.000Z",
  "site": { "id": "6ae7857a357c0f83cdb060bd", "name": "Green School" },
  "data": {
    "seq": 42,
    "at": "2026-10-31T08:21:47.000Z",
    "door": { "deviceId": "a1b2c3d4e5f6", "name": "Main gate" },
    "event": "granted",
    "uid": "",
    "reason": "",
    "credential": "card",
    "person": { "seq": 1, "name": "Ayesha Khan", "code": "S-101", "dept": "Class 5" }
  }
}
typeWhen
access.grantedA card, PIN or fingerprint opened the door.
access.deniedA card, PIN or fingerprint was refused. data.reason says why (see the deny reasons).
access.exit_buttonSomeone left through the door with the exit button.
access.remote_openThe door was opened from the app, the dashboard or the API.
access.enrolledA card tapped at the reader was given to a person (enrolment at the door).
door.forcedThe door sensor saw the door open without a valid unlock (forced open).
door.held_openThe door stayed open longer than its held-open time.
door.closedThe door sensor reports the door closed.
door.tamperThe tamper input of the reader was triggered.
door.fire_onThe fire input is active: the door is released.
door.fire_offThe fire input is back to normal.
door.bootThe reader started (power on or restart).
door.onlineThe reader connected to the server.
door.offlineThe reader lost its connection to the server.
door.overrideThe door was locked down, held unlocked or released through the API or the app (data.override says which).
access.apb_violationSoft anti-passback: the same person passed the same direction twice in a row on one day (data.previous shows the first time).
webhook.testSent only by the test call of a webhook, never by a door.

Why a door said no

data.reason of access.denied (and reasonName in GET /events) is one of these:

reasonnumberMeaning
unknown1Nobody owns this card, PIN or fingerprint.
blocked2The person is blocked.
expired3The person is past their "valid to" date.
not_yet4The person is before their "valid from" date.
schedule5Outside the time zone (schedule) of the person's level.
lockdown6The door, or the whole site, is locked down.
level7The person has no level that may enter this door.
too_many8Five wrong PINs in a row: the reader makes everybody wait one minute, also for the right PIN.

The history

Rows of GET /events look like this. The type filter takes the number:

seq
integerThe number of the event on its reader. With deviceId it is unique.
at
stringWhen it happened (ISO 8601, UTC).
type / name
integer / stringgranted, denied, exit_button, door_forced ... The numbers are the ones of ?type=.
reason / reasonName
integer / stringWhy a card was refused. Empty for an event that was not a refusal.
credential
stringWhat was presented: card, pin or finger.
uid
stringThe card number, only for an unknown card. Never a PIN.
personSeq / person / code
integer / stringWho it was (0 and empty when nobody owns the credential).
deviceId / door
stringWhich reader, and its name.
1 · boot2 · granted3 · denied4 · exit_button5 · door_forced6 · door_held7 · door_closed8 · tamper9 · fire_on10 · fire_off11 · remote_open12 · enrolled

Webhooks

Create one with POST /webhooks (see above). Its secret is shown once and signs every delivery.

  • TeqAccess POSTs the envelope to your address within about a second. Answer with any 2xx.
  • Otherwise it tries 3 times: now, after 5 seconds and after 30 seconds. 20 failed deliveries in a row switch the webhook off — you see it in the list; PATCH { "active": true } turns it on again.
  • The id of an event is evt_<deviceId>_<seq>: the same event always has the same id, so drop repeats.
  • Headers: Teq-Signature (below) and Teq-Event-Id. At most 10 webhooks per site, 5 seconds to answer, redirects are not followed.
  • Cloud: https only, and public addresses only (loopback, private ranges, .local and names that resolve to them are refused, also when connecting). School server: http and private addresses are fine.
header
Teq-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your whsec_ secret>
javascript
const crypto = require('crypto')

// rawBody: the request body exactly as received (a string or Buffer), header: the Teq-Signature header
function verify(rawBody, header, secret, toleranceSec = 300) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '')
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSec) return false
  const mac = crypto.createHmac('sha256', secret).update(m[1] + '.' + rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(m[2]))
}
Check the signature on the raw body, before you parse it, and drop a delivery whose time is too old. Anyone who knows your address could otherwise send you fake events.

Live stream

GET /events/stream keeps a connection open and sends every event as it happens (Server-Sent Events, the same envelope; event: is the type). It does not replay what you missed: ask GET /events for the gap after a reconnect.

Talking to a reader

Each reader also answers on your own network, with or without any server. It is for a quick local integration or a check that does not depend on the internet: a script on the school network can open a door or read the last events directly.

CallWhat it does
GET/api/v1/statusState of the reader: firmware, lock mode, door input, number of cards, last event, link to the server, signal, uptime.
GET/api/v1/events?after=<seq>&limit=<1..50>Events newer than seq, oldest first. after = lastEvent - 20 gives the newest 20.
POST/api/v1/openUnlock once.
POST/api/v1/lock[?minutes=N]Lock down, optionally for N minutes.
POST/api/v1/unlock[?minutes=N]Hold unlocked, optionally for N minutes.
POST/api/v1/releaseBack to normal.
POST/api/v1/rebootRestart the reader.
POST/api/v1/serverMove the reader to another server (cloud or school): body { "url", "userId", "token" }.
  • Address: http://<reader ip>/api/v1/… or http://teqaccess-<last 6 characters of the id>.local/api/v1/…
  • Header X-Api-Key: <the reader's key>. Get it from GET /doors/{deviceId}/local-key (a full key). No key means the reader's API is off. Five wrong keys in a minute lock it for a minute (429).
  • Plain HTTP for your own network: do not forward port 80 of a reader to the internet.
  • People and cards are not managed over this API. They belong to the site and the server syncs them to the readers.
bash
curl "http://192.168.18.51/api/v1/status" \
  -H "X-Api-Key: $LOCAL_KEY"
json · the answer
{
  "ok": true,
  "deviceId": "a1b2c3d4e5f6",
  "firmware": "0.5.1",
  "override": "normal",
  "relay": 0,
  "door": 0,
  "fire": 0,
  "tamper": 0,
  "cards": 5,
  "lastEvent": 1004,
  "serverLink": 1,
  "time": 1793559600,
  "timeValid": true,
  "rssi": -52,
  "up": 100,
  "heap": 100000
}

Errors and limits

Errors are { ok: false, code, message } with the HTTP status. Switch on code; the message is for people and may change. The same X-Request-Id header and requestId as everywhere.

json
{
  "ok": false,
  "code": "API_KEY_READONLY",
  "message": "This API key is read-only: it can read data but cannot change anything.",
  "requestId": "req_a1b2c3d4e5f6g7h8"
}
codeHTTPWhen
AUTH_MISSING401No Authorization header.
AUTH_INVALID401The token or API key is not recognised.
API_KEY_READONLY403A read-only API key tried to change something.
API_KEY_NOT_ALLOWED403A call that no API key may make (for example managing API keys).
VALIDATION_ERROR400A field is missing or wrong; message says which.
NAME_REQUIRED400A person needs a name.
GROUPS_INVALID400groups must be a whole number from 0 to 65535 (a bit mask of the levels).
DATE_INVALID400validFrom / validTo must be YYYY-MM-DD or empty.
STATUS_INVALID400status must be active or blocked.
SCHEDULE_INVALID400A time zone or a level is wrong; message says what.
HOLIDAYS_INVALID400holidays must be a list of YYYY-MM-DD dates.
TZ_INVALID400tzOffsetMin must be a whole number of minutes from -720 to 840.
APB_INVALID400apb must be off or soft.
INVALID_UID400A card number must be 4 to 10 bytes in hexadecimal.
KIND_INVALID400kind must be card, pin or finger.
PIN_INVALID400A PIN must be 4 to 8 digits.
PIN_TOO_SIMPLE400The PIN is too easy to guess (1111, 1234, 8765 ...).
FINGER_INVALID400fingerId must be a whole number from 1 to 65535.
MINUTES_INVALID400minutes must be a whole number from 1 to 1440.
OVERRIDE_INVALID400The lock mode is not valid.
IMPORT_SIZE400An import takes 1 to 500 rows.
EVENTS_INVALID400events must be a list of known event types.
URL_INVALID400The webhook address is not a valid URL.
URL_NOT_HTTPS400On the cloud a webhook address must be https.
URL_PRIVATE400On the cloud a webhook address must be public (no local or private address).
URL_CREDENTIALS400A webhook address must not carry a user name or password.
URL_TOO_LONG400The webhook address is too long.
NOT_FOUND404The person, webhook or route does not exist (or is not yours).
DEVICE_NOT_FOUND404The door does not exist (or is not yours).
CARD_NOT_FOUND404The person has no such card, PIN or fingerprint.
CARD_IN_USE409This card or fingerprint number already belongs to somebody.
PIN_IN_USE409Somebody else already has this PIN.
PEOPLE_LIMIT409A site holds at most 3400 people.
WEBHOOK_LIMIT409A site has at most 10 webhooks.
DEVICE_OFFLINE409The door is offline, so the command could not be delivered.
RATE_LIMITED429Too many requests: wait for the Retry-After header.
TOO_MANY_STREAMS429At most 5 live streams per site.

Limits

WhatLimit
People per site3400
Rows in one import500
Webhooks per site10
Days of attendance in one call62
Minutes of a timed lock or unlock1 to 1440
Live streams per site5
Requestssee Rate limits on the Teqlogy API page (https://teqhome.teqlogy.com/docs#rate-limits)

Not there yet

  • Card + PIN together (two factors) and a keypad or fingerprint sensor fitted to the board (the PIN and fingerprint credentials and the door logic are there).
  • OSDP, hard anti-passback and interlock between two doors (soft anti-passback is reported only), Ethernet / PoE, MQTT.