Chapter 3: Reminder APIs

Learning Objectives:

3.1 API Architecture Overview

The WIA-SENIOR-008 Reminder API provides a comprehensive, standards-compliant interface for creating, managing, and delivering memory assistance reminders. Built on REST principles with GraphQL alternatives for complex queries, the API prioritizes reliability, security, and ease of integration.

3.1.1 Core API Principles

Principle Description Implementation Benefit
Resource-Oriented RESTful resources for reminders, users, schedules Noun-based endpoints, HTTP verbs Intuitive, predictable API structure
Stateless Each request contains all necessary information JWT tokens, no server-side sessions Horizontal scalability, reliability
Versioned API version in URL path /v1/, /v2/ prefixes Backward compatibility, safe evolution
HATEOAS Hypermedia links in responses _links object with related resources Self-documenting, client discoverability
Idempotent Repeated requests have same effect Idempotency keys for POST requests Safe retries, fault tolerance

3.1.2 Base API Structure

// WIA-SENIOR-008 Reminder API - Base Configuration
const API_BASE_URL = 'https://api.wia-senior.org/v1';

// API Resource Structure
interface ReminderAPI {
  // Core Resources
  reminders: '/reminders',           // Reminder CRUD operations
  users: '/users',                   // User management
  schedules: '/schedules',           // Recurring schedules
  templates: '/templates',           // Reminder templates

  // Supporting Resources
  medications: '/medications',       // Medication database
  contacts: '/contacts',             // Emergency contacts
  events: '/events',                 // Calendar events

  // Analytics & Reporting
  analytics: '/analytics',           // Usage analytics
  reports: '/reports',               // Adherence reports

  // Real-time
  websocket: 'wss://ws.wia-senior.org/v1',  // WebSocket endpoint

  // Administration
  admin: '/admin',                   // Admin operations
  health: '/health'                  // System health check
}

// Standard Response Format
interface APIResponse<T> {
  success: boolean;
  data?: T;
  error?: {
    code: string;
    message: string;
    details?: any;
  };
  meta: {
    timestamp: string;
    requestId: string;
    version: string;
  };
  _links?: {
    self: string;
    related?: Record<string, string>;
  };
}

// Pagination
interface PaginatedResponse<T> extends APIResponse<T[]> {
  pagination: {
    page: number;
    pageSize: number;
    totalPages: number;
    totalItems: number;
    hasNext: boolean;
    hasPrevious: boolean;
  };
  _links: {
    self: string;
    first: string;
    last: string;
    next?: string;
    previous?: string;
  };
}

3.2 Authentication & Security

Memory assistance systems handle sensitive health information requiring robust security measures. The WIA-SENIOR-008 API implements multiple layers of security including OAuth 2.0, JWT tokens, API keys, and comprehensive audit logging.

3.2.1 Authentication Methods

Method Use Case Security Level Implementation
OAuth 2.0 User-facing applications High - delegated authorization Authorization Code flow with PKCE
JWT Tokens Mobile apps, SPAs High - stateless, signed RS256 signed, short-lived access tokens
API Keys Server-to-server integration Medium - requires secure storage UUID v4, rate-limited, IP-restricted
mTLS High-security enterprise Very High - certificate-based Client certificates, bi-directional SSL
// OAuth 2.0 Authentication Flow
class ReminderAPIClient {
  private accessToken: string | null = null;
  private refreshToken: string | null = null;

  // Step 1: Initiate OAuth flow
  async initiateOAuth(): Promise<string> {
    const params = new URLSearchParams({
      client_id: this.clientId,
      response_type: 'code',
      redirect_uri: this.redirectUri,
      scope: 'reminders:read reminders:write users:read',
      state: this.generateState(),
      code_challenge: this.generateCodeChallenge(),
      code_challenge_method: 'S256'
    });

    return `${this.authUrl}/authorize?${params.toString()}`;
  }

