# API Endpoint Changes This document provides a comprehensive mapping of V1 API endpoints to their V2 equivalents, including request/response format changes, authentication differences, and code migration examples. ## Overview V2 API represents a complete redesign with: - **RESTful conventions** (proper HTTP methods) - **Unified namespace** (single API at `/api/*`) - **JWT authentication** (Bearer tokens instead of sessions) - **Zod validation** (type-safe request validation) - **Standardized responses** (`{ success, data, pagination }` structure) !!! tip "Migration Strategy" Update frontend API calls incrementally, starting with authentication (foundational), then module by module (campaigns, locations, shifts, etc.). ## Authentication Changes ### V1 Authentication (Session Cookies) **V1 Login**: ```javascript // POST /auth/login fetch('http://localhost:3333/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // Send/receive cookies body: JSON.stringify({ email: 'admin@example.com', password: 'password123' }) }); // Response: 302 Redirect to /dashboard // Session cookie set automatically // Subsequent requests fetch('http://localhost:3333/campaigns', { credentials: 'include' // Sends session cookie }); ``` ### V2 Authentication (JWT Bearer Tokens) **V2 Login**: ```typescript // POST /api/auth/login const response = await fetch('http://localhost:4000/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'admin@example.com', password: 'Admin123!' }) }); const data = await response.json(); // Response: // { // "success": true, // "data": { // "user": { // "id": "clx1a2b3c4d5e6f7g8h9i", // "email": "admin@example.com", // "name": "Admin User", // "role": "SUPER_ADMIN" // }, // "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // } // } // Store tokens (localStorage, sessionStorage, or memory) localStorage.setItem('accessToken', data.data.accessToken); localStorage.setItem('refreshToken', data.data.refreshToken); // Subsequent requests fetch('http://localhost:4000/api/influence/campaigns', { headers: { 'Authorization': `Bearer ${localStorage.getItem('accessToken')}` } }); ``` **V2 Token Refresh**: ```typescript // POST /api/auth/refresh const response = await fetch('http://localhost:4000/api/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken: localStorage.getItem('refreshToken') }) }); const data = await response.json(); // Response: // { // "success": true, // "data": { // "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // New token (rotation) // } // } // Update stored tokens localStorage.setItem('accessToken', data.data.accessToken); localStorage.setItem('refreshToken', data.data.refreshToken); ``` ### Authentication Endpoint Mapping | V1 Endpoint | V2 Endpoint | Method | Changes | |-------------|-------------|--------|---------| | `/auth/login` | `/api/auth/login` | POST | Returns JWT tokens instead of setting cookie | | `/auth/logout` | `/api/auth/logout` | POST | Requires `refreshToken` in body | | `/auth/register` | `/api/auth/register` | POST | Always creates USER role (no role in request) | | `/auth/me` | `/api/auth/me` | GET | Returns 401 if invalid (not 404) | | - | `/api/auth/refresh` | POST | New: refresh token rotation | ## Influence Module API ### Campaigns #### V1 Campaign Endpoints ```javascript // List campaigns GET /campaigns Query: ?page=1 // View campaign GET /campaigns/:id // Create campaign (admin) POST /campaigns/create Body: { Title, Description, Slug, IsActive } // Update campaign (admin) POST /campaigns/:id/edit Body: { Title, Description, Slug, IsActive } // Delete campaign (admin) POST /campaigns/:id/delete ``` #### V2 Campaign Endpoints ```typescript // List campaigns GET /api/influence/campaigns Query: ?page=1&limit=20&search=query&active=true&highlighted=false Auth: Optional (public returns only active campaigns) // Get campaign by ID GET /api/influence/campaigns/:id Auth: Required (admin) // Get campaign by slug (public) GET /api/influence/campaigns/public/:slug Auth: None // Create campaign POST /api/influence/campaigns Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Body: { "title": "Save the Trees", "description": "Campaign description", "slug": "save-the-trees", "active": true, "highlighted": false, "targetLevel": "federal", "targetPosition": "MP", "responseWallEnabled": true } // Update campaign PUT /api/influence/campaigns/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Body: { title, description, ... } // Partial update // Delete campaign DELETE /api/influence/campaigns/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Toggle active status PATCH /api/influence/campaigns/:id/toggle-active Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Toggle highlighted status PATCH /api/influence/campaigns/:id/toggle-highlighted Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) ``` #### Campaign Response Format Changes **V1 Response**: ```json { "list": [ { "Id": 1, "Title": "Save the Trees", "Description": "Campaign description", "Slug": "save-the-trees", "IsActive": true, "Created": "2024-01-15T10:30:00Z" } ], "pageInfo": { "totalRows": 100, "page": 1, "pageSize": 20 } } ``` **V2 Response**: ```json { "success": true, "data": [ { "id": "clx1a2b3c4d5e6f7g8h9i", "title": "Save the Trees", "description": "Campaign description", "slug": "save-the-trees", "active": true, "highlighted": false, "targetLevel": "federal", "targetPosition": "MP", "responseWallEnabled": true, "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z", "createdBy": { "id": "clx1a2b3c4d5e6f7g8h9i", "name": "Admin User", "email": "admin@example.com" } } ], "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 } } ``` ### Representatives #### V1 Representative Endpoints ```javascript // Lookup representatives by postal code POST /representatives/lookup Body: { postalCode: "M5V 1A1" } // List cached representatives (admin) GET /admin/representatives ``` #### V2 Representative Endpoints ```typescript // Lookup representatives (public) POST /api/influence/representatives/lookup Auth: None Body: { "postalCode": "M5V1A1" } Response: { "success": true, "data": [ { "name": "John Doe", "email": "john.doe@parl.gc.ca", "district": "Toronto Centre", "party": "Liberal", "level": "federal", "photoUrl": "https://..." } ] } // List cached representatives (admin) GET /api/influence/representatives Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Query: ?page=1&limit=20&level=federal&party=Liberal&search=John // Get representative stats (admin) GET /api/influence/representatives/stats Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Response: { "success": true, "data": { "total": 338, "byLevel": { "federal": 338, "provincial": 124 }, "byParty": { "Liberal": 159, "Conservative": 119, "NDP": 25 } } } // Get representative by ID (admin) GET /api/influence/representatives/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Delete representative (admin) DELETE /api/influence/representatives/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Health check GET /api/influence/representatives/health Auth: None ``` ### Campaign Emails #### V1 Email Endpoints ```javascript // Send campaign email POST /campaigns/:id/send-email Body: { senderName, senderEmail, postalCode } ``` #### V2 Email Endpoints ```typescript // Send campaign email (public) POST /api/influence/campaign-emails/send-email Auth: None Rate Limit: 30 requests/hour per IP Body: { "campaignId": "clx1a2b3c4d5e6f7g8h9i", "postalCode": "M5V1A1", "senderName": "Jane Doe", "senderEmail": "jane@example.com", "customMessage": "Optional custom message" } // Track mailto clicks (public) GET /api/influence/campaign-emails/track-mailto/:emailId Auth: None // List campaign emails (admin) GET /api/influence/campaign-emails Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Query: ?campaignId=xxx&page=1&limit=20&sortBy=createdAt&sortOrder=desc // Get campaign email stats (admin) GET /api/influence/campaign-emails/stats/:campaignId Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Response: { "success": true, "data": { "totalEmails": 1234, "queuedEmails": 5, "sentEmails": 1200, "failedEmails": 29, "mailtoClicks": 340 } } ``` ### Email Queue #### V2 Email Queue Endpoints (New) ```typescript // Get queue stats (admin) GET /api/influence/email-queue/stats Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Response: { "success": true, "data": { "waiting": 10, "active": 2, "completed": 5000, "failed": 15, "delayed": 0, "paused": false } } // Pause queue (admin) POST /api/influence/email-queue/pause Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Resume queue (admin) POST /api/influence/email-queue/resume Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Clean completed jobs (admin) POST /api/influence/email-queue/clean Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Query: ?grace=3600 (seconds) // Retry failed jobs (admin) POST /api/influence/email-queue/retry-failed Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) ``` ### Response Wall #### V1 Response Endpoints ```javascript // Submit response POST /responses/submit Body: { campaignId, name, email, message } // List responses GET /responses/:campaignId ``` #### V2 Response Endpoints ```typescript // Submit response (public) POST /api/influence/responses/submit Auth: None Body: { "campaignId": "clx1a2b3c4d5e6f7g8h9i", "name": "Jane Doe", "email": "jane@example.com", "message": "I support this campaign!", "ipAddress": "192.168.1.1" // Auto-captured by server } // Sends verification email // Verify response email GET /api/influence/responses/verify/:token Auth: None // List responses (public) GET /api/influence/responses/campaign/:campaignId Auth: None Query: ?page=1&limit=20&sortBy=upvotes&sortOrder=desc Response: Only returns APPROVED responses // Upvote response (public) POST /api/influence/responses/:id/upvote Auth: Optional (tracks by IP + userId if logged in) Body: { "ipAddress": "192.168.1.1" } // List responses (admin) GET /api/influence/responses Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Query: ?page=1&limit=20&campaignId=xxx&status=PENDING&sortBy=createdAt&sortOrder=desc // Get response detail (admin) GET /api/influence/responses/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Approve response (admin) PATCH /api/influence/responses/:id/approve Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Reject response (admin) PATCH /api/influence/responses/:id/reject Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Delete response (admin) DELETE /api/influence/responses/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) ``` ## Map Module API ### Locations #### V1 Location Endpoints ```javascript // List locations GET /locations Query: ?page=1 // Create location (admin) POST /locations/create Body: { Address, Latitude, Longitude, SupportLevel, Notes } // Update location (admin) POST /locations/:id/edit Body: { Address, Latitude, Longitude, SupportLevel, Notes } // Delete location (admin) POST /locations/:id/delete ``` #### V2 Location Endpoints ```typescript // List locations (admin) GET /api/map/locations Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?page=1&limit=20&search=query&supportLevel=SUPPORT&cutId=xxx&geocoded=true // List locations (public map) GET /api/map/locations/public Auth: None Query: ?bounds=minLat,minLng,maxLat,maxLng (returns only geocoded locations) // Get location by ID (admin) GET /api/map/locations/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Create location (admin) POST /api/map/locations Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "address": "123 Main St", "city": "Toronto", "province": "ON", "postalCode": "M5V1A1", "country": "Canada", "latitude": 43.6532, "longitude": -79.3832, "supportLevel": "SUPPORT", "notes": "Spoke with resident", "contactName": "John Doe", "contactPhone": "416-555-1234", "contactEmail": "john@example.com", "cutId": "clx1a2b3c4d5e6f7g8h9i" } // Update location (admin) PUT /api/map/locations/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { address, city, ... } // Partial update // Delete location (admin) DELETE /api/map/locations/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Bulk delete locations (admin) POST /api/map/locations/bulk-delete Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "ids": ["id1", "id2", "id3"] } // Export locations CSV (admin) GET /api/map/locations/export Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?supportLevel=SUPPORT&cutId=xxx // Import locations CSV (admin) POST /api/map/locations/import Auth: Required (SUPER_ADMIN, MAP_ADMIN) Content-Type: multipart/form-data Body: FormData with CSV file // Geocode location (admin) POST /api/map/locations/:id/geocode Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?provider=nominatim (optional) // Bulk geocode (admin) POST /api/map/locations/bulk-geocode Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?limit=100&provider=nominatim // Reverse geocode (admin) POST /api/map/locations/reverse-geocode Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "latitude": 43.6532, "longitude": -79.3832 } // Get location stats (admin) GET /api/map/locations/stats Auth: Required (SUPER_ADMIN, MAP_ADMIN) Response: { "success": true, "data": { "total": 10000, "geocoded": 9500, "notGeocoded": 500, "bySupportLevel": { "STRONG_SUPPORT": 1200, "SUPPORT": 3400, "UNDECIDED": 2100, "OPPOSED": 1800, "STRONG_OPPOSED": 800, "UNKNOWN": 700 } } } // NAR Import (admin, new in V2) GET /api/map/locations/nar/datasets Auth: Required (SUPER_ADMIN, MAP_ADMIN) Response: List of available NAR datasets (provinces) POST /api/map/locations/nar/import Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "province": "24", "cityFilter": "Toronto", "postalCodeFilter": "M5V", "cutId": "clx1a2b3c4d5e6f7g8h9i", "residentialOnly": true } ``` ### Cuts (Territories) #### V1 Cut Endpoints ```javascript // List cuts (admin) GET /admin/cuts // Create cut (admin) POST /admin/cuts/create Body: { Name, GeoJSON } ``` #### V2 Cut Endpoints ```typescript // List cuts (admin) GET /api/map/cuts Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?page=1&limit=20&search=query // List cuts (public map) GET /api/map/cuts/public Auth: None Response: Only returns active cuts with GeoJSON // Get cut by ID (admin) GET /api/map/cuts/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Create cut (admin) POST /api/map/cuts Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "name": "Downtown Toronto", "description": "Downtown canvassing area", "color": "#FF5733", "coordinates": [[[-79.4, 43.6], [-79.3, 43.6], ...]] } // Update cut (admin) PUT /api/map/cuts/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { name, description, color, coordinates } // Delete cut (admin) DELETE /api/map/cuts/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Get locations in cut (admin) GET /api/map/cuts/:id/locations Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?page=1&limit=20 ``` ### Shifts #### V1 Shift Endpoints ```javascript // List shifts GET /shifts // Create shift (admin) POST /shifts/create Body: { Name, StartTime, EndTime, Location, Capacity } // Signup for shift POST /shifts/:id/signup Body: { name, email, phone } ``` #### V2 Shift Endpoints ```typescript // List shifts (public) GET /api/map/shifts/public Auth: None Query: ?upcoming=true&startDate=2024-01-01&endDate=2024-12-31 Response: Only returns future shifts with available capacity // List shifts (admin) GET /api/map/shifts Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?page=1&limit=20&startDate=2024-01-01&endDate=2024-12-31&cutId=xxx // Get shift by ID (admin) GET /api/map/shifts/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Create shift (admin) POST /api/map/shifts Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "name": "Downtown Canvassing", "description": "Canvassing shift for downtown area", "startTime": "2024-02-15T09:00:00Z", "endTime": "2024-02-15T12:00:00Z", "location": "Community Center, 123 Main St", "capacity": 20, "requirements": "Comfortable shoes, water bottle", "cutId": "clx1a2b3c4d5e6f7g8h9i" } // Update shift (admin) PUT /api/map/shifts/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { name, startTime, ... } // Delete shift (admin) DELETE /api/map/shifts/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Signup for shift (public) POST /api/map/shifts/:id/signup Auth: None Body: { "name": "Jane Doe", "email": "jane@example.com", "phone": "416-555-1234", "notes": "First time volunteering" } // Creates TEMP user if email doesn't exist, sends confirmation email // Cancel signup (public) DELETE /api/map/shifts/:shiftId/signups/:userId Auth: Optional (user can cancel own signup) // List signups for shift (admin) GET /api/map/shifts/:id/signups Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Update signup status (admin) PATCH /api/map/shifts/:shiftId/signups/:userId Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "status": "COMPLETED" } // Email all shift signups (admin) POST /api/map/shifts/:id/email-signups Auth: Required (SUPER_ADMIN, MAP_ADMIN) Body: { "subject": "Shift Reminder", "message": "Don't forget about tomorrow's shift!" } // Get shift stats (admin) GET /api/map/shifts/stats Auth: Required (SUPER_ADMIN, MAP_ADMIN) Response: { "success": true, "data": { "totalShifts": 50, "upcomingShifts": 12, "totalSignups": 234, "signupsByStatus": { "CONFIRMED": 200, "COMPLETED": 30, "CANCELLED": 4 } } } ``` ### Canvassing (New in V2) ```typescript // Start canvass session (volunteer) POST /api/map/canvass/sessions/start Auth: Required (any authenticated user) Body: { "shiftId": "clx1a2b3c4d5e6f7g8h9i", "cutId": "clx1a2b3c4d5e6f7g8h9i" } // End canvass session (volunteer) POST /api/map/canvass/sessions/end Auth: Required (any authenticated user) Body: { "sessionId": "clx1a2b3c4d5e6f7g8h9i" } // Get walking route (volunteer) GET /api/map/canvass/routes/:cutId Auth: Required (any authenticated user) Response: Optimized walking route (nearest-neighbor algorithm) // Record visit (volunteer) POST /api/map/canvass/visits Auth: Required (any authenticated user) Rate Limit: 30 requests/minute Body: { "sessionId": "clx1a2b3c4d5e6f7g8h9i", "locationId": "clx1a2b3c4d5e6f7g8h9i", "outcome": "CONTACT_MADE", "supportLevel": "SUPPORT", "notes": "Very interested in campaign" } // Get canvass dashboard stats (admin) GET /api/map/canvass/dashboard/stats Auth: Required (SUPER_ADMIN, MAP_ADMIN) Response: { "success": true, "data": { "activeSessions": 5, "totalVisitsToday": 234, "totalVisitsWeek": 1420, "avgVisitsPerSession": 47 } } // Get activity feed (admin) GET /api/map/canvass/dashboard/activity Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?limit=50 // Get cut progress (admin) GET /api/map/canvass/dashboard/cut-progress Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Get leaderboard (admin) GET /api/map/canvass/dashboard/leaderboard Auth: Required (SUPER_ADMIN, MAP_ADMIN) Query: ?period=week&limit=10 ``` ### GPS Tracking (New in V2) ```typescript // Start tracking session (volunteer) POST /api/map/tracking/sessions/start Auth: Required (any authenticated user) Body: { "sessionId": "clx1a2b3c4d5e6f7g8h9i" } // Record GPS point (volunteer) POST /api/map/tracking/points Auth: Required (any authenticated user) Body: { "sessionId": "clx1a2b3c4d5e6f7g8h9i", "latitude": 43.6532, "longitude": -79.3832, "accuracy": 10.5, "altitude": 120.3, "speed": 1.2 } // End tracking session (volunteer) POST /api/map/tracking/sessions/end Auth: Required (any authenticated user) Body: { "sessionId": "clx1a2b3c4d5e6f7g8h9i" } // Get tracking session (admin) GET /api/map/tracking/sessions/:id Auth: Required (SUPER_ADMIN, MAP_ADMIN) // Get tracking points (admin) GET /api/map/tracking/sessions/:id/points Auth: Required (SUPER_ADMIN, MAP_ADMIN) ``` ## Landing Pages & Email Templates (New in V2) ### Landing Pages ```typescript // List landing pages (admin) GET /api/pages/admin Auth: Required (SUPER_ADMIN) Query: ?page=1&limit=20&search=query // Get page by ID (admin) GET /api/pages/admin/:id Auth: Required (SUPER_ADMIN) // Create page (admin) POST /api/pages/admin Auth: Required (SUPER_ADMIN) Body: { "title": "About Us", "slug": "about", "content": "...", "published": true } // Update page (admin) PUT /api/pages/admin/:id Auth: Required (SUPER_ADMIN) Body: { title, slug, content, published } // Delete page (admin) DELETE /api/pages/admin/:id Auth: Required (SUPER_ADMIN) // Export page to MkDocs (admin) POST /api/pages/admin/:id/export Auth: Required (SUPER_ADMIN) Query: ?format=themed&filename=about.html // Get page by slug (public) GET /api/pages/public/:slug Auth: None Response: Rendered HTML page ``` ### Email Templates ```typescript // List templates (admin) GET /api/email-templates Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Query: ?page=1&limit=20&category=campaign&published=true // Get template by ID (admin) GET /api/email-templates/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Create template (admin) POST /api/email-templates Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Body: { "name": "Campaign Launch", "category": "campaign", "subject": "New Campaign: {{campaignTitle}}", "htmlBody": "...", "textBody": "Plain text version", "published": true } // Update template (admin) PUT /api/email-templates/:id Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Body: { name, subject, htmlBody, ... } // Publish template version (admin) POST /api/email-templates/:id/publish Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) // Send test email (admin) POST /api/email-templates/:id/test Auth: Required (SUPER_ADMIN, INFLUENCE_ADMIN) Body: { "toEmail": "test@example.com", "variables": { "campaignTitle": "Save the Trees", "userName": "Test User" } } ``` ## Response Format Standards ### Success Response ```json { "success": true, "data": { /* response data */ } } ``` ### Paginated Response ```json { "success": true, "data": [ /* items */ ], "pagination": { "page": 1, "limit": 20, "total": 100, "totalPages": 5 } } ``` ### Error Response ```json { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [ { "path": ["email"], "message": "Invalid email format" } ] } } ``` ### HTTP Status Codes | Code | V1 Usage | V2 Usage | |------|----------|----------| | 200 | Success (all responses) | Success (GET, PUT, PATCH) | | 201 | - | Created (POST) | | 204 | - | No Content (DELETE) | | 400 | Validation error | Bad Request (validation error) | | 401 | Not logged in | Unauthorized (invalid token) | | 403 | - | Forbidden (insufficient permissions) | | 404 | Not found | Not Found | | 409 | - | Conflict (duplicate resource) | | 422 | - | Unprocessable Entity (business logic error) | | 429 | - | Too Many Requests (rate limit) | | 500 | Server error | Internal Server Error | ## Migration Examples ### Example 1: Campaign List Page **V1 Code**: ```javascript // Fetch campaigns fetch('/campaigns?page=1', { credentials: 'include' }) .then(res => res.json()) .then(data => { displayCampaigns(data.list); displayPagination(data.pageInfo); }); ``` **V2 Code**: ```typescript // Fetch campaigns const token = localStorage.getItem('accessToken'); fetch('/api/influence/campaigns?page=1&limit=20', { headers: { 'Authorization': `Bearer ${token}` } }) .then(res => res.json()) .then(response => { if (response.success) { displayCampaigns(response.data); displayPagination(response.pagination); } else { handleError(response.error); } }); ``` ### Example 2: Location Creation **V1 Code**: ```javascript // Create location fetch('/locations/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ Address: '123 Main St, Toronto, ON M5V 1A1', Latitude: 43.6532, Longitude: -79.3832, SupportLevel: 'support', Notes: 'Spoke with resident' }) }); ``` **V2 Code**: ```typescript // Create location const token = localStorage.getItem('accessToken'); fetch('/api/map/locations', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ address: '123 Main St', city: 'Toronto', province: 'ON', postalCode: 'M5V1A1', country: 'Canada', latitude: 43.6532, longitude: -79.3832, supportLevel: 'SUPPORT', notes: 'Spoke with resident' }) }) .then(res => res.json()) .then(response => { if (response.success) { console.log('Created location:', response.data); } else { handleError(response.error); } }); ``` ## Rate Limiting V2 adds rate limiting to prevent abuse: | Endpoint | Limit | Window | |----------|-------|--------| | `/api/auth/login` | 10 requests | 1 minute | | `/api/auth/register` | 10 requests | 1 minute | | `/api/influence/campaign-emails/send-email` | 30 requests | 1 hour | | `/api/map/canvass/visits` | 30 requests | 1 minute | **Rate Limit Headers** (V2 only): ``` X-RateLimit-Limit: 10 X-RateLimit-Remaining: 8 X-RateLimit-Reset: 1707835200 ``` ## Related Documentation - [Migration Overview](index.md) - Migration planning - [Breaking Changes](breaking-changes.md) - V1→V2 differences - [Data Migration](data-migration.md) - Data transfer guide - [Authentication](../architecture/authentication.md) - JWT flow details - [API Reference](../api-reference/index.md) - Full API documentation ## Next Steps 1. **Review endpoint mappings** for your application's usage 2. **Update API client** to use JWT authentication 3. **Migrate endpoints incrementally** (auth first, then modules) 4. **Test error handling** with new response format 5. **Implement rate limit handling** (exponential backoff) !!! tip "API Testing" Use tools like Postman or Thunder Client to test V2 endpoints before frontend migration. Import the V2 API collection from `/docs/postman-collection.json` (if available).