API Routing Architecture
The Zynomi platform uses same-origin split routing so that all browser API calls stay on a single domain — eliminating CORS complexity and simplifying authentication.
Overview
Every request hits a single origin (e.g., https://ctms.example.com). The reverse proxy (Caddy) inspects the path and routes it to the correct backend:
| Path | Routed To | Handles |
|---|---|---|
/api/v1/* | KrakenD API Gateway | REST API (patients, studies, auth, appointments, etc.) |
/* (everything else) | Next.js Application | Pages, SSR, static assets, /api/health |
┌──────────────────────────┐
│ Browser / Client │
│ https://ctms.example.com │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ Caddy (Reverse Proxy) │
│ Automatic HTTPS (443) │
└────────┬───────┬─────────┘
│ │
/api/v1/* │ │ /* (all else)
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ KrakenD Gateway │ │ Next.js (Zynexa) │
│ (Port 8080) │ │ (Port 3000) │
└────────┬────────┘ └──────────────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│ Frappe │ │ Supabase │ │ Other │
│ Health │ │ (Auth) │ │ Services │
└────────┘ └──────────┘ └──────────┘
Why Same-Origin?
| Benefit | Description |
|---|---|
| No CORS | Browser API calls are same-origin — no preflight OPTIONS requests or Access-Control-* headers needed |
| Shared cookies | Authentication cookies are shared automatically between pages and API calls |
| Simpler DNS | One domain per environment instead of separate api.* and app.* subdomains |
| Better security | Tighter same-origin policy by default |
API Endpoints
The KrakenD API Gateway provides a unified REST API over multiple backends (Frappe Health, Supabase, external services):
Core Endpoints
| Endpoint | Methods | Description |
|---|---|---|
/api/v1/signin | POST | User authentication (Supabase) |
/api/v1/signup | POST | User registration (Supabase) |
/api/v1/signout | POST | Sign out |
/api/v1/patient/{email} | GET | Patient details by email |
/api/v1/patients | GET | List patients |
/api/v1/doctype/{entity} | GET, POST | Generic entity CRUD |
/api/v1/doctype/{entity}/{id} | GET, PUT, DELETE | Specific entity operations |
/api/v1/{entity}/count | GET | Entity record count |
Clinical Endpoints
| Endpoint | Methods | Description |
|---|---|---|
/api/v1/appointments | GET | List appointments |
/api/v1/appointment/{id} | GET, DELETE | Appointment details |
/api/v1/appointment/booking | POST | Book new appointment |
/api/v1/practitioner/{id} | GET | Practitioner details |
/api/v1/practitioner/appointment/availability | GET | Available slots |
Mobile / Sublink Endpoints
| Endpoint | Methods | Description |
|---|---|---|
/api/v1/devices | GET, POST, DELETE | Device management |
/api/v1/medication_consumption_logs | GET, POST | Medication tracking |
/api/v1/notification/logs | GET, POST, PATCH | Push notifications |
/api/v1/reminders | GET | Medication reminders |
Server-Side vs Client-Side
The platform separates API URLs for Docker deployments where internal and external addresses differ:
| Context | URL | Transport |
|---|---|---|
| Browser (client-side) | https://ctms.example.com/api/v1 | → Caddy → KrakenD |
| Server (SSR / API routes) | http://api-gateway:8080/api/v1 | → Docker network (direct) |
- Client-side calls go through Caddy (HTTPS, same-origin)
- Server-side calls skip Caddy and go directly to the API Gateway over the Docker network — faster and no TLS overhead
This is configured via environment variables:
| Variable | Purpose |
|---|---|
RUNTIME_API_BASE_URL | Server-side URL (Docker-internal, used by Next.js SSR) |
RUNTIME_API_CLIENT_URL | Client-side URL (browser-accessible, same-origin) |
Adding New API Routes
When adding new endpoints, choose the correct location:
| If the route needs... | Add it to... | Path prefix |
|---|---|---|
| Frappe/Supabase proxy, aggregation, caching | KrakenD gateway config | /api/v1/ |
| Next.js-specific logic (SSR data, auth callbacks) | Next.js API routes | /api/ (no /v1/) |
All requests to /api/v1/* are routed to the KrakenD API Gateway. New Next.js API routes should not use the /api/v1/ prefix — use /api/ instead.
Local Development
For local Docker development, the platform uses *.localhost domains:
# /etc/hosts (add these entries)
127.0.0.1 zynexa.localhost api.localhost sublink.localhost
127.0.0.1 cube.localhost mcp.localhost odm.localhost observe.localhost
| URL | Service |
|---|---|
https://zynexa.localhost | CTMS Web App + API (same-origin) |
https://api.localhost | API Gateway (standalone, for direct testing) |
https://sublink.localhost | Sublink Mobile App |
https://cube.localhost | Cube.dev Analytics Playground |
https://observe.localhost | OpenObserve (Logs & Metrics) |
Caddy generates self-signed certificates automatically (local_certs). Accept the browser warning on first visit.