  // Step 2: Exchange authorization code for tokens
  async exchangeCodeForTokens(code: string): Promise<TokenResponse> {
    const response = await fetch(`${this.authUrl}/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code: code,
        redirect_uri: this.redirectUri,
        client_id: this.clientId,
        code_verifier: this.codeVerifier
      })
    });

    const tokens = await response.json();
    this.accessToken = tokens.access_token;
    this.refreshToken = tokens.refresh_token;

    // Schedule token refresh before expiration
    this.scheduleTokenRefresh(tokens.expires_in);

    return tokens;
  }

  // Step 3: Refresh access token
  async refreshAccessToken(): Promise<TokenResponse> {
    if (!this.refreshToken) {
      throw new Error('No refresh token available');
    }

    const response = await fetch(`${this.authUrl}/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: this.refreshToken,
        client_id: this.clientId
      })
    });

    const tokens = await response.json();
    this.accessToken = tokens.access_token;

    if (tokens.refresh_token) {
      this.refreshToken = tokens.refresh_token;
    }

    this.scheduleTokenRefresh(tokens.expires_in);
    return tokens;
  }

  // Make authenticated API request
  async request<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<APIResponse<T>> {
    // Ensure we have valid access token
    if (!this.accessToken || this.isTokenExpired()) {
      await this.refreshAccessToken();
    }

    const response = await fetch(`${API_BASE_URL}${endpoint}`, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${this.accessToken}`,
        'Content-Type': 'application/json',
        'X-Request-ID': this.generateRequestId()
      }
    });

    if (response.status === 401) {
      // Token expired, refresh and retry
      await this.refreshAccessToken();
      return this.request(endpoint, options);
    }

    return response.json();
  }
}

// Security Headers
const SECURITY_HEADERS = {
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'DENY',
  'X-XSS-Protection': '1; mode=block',
  'Content-Security-Policy': "default-src 'self'",
  'Referrer-Policy': 'strict-origin-when-cross-origin'
};

3.3 Reminder CRUD Operations

The core functionality of the Reminder API revolves around creating, reading, updating, and deleting reminders. These operations support both simple one-time reminders and complex recurring schedules.

3.3.1 Reminder Resource Schema

// Complete Reminder Resource Definition
interface Reminder {
  // Identity
  id: string;                          // UUID v4
  userId: string;                      // Owner user ID
  createdAt: string;                   // ISO 8601 timestamp
  updatedAt: string;
  version: number;                     // Optimistic locking

  // Core Properties
  title: string;                       // Max 200 chars
  description?: string;                // Max 2000 chars
  category: ReminderCategory;          // medication, appointment, task, etc.
  priority: 'low' | 'medium' | 'high' | 'urgent';

  // Timing
  scheduledTime: string;               // ISO 8601 timestamp
  timezone: string;                    // IANA timezone
  duration?: number;                   // Duration in minutes
  leadTime?: number;                   // Advance notice in minutes

  // Recurrence
  recurrence?: {
    pattern: 'daily' | 'weekly' | 'monthly' | 'custom';
    interval: number;                  // Every N days/weeks/months
    daysOfWeek?: number[];             // 0=Sunday, 6=Saturday
    daysOfMonth?: number[];            // 1-31
    endDate?: string;                  // When recurrence stops
    exceptions?: string[];             // Dates to skip
  };

  // Location
  location?: {
    name?: string;
    address?: string;
    coordinates?: {
      latitude: number;
      longitude: number;
    };
    radius?: number;                   // Geofence radius in meters
    trigger?: 'arrival' | 'departure' | 'proximity';
  };

  // Delivery
  delivery: {
    channels: DeliveryChannel[];       // phone, email, sms, push, voice, etc.
    escalation?: {
      enabled: boolean;
      steps: EscalationStep[];
    };
    confirmation?: {
      required: boolean;
      timeout: number;                 // Seconds before escalation
    };
  };

  // Content Customization
  content?: {
    message?: string;                  // Custom message text
    audioUrl?: string;                 // Custom audio file
    imageUrl?: string;                 // Visual reminder image
    actionUrl?: string;                // Deep link or URL
  };

  // Medication-Specific (if category === 'medication')
  medication?: {
    name: string;
    dosage: string;
    form: 'pill' | 'liquid' | 'injection' | 'inhaler';
    instructions?: string;
    refillDate?: string;
    prescriberId?: string;
    ndc?: string;                      // National Drug Code
  };

  // Status & Tracking
  status: 'active' | 'completed' | 'snoozed' | 'cancelled' | 'missed';
  completionHistory?: {
    timestamp: string;
    status: 'completed' | 'missed' | 'snoozed';
    respondedAt?: string;
    method?: string;
  }[];

  // Metadata
  tags?: string[];
  metadata?: Record<string, any>;     // Extensibility

  // Links
  _links: {
    self: string;
    user: string;
    schedule?: string;
    analytics?: string;
  };
}

// Create Reminder Request
interface CreateReminderRequest {
  title: string;
  category: ReminderCategory;
  scheduledTime: string;
  // ... other optional fields from Reminder interface
}

// Update Reminder Request
interface UpdateReminderRequest {
  version: number;                     // For optimistic locking
  // Any Reminder fields to update (except id, userId, createdAt)
}

3.3.2 CRUD Endpoint Examples

// CREATE - POST /v1/reminders
async function createReminder(
  data: CreateReminderRequest
): Promise<APIResponse<Reminder>> {
  const response = await client.request('/reminders', {
    method: 'POST',
    body: JSON.stringify(data)
  });
  return response;
}

// Example: Create medication reminder
const medicationReminder = await createReminder({
  title: 'Take morning blood pressure medication',
  category: 'medication',
  priority: 'high',
  scheduledTime: '2025-01-15T08:00:00-05:00',
  timezone: 'America/New_York',
  recurrence: {
    pattern: 'daily',
    interval: 1,
    endDate: '2025-12-31T23:59:59-05:00'
  },
  delivery: {
    channels: ['push', 'voice'],
    escalation: {
      enabled: true,
      steps: [
        { delay: 300, channels: ['push', 'sms'] },
        { delay: 900, channels: ['voice', 'caregiver_alert'] }
      ]
    },
    confirmation: {
      required: true,
      timeout: 600
    }
  },
  medication: {
    name: 'Lisinopril',
    dosage: '10mg',
    form: 'pill',
    instructions: 'Take with food and full glass of water'
  }
});

// READ - GET /v1/reminders/:id
async function getReminder(id: string): Promise<APIResponse<Reminder>> {
  return await client.request(`/reminders/${id}`);
}

// LIST - GET /v1/reminders with filters
async function listReminders(filters: {
  status?: string[];
  category?: string[];
  startDate?: string;
  endDate?: string;
  page?: number;
  pageSize?: number;
}): Promise<PaginatedResponse<Reminder>> {
  const params = new URLSearchParams();

  if (filters.status) params.append('status', filters.status.join(','));
  if (filters.category) params.append('category', filters.category.join(','));
  if (filters.startDate) params.append('start_date', filters.startDate);
  if (filters.endDate) params.append('end_date', filters.endDate);
  params.append('page', String(filters.page || 1));
  params.append('page_size', String(filters.pageSize || 50));

  return await client.request(`/reminders?${params.toString()}`);
}

// UPDATE - PUT /v1/reminders/:id
async function updateReminder(
  id: string,
  updates: UpdateReminderRequest
): Promise<APIResponse<Reminder>> {
  return await client.request(`/reminders/${id}`, {
    method: 'PUT',
    body: JSON.stringify(updates)
  });
}

// PATCH - PATCH /v1/reminders/:id (partial update)
async function patchReminder(
  id: string,
  patches: Partial<Reminder>
): Promise<APIResponse<Reminder>> {
  return await client.request(`/reminders/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(patches)
  });
}

