E-Pondok
ID EN
← Home
Developer Reference

E-Pondok Developer Reference

The technical side of E-Pondok — concepts, the data model, the kiosk pipeline, security internals, architecture, and the full HTTP API. For step-by-step product setup, see the Setup Guide.

Introduction

E-Pondok is a presence & relations system for the modern pondok. A single face kiosk at the gate records who comes and goes, a digital guest book manages visits, and the dashboard weaves that data into reports and a living map of institutional relationships.

Built for the realities of a pondok: it works in places with unreliable connectivity (offline-first), grows with many pondok (multi-tenancy), and keeps things secure without heavy operational burden.

What you get

  • Touchless face attendance with automatic entry/exit direction detection.
  • Anti-spoofing — rejects photos, videos, and fake face masks.
  • Digital guest book with categories, purpose, and visit contributions.
  • Institutional relationship management: contacts, partner orgs, tags, and follow-ups.
  • Summary dashboard + attendance analytics.
  • Passwordless magic-link login; every kiosk signs its requests.

Core concepts

Pondok = tenant

A pondok is a single isolated tenant. Each pondok gets its own subdomain — for example pondokabc.epondok.id — and its data never mixes with another pondok’s, even though they share one database.

Three surfaces

  • Apex — the root domain epondok.id. Public landing, signup, checkout, and this documentation.
  • Platform — the subdomain platform.epondok.id. Super-admin: manage accounts, subscriptions, and provision new pondok.
  • Tenant — a subdomain *.epondok.id. The per-pondok admin app (dashboard, attendance, visitors, relations, devices).
Dev note

Locally, localhost and *.workers.dev are treated as tenant. Append ?surface=platform to the URL to force the platform surface during development.

Roles

A person in a pondok has one of the following roles:

santriStudent
ustadzTeacher
staffOperational staff
pengurusPondok committee

Admin roles in the app: admin (full access including Devices), pengurus, and platform (super-admin at apex).

Kiosk

A kiosk is the gate device — a desktop app that runs the face pipeline and records attendance/visitors. Each kiosk registers its own public key and signs every request.

Quick start

From zero to automatic attendance in five steps.

Moved to the Setup Guide

The five-step product walkthrough (register → subscribe → create pondok → install kiosk → enroll → live), with live previews of every form, now lives in the Setup Guide. This page keeps the technical reference.

Dashboard

The tenant home page. A real-time summary across all modules. Data source: GET /api/dashboard.

Summary cards

  • Attendance — present today, absent, late, total population, daily trend.
  • Visitors — visits today, this month, lecturer visits over 3 years.
  • Relations — new, active partners, potential, strategic, alumni network.
  • Follow-ups — number due.

Persons & enrollment

Person = every individual in the pondok (santri, ustadz, staff, pengurus). Full CRUD via /api/persons.

Face enrollment

Enrollment captures a face, converts it to a 512-dimensional embedding, then stores it in Vectorize. On enroll, the checkDuplicates option prevents the same face from being registered twice, and qualityScore records capture quality.

enroll flow
capture frame
→ anti-spoofing (MiniFASNet)   // reject photo/video/mask
→ embedding 512-d (InsightFace)
→ POST /api/persons/:id/enrollments
      { embedding, deviceId, qualityScore, checkDuplicates: true }
  • POST /api/persons/:id/verify-face — match a new embedding vs the person’s enrollments.
  • DELETE /api/persons/:id/enrollments/:eid — deactivate an old enrollment (not a hard delete).
Kiosk build

The real face pipeline needs the cargo feature —features real-face. Without it, the kiosk uses mock embeddings in development.

Attendance

The product’s core. The kiosk captures a face at the gate → matches it against Vectorize → entry/exit direction is detected automatically → the event is stored with a confidence score (confidence) and device id (device_id).

Offline-first

The kiosk records attendance even when the network drops, queuing locally, then syncs automatically once back online. No check-in is lost to connectivity issues.

Queries

  • GET /api/attendance — event list (Paged), filters personId, role, from, to.
  • GET /api/attendance/presence — who is currently inside.
  • GET /api/attendance/analytics — daily entries/exits, top late, most punctual, window & role filters.

Visitors

The digital guest book. Each visit has a category, purpose, check-in/out, and a visitor number (visitor_no).

Categories

parent_guardianlecturerteachergovernment_officialcommunity_leaderalumnivendorgeneralother

Visit contributions

Each visit can record contributions for reporting: workshop, guest_lecture, training, mentoring, community_service, research_collaboration, recruitment_opportunity, other — with topic, audience, participants, and outcomes.

Institutional relations

