Overview
RLink follows RESTful API conventions with endpoints organized by module (CMS, CRM, IAM). All API routes are located in the app/api/ directory.
All API endpoints require authentication unless explicitly marked as public. Include session cookies in requests.
Base URL
Development: http://localhost:3000/api
Production: https://your-domain.com/api
Authentication
POST /api/auth/login
Authenticate user and create session.
Request Body:
{
"email": "user@example.com",
"password": "password123"
}
Response (200 OK):
{
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe"
},
"session": {
"token": "session_token",
"expiresAt": "2026-04-30T12:00:00Z"
}
}
POST /api/auth/logout
Terminate current session.
Response (200 OK):
{
"message": "Logged out successfully"
}
POST /api/auth/forgot-password
Request password reset email.
Request Body:
{
"email": "user@example.com"
}
Response (200 OK):
{
"message": "Password reset email sent"
}
POST /api/auth/reset-password
Reset password using token from email.
Request Body:
{
"token": "reset_token",
"password": "new_password123"
}
Response (200 OK):
{
"message": "Password reset successful"
}
CMS Endpoints
Projects
GET /api/cms/projects
List all projects with pagination.
Query Parameters:
page (number): Page number (default: 1)
limit (number): Items per page (default: 10)
status (string): Filter by status (active, upcoming, completed, on-hold)
type (string): Filter by type (residential, commercial, mixed-use)
sort (string): Sort field (e.g., name, created_at)
order (string): Sort order (asc, desc)
Response (200 OK):
{
"data": [
{
"id": "uuid",
"name": "Arcoe Residence",
"slug": "arcoe-residence",
"status": "active",
"type": "residential",
"stage": "pre-selling",
"location": "123 Main St, City",
"price_range": { "min": 5000000, "max": 8000000 },
"cover_photo": "/uploads/projects/arcoe.jpg",
"published": true,
"publish_date": "2026-01-01T00:00:00Z",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 25,
"totalPages": 3
}
}
GET /api/cms/projects/[id]
Get single project by ID.
Response (200 OK):
{
"id": "uuid",
"name": "Arcoe Residence",
"slug": "arcoe-residence",
"description": "Luxury residential project...",
"status": "active",
"type": "residential",
"stage": "pre-selling",
"location": "123 Main St, City",
"coordinates": { "lat": 14.5995, "lng": 120.9842 },
"nearby_landmarks": [
{ "name": "Mall", "distance": "1km" }
],
"amenities": ["pool", "gym", "playground"],
"price_range": { "min": 5000000, "max": 8000000 },
"cover_photo": "/uploads/projects/arcoe.jpg",
"seo_title": "Arcoe Residence - Luxury Living",
"seo_description": "Experience luxury living...",
"seo_keywords": "luxury, residence, property",
"published": true,
"publish_date": "2026-01-01T00:00:00Z",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z",
"galleries": [
{
"id": "uuid",
"image_url": "/uploads/galleries/1.jpg",
"caption": "Living room",
"display_order": 1,
"house_model": "Model A"
}
]
}
POST /api/cms/projects
Create new project.
Request Body:
{
"name": "New Project",
"slug": "new-project",
"description": "Project description",
"status": "active",
"type": "residential",
"stage": "pre-selling",
"location": "123 Main St",
"coordinates": { "lat": 14.5995, "lng": 120.9842 },
"nearby_landmarks": [],
"amenities": [],
"price_range": { "min": 5000000, "max": 8000000 },
"cover_photo": "/uploads/projects/cover.jpg",
"seo_title": "SEO Title",
"seo_description": "SEO Description",
"seo_keywords": "keyword1, keyword2",
"published": true,
"publish_date": "2026-04-01T00:00:00Z"
}
Response (201 Created):
{
"id": "uuid",
"message": "Project created successfully"
}
PATCH /api/cms/projects/[id]
Update existing project.
Request Body (partial update):
{
"name": "Updated Name",
"status": "completed"
}
Response (200 OK):
{
"message": "Project updated successfully"
}
Only fields included in the request body will be updated. All other fields remain unchanged.
DELETE /api/cms/projects/[id]
Delete project.
Response (200 OK):
{
"message": "Project deleted successfully"
}
Project Galleries
POST /api/cms/projects/[id]/galleries
Add photos to project gallery.
Request Body:
{
"images": [
{
"image_url": "/uploads/galleries/1.jpg",
"caption": "Living room",
"display_order": 1,
"house_model": "Model A"
}
]
}
Response (201 Created):
{
"message": "Gallery images added successfully"
}
DELETE /api/cms/projects/galleries/[id]
Delete gallery image.
Response (200 OK):
{
"message": "Gallery image deleted successfully"
}
Articles
GET /api/cms/articles
List articles with filtering.
Query Parameters:
page, limit, sort, order (same as projects)
type (string): Filter by type (news, announcement, blog)
published (boolean): Filter by published status
Response (200 OK):
{
"data": [
{
"id": "uuid",
"title": "Company News",
"slug": "company-news",
"excerpt": "Brief summary...",
"type": "news",
"cover_image": "/uploads/articles/cover.jpg",
"author": {
"id": "uuid",
"name": "John Doe"
},
"tags": ["news", "company"],
"published": true,
"publish_date": "2026-03-30T00:00:00Z",
"created_at": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
GET /api/cms/articles/[id]
Get article with full content.
Response (200 OK):
{
"id": "uuid",
"title": "Company News",
"slug": "company-news",
"content": "# Article Content\n\nMarkdown content here...",
"excerpt": "Brief summary...",
"type": "news",
"cover_image": "/uploads/articles/cover.jpg",
"author": {
"id": "uuid",
"name": "John Doe",
"email": "john@example.com"
},
"tags": ["news", "company"],
"published": true,
"publish_date": "2026-03-30T00:00:00Z",
"seo_title": "SEO Title",
"seo_description": "SEO Description",
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
POST /api/cms/articles
Create article.
Request Body:
{
"title": "New Article",
"slug": "new-article",
"content": "# Article\n\nMarkdown content...",
"excerpt": "Summary",
"type": "news",
"cover_image": "/uploads/articles/cover.jpg",
"tags": ["news"],
"published": true,
"publish_date": "2026-04-01T00:00:00Z",
"seo_title": "SEO Title",
"seo_description": "SEO Description"
}
Response (201 Created):
{
"id": "uuid",
"message": "Article created successfully"
}
Careers
GET /api/cms/careers
List job postings.
Query Parameters: Same as projects, plus:
department (string): Filter by department
employment_type (string): Filter by type (full-time, part-time, contract)
Response (200 OK):
{
"data": [
{
"id": "uuid",
"title": "Software Engineer",
"slug": "software-engineer",
"department": "IT",
"location": "Manila, Philippines",
"employment_type": "full-time",
"salary_range": { "min": 50000, "max": 80000, "currency": "PHP" },
"published": true,
"publish_date": "2026-03-30T00:00:00Z",
"created_at": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
Amenities
GET /api/cms/amenities
List all amenities.
Response (200 OK):
{
"data": [
{
"id": "uuid",
"name": "Swimming Pool",
"description": "Olympic-sized pool...",
"icon": "pool",
"cover_photo": "/uploads/amenities/pool.jpg",
"category": "recreational",
"created_at": "2026-01-01T00:00:00Z"
}
]
}
CRM Endpoints
Leads
GET /api/crm/leads
List leads with filtering.
Query Parameters:
page, limit, sort, order
status (string): Filter by status (new, contacted, qualified, converted, lost)
source (string): Filter by source (website, referral, event, social)
assigned_to (uuid): Filter by assigned user
Response (200 OK):
{
"data": [
{
"id": "uuid",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "+63 912 345 6789",
"source": "website",
"status": "new",
"interest": "Residential property",
"notes": "Interested in Arcoe Residence",
"assigned_to": {
"id": "uuid",
"name": "Agent Name"
},
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
POST /api/crm/leads
Create lead.
Request Body:
{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "+63 912 345 6789",
"source": "website",
"status": "new",
"interest": "Residential property",
"notes": "Interested in Arcoe Residence",
"assigned_to": "user_uuid"
}
Response (201 Created):
{
"id": "uuid",
"message": "Lead created successfully"
}
PATCH /api/crm/leads/[id]
Update lead status or assignment.
Request Body:
{
"status": "contacted",
"notes": "Called on March 30, 2026"
}
Response (200 OK):
{
"message": "Lead updated successfully"
}
Reservations
GET /api/crm/reservations
List reservations.
Query Parameters:
page, limit, sort, order
status (string): Filter by status (pending, confirmed, cancelled)
payment_status (string): Filter by payment (unpaid, partial, paid)
project_name (string): Filter by project
Response (200 OK):
{
"data": [
{
"id": "uuid",
"inventory": {
"id": "uuid",
"inventory_code": "AR-B1-L5",
"block": "B1",
"lot": "L5",
"house_model": "Model A",
"price": 6500000
},
"project_name": "Arcoe Residence",
"client_name": "Jane Smith",
"contact_number": "+63 912 345 6789",
"email": "jane@example.com",
"reservation_date": "2026-03-30T00:00:00Z",
"status": "confirmed",
"payment_status": "partial",
"notes": "Paid 20% down payment",
"created_by": {
"id": "uuid",
"name": "Agent Name"
},
"created_at": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
POST /api/crm/reservations
Create reservation and link to inventory.
Request Body:
{
"inventory_id": "uuid",
"project_name": "Arcoe Residence",
"client_name": "Jane Smith",
"contact_number": "+63 912 345 6789",
"email": "jane@example.com",
"reservation_date": "2026-03-30T00:00:00Z",
"status": "pending",
"payment_status": "unpaid",
"notes": "Initial reservation"
}
Response (201 Created):
{
"id": "uuid",
"message": "Reservation created successfully"
}
Creating a reservation automatically updates the linked inventory unit’s sold_to field and changes its status to reserved.
Inventory
GET /api/crm/inventory
List inventory units with project details.
Query Parameters:
page, limit, sort, order
project_id (uuid): Filter by project
status (string): Filter by status (available, reserved, sold)
block (string): Filter by block
house_model (string): Filter by model
Response (200 OK):
{
"data": [
{
"id": "uuid",
"project": {
"id": "uuid",
"name": "Arcoe Residence",
"slug": "arcoe-residence"
},
"inventory_code": "AR-B1-L5",
"block": "B1",
"lot": "L5",
"house_model": "Model A",
"floor_area": 120.5,
"lot_area": 80.0,
"price": 6500000,
"status": "available",
"sold_to": null,
"created_at": "2026-01-01T00:00:00Z"
}
],
"pagination": { ... }
}
POST /api/crm/inventory
Add inventory unit to project.
Request Body:
{
"project_id": "uuid",
"block": "B1",
"lot": "L5",
"house_model": "Model A",
"floor_area": 120.5,
"lot_area": 80.0,
"price": 6500000,
"status": "available"
}
Response (201 Created):
{
"id": "uuid",
"inventory_code": "AR-B1-L5",
"message": "Inventory unit created successfully"
}
The inventory_code is auto-generated based on project initials, block, and lot. Format: {ProjectInitials}-{Block}-{Lot}
PATCH /api/projects/inventory
Special endpoint to update inventory (used by reservation flow).
Request Body:
{
"inventory_id": "uuid",
"sold_to": "reservation_uuid",
"status": "reserved"
}
Response (200 OK):
{
"message": "Inventory updated successfully"
}
Inquiries
GET /api/crm/inquiries
List customer inquiries.
Query Parameters:
page, limit, sort, order
status (string): Filter by status (unread, read, responded, closed)
Response (200 OK):
{
"data": [
{
"id": "uuid",
"name": "John Doe",
"email": "john@example.com",
"phone": "+63 912 345 6789",
"subject": "Property Inquiry",
"message": "I'm interested in...",
"status": "unread",
"responded_by": null,
"response": null,
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
PATCH /api/crm/inquiries/[id]
Update inquiry status or add response.
Request Body:
{
"status": "responded",
"response": "Thank you for your inquiry..."
}
Response (200 OK):
{
"message": "Inquiry updated successfully"
}
Newsletter
GET /api/crm/newsletters
List newsletter subscriptions.
Query Parameters:
page, limit
status (string): Filter by status (subscribed, unsubscribed)
Response (200 OK):
{
"data": [
{
"id": "uuid",
"email": "subscriber@example.com",
"name": "John Doe",
"status": "subscribed",
"subscribed_at": "2026-01-01T00:00:00Z",
"unsubscribed_at": null
}
],
"pagination": { ... }
}
POST /api/crm/newsletters
Add newsletter subscription.
Request Body:
{
"email": "subscriber@example.com",
"name": "John Doe"
}
Response (201 Created):
{
"id": "uuid",
"message": "Subscription created successfully"
}
Campaigns
GET /api/crm/campaigns
List email campaigns.
Query Parameters:
page, limit, sort, order
status (string): Filter by status (draft, scheduled, sent)
Response (200 OK):
{
"data": [
{
"id": "uuid",
"name": "March Newsletter",
"subject": "Monthly Updates",
"content": "# Newsletter\n\nMarkdown content...",
"recipients": ["all"],
"status": "sent",
"scheduled_at": null,
"sent_at": "2026-03-30T00:00:00Z",
"created_by": {
"id": "uuid",
"name": "Admin User"
},
"created_at": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
POST /api/crm/campaigns
Create campaign.
Request Body:
{
"name": "April Newsletter",
"subject": "Monthly Updates",
"content": "# Newsletter\n\nMarkdown content...",
"recipients": ["all"],
"status": "draft"
}
Response (201 Created):
{
"id": "uuid",
"message": "Campaign created successfully"
}
POST /api/crm/campaigns/[id]/send
Send campaign immediately or schedule for later.
Request Body:
{
"scheduled_at": "2026-04-01T09:00:00Z"
}
Response (200 OK):
{
"message": "Campaign scheduled successfully"
}
If scheduled_at is omitted, the campaign sends immediately. Otherwise, it’s queued for the specified time.
IAM Endpoints
Users
GET /api/iam/users
List users with role filtering.
Query Parameters:
page, limit, sort, order
department (string): Filter by department
emailVerified (boolean): Filter by verification status
Response (200 OK):
{
"data": [
{
"id": "uuid",
"email": "user@example.com",
"name": "John Doe",
"image": "/uploads/avatars/user.jpg",
"emailVerified": true,
"department": {
"id": "uuid",
"name": "IT"
},
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-03-30T00:00:00Z"
}
],
"pagination": { ... }
}
POST /api/iam/users
Create user account.
Request Body:
{
"email": "newuser@example.com",
"name": "Jane Smith",
"password": "generated_password",
"department_id": "uuid",
"emailVerified": false
}
Response (201 Created):
{
"id": "uuid",
"message": "User created successfully"
}
New users receive a welcome email with onboarding instructions via Resend integration.
PATCH /api/iam/users/[id]
Update user details.
Request Body:
{
"name": "Updated Name",
"department_id": "new_department_uuid"
}
Response (200 OK):
{
"message": "User updated successfully"
}
DELETE /api/iam/users/[id]
Delete user account.
Response (200 OK):
{
"message": "User deleted successfully"
}
Module Access
GET /api/iam/module-access/[userId]
Get user’s module permissions.
Response (200 OK):
{
"data": [
{
"id": "uuid",
"module": "cms",
"can_read": true,
"can_write": true,
"can_delete": false
},
{
"id": "uuid",
"module": "crm",
"can_read": true,
"can_write": false,
"can_delete": false
}
]
}
POST /api/iam/module-access
Grant module permissions to user.
Request Body:
{
"user_id": "uuid",
"module": "cms",
"can_read": true,
"can_write": true,
"can_delete": false
}
Response (201 Created):
{
"id": "uuid",
"message": "Module access granted successfully"
}
PATCH /api/iam/module-access/[id]
Update module permissions.
Request Body:
{
"can_write": true,
"can_delete": true
}
Response (200 OK):
{
"message": "Module access updated successfully"
}
Activity Logs
GET /api/iam/activity-logs
List audit trail with filtering.
Query Parameters:
page, limit, sort, order
user_id (uuid): Filter by user
module (string): Filter by module (cms, crm, iam)
action (string): Filter by action (create, update, delete)
date_from (ISO date): Filter by start date
date_to (ISO date): Filter by end date
Response (200 OK):
{
"data": [
{
"id": "uuid",
"user": {
"id": "uuid",
"name": "John Doe",
"email": "john@example.com"
},
"action": "update",
"module": "cms",
"resource": "projects",
"resource_id": "uuid",
"changes": {
"before": { "status": "active" },
"after": { "status": "completed" }
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2026-03-30T12:00:00Z"
}
],
"pagination": { ... }
}
Activity logs are automatically created by the system and cannot be manually added. They have a 90-day retention policy.
Cron Endpoints
POST /api/cron/activity-logs-retention
Clean up activity logs older than 90 days.
Headers:
Authorization: Bearer {CRON_SECRET}
Response (200 OK):
{
"deleted": 127,
"message": "Activity logs cleaned up successfully"
}
This endpoint is called automatically by Vercel Cron daily. Manual invocation requires the CRON_SECRET bearer token.
Error Responses
All endpoints return consistent error formats:
400 Bad Request
{
"error": "Validation error",
"details": {
"email": "Invalid email format"
}
}
401 Unauthorized
{
"error": "Authentication required"
}
403 Forbidden
{
"error": "Insufficient permissions"
}
404 Not Found
{
"error": "Resource not found"
}
500 Internal Server Error
{
"error": "Internal server error",
"message": "An unexpected error occurred"
}
Rate Limiting
Rate limiting (where implemented) is server-side to reduce abuse:
- Authentication endpoints: 5 requests per minute per IP
- Data modification (POST, PATCH, DELETE): 30 requests per minute per user
- Data retrieval (GET): 100 requests per minute per user
Rate limit headers are included in responses:
X-RateLimit-Limit: Maximum requests allowed
X-RateLimit-Remaining: Requests remaining
X-RateLimit-Reset: Time when limit resets (Unix timestamp)
Export Data
GET /api///export
Export data as CSV.
Example: /api/crm/leads/export?status=new
Response: CSV file download
Export is commonly available for:
- Leads
- Reservations
- Users
- Activity Logs
Next steps
Authentication guide
Sessions, Better Auth, and 2FA
Troubleshooting
HTTP errors, auth, and environment issues