// DELETE - DELETE /v1/reminders/:id
async function deleteReminder(id: string): Promise<APIResponse<void>> {
  return await client.request(`/reminders/${id}`, {
    method: 'DELETE'
  });
}

// BATCH OPERATIONS - POST /v1/reminders/batch
async function batchCreateReminders(
  reminders: CreateReminderRequest[]
): Promise<APIResponse<{
  created: Reminder[];
  failed: { index: number; error: string }[];
}>> {
  return await client.request('/reminders/batch', {
    method: 'POST',
    body: JSON.stringify({ reminders })
  });
}

3.4 Webhook Integration

Webhooks enable event-driven architecture where external systems receive real-time notifications about reminder events. This is essential for integrating memory assistance systems with caregiving platforms, healthcare systems, and family portals.

3.4.1 Webhook Event Types

Event Type Trigger Payload Includes Typical Use Case
reminder.created New reminder created Complete reminder object Sync with calendar, notify caregivers
reminder.delivered Reminder sent to user Reminder ID, delivery method, timestamp Track delivery success rates
reminder.acknowledged User confirms seeing reminder Reminder ID, response time, method Update adherence tracking
reminder.completed Task marked as done Reminder ID, completion time, proof Healthcare compliance reporting
reminder.missed Reminder not acknowledged in time Reminder ID, scheduled time, severity Alert caregivers, escalate
reminder.escalated Escalation step triggered Reminder ID, escalation level, contacts Emergency notifications
// Webhook Configuration
interface WebhookConfig {
  id: string;
  url: string;                         // HTTPS endpoint to receive events
  events: string[];                    // Event types to subscribe to
  secret: string;                      // For HMAC signature verification
  active: boolean;
  retryPolicy: {
    maxAttempts: number;
    backoffMultiplier: number;
    maxBackoffSeconds: number;
  };
  filters?: {
    userId?: string[];                 // Only events for these users
    category?: string[];               // Only specific reminder categories
    priority?: string[];               // Only certain priorities
  };
  headers?: Record<string, string>;   // Custom HTTP headers
}