A map of the pondok’s network so it stays alive and traceable.

Contacts

RelationshipContact stores name, institution, position, expertise, status (new / active / potential / alumni / strategic), and notes.

Collaboration tags

CollaborationTag (slug + label) can be freely attached/detached on contacts to group partners — e.g. guest-teacher, scholarship.

Follow-ups

Scheduled follow-ups: contact_id, due_date, assigned_to, status (open / in_progress / completed / cancelled). The overdue filter highlights those past due.

Devices

The Devices page (admin only) manages kiosks. Each kiosk is registered with a globally unique device_uid, an Ed25519 public key, and an optional bound_network.

  • POST /api/devices/register — register a new kiosk.
  • POST /api/devices/:id/revoke — revoke access (the kiosk can no longer sync).
  • POST /api/devices/:id/activate — reactivate.
  • last_seen_at tracks the last sync.
device_uid is globally unique

device_uid is unique across all pondok (not per-pondok). Never reuse the same UID for two kiosks.

Kiosk desktop

A desktop app (Tauri) installed on the gate device. It runs the entire face pipeline locally: capture frame → anti-spoofing → embedding → match → send the event to the backend.

  • Full-screen UI for fast check-in at the gate.
  • Persons & devices loaded from the backend using the kiosk token.
  • Visual (flash) & audio (TTS) feedback when a face is recognized.
  • Builds for aarch64 Android as well (experimental).

Offline-first & sync

Because connectivity at a pondok is often unstable, the kiosk does not depend on the internet to record. The local event queue syncs via device-authed routes (sync) once the connection recovers. The backend accepts idempotently — no duplicates on retry.

sync sequence
1. event recorded locally (kiosk offline)
2. enters the sync queue
3. when online → POST to the sync route (Ed25519-signed)
4. backend verifies signature + dedup
5. dashboard updates immediately

Face anti-spoofing

Before an embedding is produced, the frame is checked by MiniFASNet to tell a real face apart from a photo, video, or mask. Only a real face (class index 1) is processed further.

Preprocessing

The anti-spoofing model needs raw BGR input in the [0, 255] range — not a normalized tensor. The kiosk pipeline prepares the frame accordingly.

Automatic updates

The kiosk checks /kiosk/update.json for a new version. Releases are stored in the R2 bucket epondok-kiosk-releases and recorded in the kiosk_release table. The built-in Tauri updater downloads & installs the update, signed with the Tauri key. The kiosk account is paired with pin b7e6.

Platform admin

The platform surface (platform.epondok.id) is the super-admin space: provision new pondok, manage subscriptions, and change pondok status (active / suspended). The platform token is separate from the tenant token — the two are different origins.

Multi-pondok

One system, many pondok. Each pondok gets a subdomain & isolated data. Technically: a pondok_id column on every tenant table, a subdomain→pondok resolver, and the JWT carries pondokId so every query is scoped to the pondok that owns the token.

Deploy order

When adding a new tenant table, pondok_id + foreign key must be included from the start. Global constraints & FKs in D1 cannot be arbitrarily rebuilt after the fact.

Pricing & seats

Billing is seat-based (seats) via Polar. Monthly payment, minimum 10 seats.

Seat rangePer seat / mo
10–29$0.90
30–59$0.50
60–99$0.42
100$0.50
101+$0.30
Webhook

The Polar webhook uses the Standard Webhooks (Svix) spec — three headers + base64 signature, not polar-*. cancel = grace period, revoke = immediate. POLAR_WEBHOOK_SECRET is mandatory in production.

Device signing

Each kiosk holds an Ed25519 private key and sends its public key on registration. Every sync/attendance request is signed; the deviceAuth middleware verifies the signature before processing. A revoked (revoked) kiosk is rejected outright.

Tenant isolation

Pondok isolation is layered: subdomain → pondok (resolver), pondok_id on every row, and pondokId in the JWT. No tenant endpoint can read another pondok’s data. All traffic is encrypted in transit (TLS).

Architecture

  • Cloudflare Workers — edge API, serverless, global.
  • D1 — SQLite database, multi-tenant via pondok_id.
  • Vectorize — 512-d face embedding index for fast matching.
  • Polar — subscriptions & checkout; Standard Webhooks for status.
  • R2 — kiosk releases (epondok-kiosk-releases).
History

The backend once ran on Bun + Postgres + pgvector, then migrated to Workers + D1 + Vectorize. Old traces may appear in comments or schema — what runs now is the Cloudflare version.

Routing

