This document provides comprehensive documentation for all API endpoints in the Betirement website.
The Betirement API provides endpoints for:
- Email newsletter subscriptions
- YouTube video data
- Social media integration
- Analytics and tracking
All API endpoints are located in the app/api/ directory and follow Next.js App Router conventions.
- Development:
http://localhost:3000/api - Production:
https://betirement.com/api
All API endpoints include:
- Protects against abuse and excessive requests
- Returns
429 Too Many Requestswhen limit exceeded - Includes
X-RateLimit-*headers with limit information
- Configured for cross-origin requests
- Supports preflight OPTIONS requests
- Customizable per endpoint
- Consistent error response format
- Appropriate HTTP status codes
- Detailed error messages in development
- All inputs are sanitized to prevent XSS and SQL injection
- Email validation
- Text length limits
- Honeypot spam protection
Subscribe a user to the email newsletter via ConvertKit.
Method: POST
Headers:
Content-Type: application/json
Body:
{
"email": "user@example.com",
"firstName": "John",
"tags": ["website-home", "bitcoin"],
"source": "homepage-hero"
}Parameters:
email(string, required): Valid email addressfirstName(string, optional): Subscriber's first nametags(array, optional): Array of tags to apply (max 10)source(string, optional): Signup source for tracking
Honeypot Fields (for spam protection):
website(string): Should be emptyphone_number(string): Should be emptycompany(string): Should be empty
Success (200):
{
"success": true,
"message": "Successfully subscribed to the newsletter!",
"subscriberId": "123456789"
}Already Subscribed (409):
{
"error": "This email is already subscribed"
}Invalid Email (400):
{
"error": "Invalid email address"
}Rate Limited (429):
{
"error": "Too many requests. Please try again later.",
"retryAfter": "2024-01-01T12:00:00.000Z"
}Server Error (500):
{
"error": "Failed to subscribe. Please try again later."
}- 10 requests per 10 minutes per IP address
const response = await fetch('/api/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'user@example.com',
firstName: 'John',
tags: ['website-home'],
source: 'homepage-hero'
}),
});
const data = await response.json();
if (data.success) {
console.log('Subscribed successfully!');
}Fetch YouTube videos from the channel with optional search.
Method: GET
Query Parameters:
maxResults(number, optional): Number of videos to return (1-50, default: 50)query(string, optional): Search query to filter videos
Success (200):
{
"success": true,
"videos": [
{
"id": "abc123",
"youtubeId": "dQw4w9WgXcQ",
"title": "Bitcoin Retirement Strategy",
"description": "Learn how to retire early with Bitcoin...",
"thumbnail": {
"default": "https://i.ytimg.com/vi/dQw4w9WgXcQ/default.jpg",
"medium": "https://i.ytimg.com/vi/dQw4w9WgXcQ/mqdefault.jpg",
"high": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg"
},
"publishedAt": "2024-01-01T12:00:00Z",
"duration": "PT10M30S",
"category": "bitcoin-fundamentals",
"tags": ["bitcoin", "retirement", "investing"],
"viewCount": 125000,
"likeCount": 8500
}
]
}Invalid Parameters (400):
{
"error": "maxResults must be a number between 1 and 50"
}Rate Limited (429):
{
"error": "Too many requests. Please try again later.",
"retryAfter": "2024-01-01T12:00:00.000Z"
}- 100 requests per 15 minutes per IP address
// Get all videos
const response = await fetch('/api/videos?maxResults=20');
const data = await response.json();
// Search videos
const searchResponse = await fetch('/api/videos?query=bitcoin&maxResults=10');
const searchData = await searchResponse.json();Get detailed information for a specific video.
Method: GET
Path Parameters:
id(string, required): YouTube video ID
Success (200):
{
"success": true,
"video": {
"id": "abc123",
"youtubeId": "dQw4w9WgXcQ",
"title": "Bitcoin Retirement Strategy",
"description": "Full description...",
"thumbnail": { /* ... */ },
"publishedAt": "2024-01-01T12:00:00Z",
"duration": "PT10M30S",
"category": "bitcoin-fundamentals",
"tags": ["bitcoin", "retirement"],
"viewCount": 125000,
"likeCount": 8500,
"transcript": "Video transcript if available..."
}
}Not Found (404):
{
"error": "Video not found"
}- 100 requests per 15 minutes per IP address
const response = await fetch('/api/videos/dQw4w9WgXcQ');
const data = await response.json();
console.log(data.video.title);Fetch Instagram posts (placeholder for future implementation).
Method: GET
Success (200):
{
"data": [
{
"id": "1",
"caption": "Bitcoin retirement strategy #1",
"media_url": "/images/placeholder-instagram-1.jpg",
"media_type": "IMAGE",
"permalink": "https://instagram.com/p/placeholder1",
"timestamp": "2024-01-01T12:00:00.000Z"
}
]
}- 100 requests per 15 minutes per IP address
To implement live Instagram integration:
- Create a Facebook App at developers.facebook.com
- Add Instagram Basic Display product
- Configure OAuth redirect URIs
- Generate User Access Token
- Add
INSTAGRAM_ACCESS_TOKENto environment variables - Update the endpoint implementation in
app/api/instagram/route.ts
Get social media follower counts (placeholder for future implementation).
Method: GET
Query Parameters:
platform(string, optional): Specific platform (youtube, twitter, instagram, linkedin)
All Platforms (200):
{
"counts": {
"youtube": 125000,
"twitter": 45000,
"instagram": 32000,
"linkedin": 18000
},
"lastUpdated": "2024-01-01T12:00:00.000Z"
}Single Platform (200):
{
"platform": "youtube",
"count": 125000,
"lastUpdated": "2024-01-01T12:00:00.000Z"
}- 100 requests per 15 minutes per IP address
To implement live follower counts:
YouTube:
- Use existing
YOUTUBE_API_KEY - Endpoint:
https://www.googleapis.com/youtube/v3/channels - Get
statistics.subscriberCount
Twitter:
- Get Bearer Token from developer.twitter.com
- Add
TWITTER_BEARER_TOKENto environment variables - Endpoint:
https://api.twitter.com/2/users/by/username/:username - Get
public_metrics.followers_count
Instagram:
- Requires Facebook App with Instagram Graph API
- Add
INSTAGRAM_ACCESS_TOKENto environment variables - Endpoint:
https://graph.instagram.com/{user-id}?fields=followers_count
LinkedIn:
- Requires OAuth 2.0 authentication
- More complex setup, consider manual updates
The API uses three rate limit presets:
STRICT (10 requests per 10 minutes):
- Used for: Email subscriptions
- Prevents: Spam and abuse
STANDARD (50 requests per 10 minutes):
- Used for: Form submissions
- Balances: Protection and usability
RELAXED (100 requests per 15 minutes):
- Used for: Public content (videos, social data)
- Allows: Frequent browsing
Rate limit information is included in response headers:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1704110400000
When rate limited (429 response):
- Check
retryAfterin response body - Wait until the specified time
- Implement exponential backoff
- Show user-friendly error message
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const data = await response.json();
const retryAfter = new Date(data.retryAfter);
const waitTime = retryAfter.getTime() - Date.now();
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, waitTime));
continue;
}
}
return response;
}
}All errors follow a consistent format:
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": { /* Additional context */ }
}200- Success400- Bad Request (invalid input)401- Unauthorized (missing/invalid auth)404- Not Found409- Conflict (e.g., already subscribed)413- Payload Too Large429- Too Many Requests (rate limited)500- Internal Server Error
async function apiCall(endpoint, options) {
try {
const response = await fetch(endpoint, options);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Request failed');
}
return data;
} catch (error) {
console.error('API Error:', error);
// Show user-friendly error message
throw error;
}
}All inputs are validated and sanitized:
- Email: RFC 5322 compliant validation
- Text: Length limits, XSS prevention
- Numbers: Range validation
- Arrays: Type checking, length limits
Multiple layers of spam protection:
- Honeypot Fields: Hidden fields that bots fill
- Rate Limiting: Prevents automated abuse
- Input Sanitization: Removes malicious content
- IP Tracking: Monitors suspicious activity
CORS is configured per endpoint:
Strict (subscribe endpoint):
- Allowed origins: Same origin only
- Credentials: Not allowed
- Methods: POST, OPTIONS
Default (public endpoints):
- Allowed origins: All
- Credentials: Not allowed
- Methods: GET, OPTIONS
# Start development server
npm run dev
# Test subscribe endpoint
curl -X POST http://localhost:3000/api/subscribe \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com"}'
# Test videos endpoint
curl http://localhost:3000/api/videos?maxResults=5# Test production endpoints
curl https://betirement.com/api/videos
# Test with authentication
curl -X POST https://betirement.com/api/subscribe \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com"}'API calls are logged with:
- Timestamp
- Endpoint
- IP address (hashed in production)
- Response status
- Error details (if any)
Track API usage:
- Request count per endpoint
- Error rates
- Response times
- Rate limit hits
Planned API improvements:
- GraphQL endpoint for flexible queries
- WebSocket support for real-time updates
- API versioning (v1, v2)
- OAuth authentication
- Webhook support
- Batch operations
- API documentation UI (Swagger/OpenAPI)
For API issues or questions:
- Check this documentation
- Review error messages and status codes
- Check environment variables configuration
- Review API provider documentation (YouTube, ConvertKit)
- Check rate limit status