// Webhook Payload Structure
interface WebhookPayload {
  event: string;                       // e.g., "reminder.completed"
  timestamp: string;                   // ISO 8601 timestamp
  data: {
    reminder: Reminder;
    previousState?: Partial<Reminder>; // For update events
    metadata?: any;                    // Event-specific data
  };
  signature: string;                   // HMAC-SHA256 signature
}

// Verify Webhook Signature
function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const crypto = require('crypto');
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

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

// Handle Webhook Event (Express.js example)
app.post('/webhooks/reminder-events', (req, res) => {
  const signature = req.headers['x-wia-signature'];
  const payload = JSON.stringify(req.body);

  // Verify signature
  if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process event
  const event: WebhookPayload = req.body;

  switch (event.event) {
    case 'reminder.missed':
      // Alert caregiver
      alertCaregiver(event.data.reminder);
      break;

    case 'reminder.completed':
      // Update adherence records
      updateAdherenceRecord(event.data.reminder);
      break;

    case 'reminder.escalated':
      // Send emergency notification
      sendEmergencyNotification(event.data.reminder);
      break;
  }

  // Acknowledge receipt
  res.status(200).json({ received: true });
});

// Register Webhook
async function registerWebhook(
  config: Omit<WebhookConfig, 'id' | 'secret'>
): Promise<APIResponse<WebhookConfig>> {
  return await client.request('/webhooks', {
    method: 'POST',
    body: JSON.stringify(config)
  });
}

3.5 Rate Limiting & Performance

To ensure fair usage and system stability, the WIA-SENIOR-008 API implements comprehensive rate limiting. Understanding these limits is essential for building robust, production-ready integrations.

3.5.1 Rate Limit Tiers

Tier Requests/Minute Requests/Hour Burst Allowance Use Case
Free 60 1,000 10 Development, testing
Basic 300 10,000 50 Small practices, individual caregivers
Professional 1,200 50,000 200 Medium organizations, care facilities
Enterprise Custom Custom Custom Large healthcare systems, research institutions
Rate Limit Headers: Every API response includes rate limit headers: X-RateLimit-Limit (total allowed), X-RateLimit-Remaining (requests left), X-RateLimit-Reset (Unix timestamp when limit resets). Clients should respect these headers and implement exponential backoff when approaching limits.

Key Takeaways

Review Questions

  1. Explain the five core principles of the WIA-SENIOR-008 Reminder API architecture. How does each principle contribute to a robust, scalable API design?
  2. Compare OAuth 2.0, JWT tokens, and API keys as authentication methods. When would you choose each method, and what are their relative security trade-offs?
  3. Describe the complete lifecycle of a recurring medication reminder. Include creation via API, scheduled delivery, user acknowledgment, and webhook notifications to caregivers.
  4. What is an idempotency key, and why is it important for reminder APIs? Provide a concrete example of when idempotency prevents problems.
  5. Design a webhook integration for a care facility management system. Which events should be subscribed to, how should signatures be verified, and what retry logic is appropriate?
  6. Explain how rate limiting protects the API and its users. What should a client do when it approaches or exceeds rate limits?
  7. How does the API support complex reminder scenarios like medication with food requirements? Describe how you would model this using the API's data structures.
  8. What role does HATEOAS play in the API design? Provide an example of how hypermedia links make the API more usable and maintainable.