host mapping
epondok.id            → apex     (landing, /docs)
platform.epondok.id   → platform (super-admin: provisioning, billing)
*.epondok.id          → tenant   (per-pondok admin app)
localhost / *.workers.dev → tenant (dev)

Data model

Core entities and their key fields:

EntityKey fields
Personid, full_name, role, phone, active
EnrollmentRowperson_id, device_id, quality_score, active
AttendanceEventperson_id, ts, event_type(entry/exit), confidence, device_id
Visitorvisitor_no, name, category, purpose, arrival_ts, departure_ts
VisitContributiontype, topic, audience, participants, outcomes
RelationshipContactname, institution, status, tags
CollaborationTagslug, label
FollowUpcontact_id, due_date, assigned_to, status
Devicedevice_uid, public_key, status, bound_network, last_seen_at
Pondokid, slug, name, status

All tenant entities also carry pondok_id for isolation.

API reference

The JSON API behind the app. Every list route returns a paginated envelope:

Paged<T>
{
"items":  T[],
"total":  number,
"limit":  number,
"offset": number
}

Example authenticated request:

curl
curl -H "Authorization: Bearer [REDACTED:Authorization header] header] \
"https://pondokabc.epondok.id/api/attendance?from=2026-01-01&limit=50"

Tenant routes

Authentication

POST/api/auth/loginPhone + password → { token, user }
POST/api/auth/magic/requestSend a magic-link to email
POST/api/auth/magic/verifyExchange magic-link token → { token, user }
GET/api/auth/meCurrent admin session

Dashboard

GET/api/dashboardAttendance, visitor, relation, follow-up summary

Pondok (public)

GET/api/pondoksPondok list for the landing directory

Persons

GET/api/personsList (Paged<Person>) — role, active, q
GET/api/persons/:idPerson detail
POST/api/personsCreate person (full_name, role, phone)
PATCH/api/persons/:idUpdate
DELETE/api/persons/:idDelete (soft delete)

Face enrollment

GET/api/persons/:id/enrollmentsPerson's enrollment history
POST/api/persons/:id/enrollmentsEnroll embedding (+ deviceId, qualityScore, checkDuplicates)
DELETE/api/persons/:id/enrollments/:eidDeactivate enrollment
POST/api/persons/:id/verify-faceMatch embedding vs person's enrollments

Attendance

GET/api/attendanceEvent list (Paged) — personId, role, from, to
GET/api/attendance/presenceWho is currently inside
GET/api/attendance/analyticsWindow analytics — daily, top late, most punctual

Visitors

GET/api/visitorsList (Paged) — category, from, to, open, q
GET/api/visitors/:idVisit detail
POST/api/visitorsCheck-in (name, phone, category, purpose, …)
POST/api/visitors/:id/checkoutCheck-out
GET/api/visitors/:id/contributionsVisit contributions
POST/api/visitors/:id/contributionsRecord contribution (type, topic)

Relations

GET/api/contactsContact list (Paged) — q, status, institution, tag
GET/api/contacts/:idContact detail
POST/api/contactsCreate contact (name, phone)
PATCH/api/contacts/:idUpdate
POST/api/contacts/:id/tagsAttach tag (slug)
DELETE/api/contacts/:id/tags/:slugDetach tag
GET/api/contacts/:id/follow-upsContact follow-ups

Follow-ups

GET/api/follow-upsList (Paged) — status, overdue
POST/api/follow-upsCreate (contact_id, due_date, assigned_to)
PATCH/api/follow-ups/:idUpdate status / due date

Tags

GET/api/tagsList collaboration tags
POST/api/tagsCreate tag (slug, label)

Devices

GET/api/devicesList (Paged) — q, status (admin)
POST/api/devices/registerRegister kiosk (device_uid, public_key, bound_network)
POST/api/devices/:id/revokeRevoke access
POST/api/devices/:id/activateReactivate

Platform routes (apex)

Platform (apex)

Platform admins only. Token is separate from tenant — apex & subdomain are different origins.
POST/api/platform/registerRegister platform account (name, email, password)
POST/api/platform/loginPlatform login
POST/api/platform/magic/requestPlatform magic-link
POST/api/platform/magic/verifyVerify platform magic-link
GET/api/platform/meCurrent platform session
GET/api/platform/slug/checkCheck pondok slug availability
POST/api/platform/checkoutCreate Polar checkout session (seats)
GET/api/platform/pondokList pondok owned by the account
POST/api/platform/pondokCreate a new pondok (slug, name)
PATCH/api/platform/pondok/:idUpdate pondok name / status
Device-authed routes

The kiosk & sync routes are used by the kiosk and protected by an Ed25519 signature, not an admin token. Not documented for direct use.