JSON API endpoints for frontend/backend communication.
API routes handle:
- JSON request/response formatting
- HTTP status code management
- Parameter extraction from URLs
- Error response standardization
API routes delegate business logic to controllers and return clean JSON responses.
Purpose: System health check endpoint
Input Data: None
Output Data:
{
status: "healthy" | "unhealthy",
timestamp: string,
// Additional health metrics from controller
}
HTTP Status: Always 200
Purpose: Update page viewing status in blob storage for real-time tracking with immediate Notion sync
Input Data:
{
pageId: string, // Notion page UUID
viewing: boolean, // true when actively viewing, false when leaving
tabVisible: boolean // Page Visibility API state
}
Processing Flow:
- Extract viewing data from request body
- Get user email from authentication context
- Update blob storage with key
glimpse--viewing--{pageId}
- If viewing status changed to true: Immediately sync to Notion page properties
- Return success confirmation
Notion Integration:
- Real-time sync: When user starts viewing (
viewing: true
), immediately updates Notion page - Properties updated: "Viewing" (checkbox), "Last Viewed" (date), "Current Viewer" (text), "Tab Visible" (checkbox)
- Cleanup: Cron job runs every minute to mark stale sessions as
viewing: false
and sync to Notion
Success Response (200):
{
success: true,
data: {
pageId: string, // Notion page UUID
viewing: boolean,
tabVisible: boolean,
lastUpdate: string, // ISO timestamp
userEmail?: string,
sessionId: string // Generated session identifier
}
}
Error Responses:
// 400 - Invalid Request
{
error: "Page ID is required" | "Invalid request body",
details?: string
}
// 500 - Server Error
{
error: "Failed to update viewing status",
details: string
}
Purpose: Get current viewing status for a page
Input Data:
// URL Parameters
{
id: string // Notion page UUID
}
Processing Flow:
- Extract page ID from URL parameter
- Retrieve viewing status from blob storage
- Check for stale data (>30 minutes old)
- Return current status or default values
Success Response (200):
{
pageId: string,
viewing: boolean,
tabVisible: boolean,
lastUpdate: string | null,
userEmail?: string,
sessionId?: string,
stale?: boolean // Present if data is >30 minutes old
}
Error Responses: Same pattern as other endpoints
Purpose: Get viewing analytics/statistics for a page
Input Data:
// URL Parameters
{
id: string // Notion page UUID
}
Success Response (200):
{
currentlyViewing: boolean, // Active viewing status (excludes stale)
lastViewed: string | null, // ISO timestamp of last update
currentUser: string | null, // Email of current/last viewer
tabVisible: boolean, // Current tab visibility
sessionId: string | null // Current/last session ID
}
Purpose: Get Notion page properties (filtered for UI consumption)
Input Data:
// URL Parameters
{
id: string // Notion page UUID
}
Processing Flow:
- Extract page ID from URL parameter
- Call
getDemoProperties(id)
controller - Return filtered page properties or error
Success Response (200):
{
id: string,
properties: {
// All properties except type: "button"
[propertyName: string]: {
type: "title" | "rich_text" | "number" | "select" | "multi_select" | "date" | "checkbox" | "url" | "email" | "phone_number" | "formula" | "relation" | "rollup" | "created_time" | "created_by" | "last_edited_time" | "last_edited_by",
// Property-specific value structure
}
},
parent: {
type: "database_id" | "page_id",
database_id?: string,
page_id?: string
},
created_time: string,
last_edited_time: string,
// ... other Notion page fields
}
Error Responses:
// 400 - Invalid Request
{
error: "Page ID is required",
details?: string
}
// 500 - Server Error
{
error: "Failed to fetch page data",
details: "Notion API error message"
}
Purpose: Get complete Notion page with content blocks
Input Data:
// URL Parameters
{
id: string // Notion page UUID
}
Processing Flow:
- Extract page ID from URL parameter
- Call
getDemoFull(id)
controller - Return complete page with blocks or error
Success Response (200):
{
// Page properties (same as /properties endpoint)
id: string,
properties: { [key: string]: NotionPropertyValue },
parent: NotionParent,
created_time: string,
last_edited_time: string,
// Block content (hierarchical structure)
blocks: [
{
id: string,
type: "paragraph" | "heading_1" | "heading_2" | "heading_3" | "bulleted_list_item" | "numbered_list_item" | "to_do" | "toggle" | "child_page" | "child_database" | "embed" | "image" | "video" | "file" | "pdf" | "bookmark" | "callout" | "quote" | "equation" | "divider" | "table_of_contents" | "column" | "column_list" | "link_preview" | "synced_block" | "template" | "link_to_page" | "table" | "table_row" | "unsupported",
created_time: string,
last_edited_time: string,
has_children: boolean,
children?: NotionBlock[], // Recursive structure for nested blocks
// Type-specific content
[blockType]: {
// Block-specific properties
}
}
]
}
Error Responses: Same as /properties
endpoint
All API routes follow consistent error handling:
// In route handler
const result = await controllerFunction(params);
if (!result.success) {
return c.json({
error: result.error,
details: result.details
}, result.error === "Page ID is required" ? 400 : 500);
}
return c.json(result.data);
API routes return filtered data for UI consumption:
- Button properties removed: Notion button properties are filtered out to prevent UI confusion
- Raw Notion format: Other properties maintain their original Notion API structure
- Complete hierarchy: Block content includes full nested structure with children
# Get page properties only curl https://your-val.web.val.run/api/demo/abc123/properties # Get complete page with content curl https://your-val.web.val.run/api/demo/abc123 # Health check curl https://your-val.web.val.run/api/health