Echo is an AI-powered journaling companion that helps users track their mood, create journal entries, and receive personalized insights. This document provides comprehensive API documentation for the Echo platform.
Base URL: https://echojournal.life/api
Echo uses custom JWT (JSON Web Tokens) for authentication. All protected endpoints require a Bearer token in the Authorization header or a session cookie.
Authorization: Bearer <jwt_token>
Access tokens are obtained through login and are included in the session object.
Register a new user account.
Request Body: multipart/form-data
{
name: string; // Required: User's full name
email: string; // Required: Valid email address
password: string; // Required: Minimum 6 characters
image?: File; // Optional: Profile image file
}Response: 200 OK
{
"message": "User registered successfully. Please check your email for welcome message."
}Errors:
400: Missing required fields, password too short, or user already exists500: Server error
Authenticate user and get JWT token.
Request Body: application/json
{
email: string; // Required: User email
password: string; // Required: User password
}Response: 200 OK
{
"token": "jwt_token_string",
"message": "Login successful."
}Errors:
400: Missing email or password401: Invalid credentials500: Server error
Send password reset code to user's email.
Request Body: application/json
{
email: string; // Required: User email
}Response: 200 OK
{
"message": "Password reset code sent to your email."
}Reset user password using reset code.
Request Body: application/json
{
email: string; // Required: User email
code: string; // Required: Reset code from email
newPassword: string; // Required: New password (min 6 chars)
}Create a new mood entry with AI analysis.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
content: string; // Required: Journal entry text
imgUrl?: string; // Optional: Image URL for entry
}Response: 200 OK
{
"_id": "entry_id",
"userId": "user_id",
"mood": "happy",
"score": 8,
"comment": "It sounds like you're having a wonderful day!",
"content": "encrypted_content",
"imgUrl": "image_url",
"todo": ["suggestion1", "suggestion2"],
"createdAt": "2024-01-01T00:00:00.000Z"
}Features:
- AI mood analysis via the configured OpenAI-compatible provider
- Automatic mood scoring (1-10 scale)
- Supportive AI comments
- Todo suggestions based on mood
- Content encrypted at rest (AES-256-GCM, server-held key)
Get user's mood tracking data and badge progress.
Headers: Authorization: Bearer <token>
Response: 200 OK
[
{
"mood": "happy",
"score": 8,
"_id": "mood_id",
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Features:
- Badge system with 5 achievement levels
- Email notifications for new badges
- Mood pattern analysis
Get personalized journaling analytics for the authenticated user.
Headers: Authorization: Bearer <token>
Query Parameters:
range?: 'week' | 'month' | 'year' // Default: 'week'
Response: 200 OK
{
"stats": { "totalEntries": 12, "allTimeEntries": 87, "currentStreak": 4, "bestStreak": 9, "totalXp": 340, "avgWordCount": 68 },
"moodTimeline": [{ "day": "Mon", "date": "2026-03-01", "mood": "happy", "score": 8 }],
"writingTrend": [{ "label": "Mar 1", "count": 1 }],
"weeklyEntries": [{ "label": "W1", "count": 5 }],
"topTopics": [{ "topic": "Work", "count": 3 }],
"commonWords": [{ "word": "grateful", "frequency": 4 }],
"activityCalendar": [{ "date": "2026-03-01", "hasEntry": true }],
"aiInsights": ["π₯ You're on a roll this week!", "π‘ Try writing about your goals more often."],
"writingTrendComparison": "+23%",
"badgeProgress": { "earned": ["Echo Sunshine"], "nextBadge": "Pen Whisperer", "nextBadgeAt": 7, "entriesUntilNext": 3, "milestones": [] },
"xpStatus": { "totalXp": 340, "currentStreak": 4, "maxStreak": 9, "subscription": "free" },
"moodTrend": { "baselineMean": 6.2, "baselineStdDev": 1.1, "recentMean": 4.8, "zScore": -1.27, "direction": "declining", "alert": false },
"linguisticSignal": {
"timeline": [{ "date": "2026-03-01", "wordCount": 62, "absolutistRatio": 0.02, "firstPersonRatio": 0.08, "negationRatio": 0.03 }],
"average": { "absolutistRatio": 0.02, "firstPersonRatio": 0.08, "negationRatio": 0.03 }
}
}Features:
- AI insights via GPT-4o-mini, cached once per day per user
moodTrend: statistical (non-LLM) z-score comparison of recent mood scores against the user's own baseline β flagsalert: trueonly on a real decline, not routine day-to-day variationlinguisticSignal: deterministic (non-LLM) absolutist / first-person / negation word-ratio markers computed from entry plaintext at read time (never stored)activityCalendaralways spans a full year regardless ofrange
Submit a PHQ-9 (depression) or GAD-7 (anxiety) self-report screening. Scoring is deterministic and follows the standard published cutoffs β these are validated screening instruments, not a diagnosis.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
type: 'phq9' | 'gad7';
answers: number[]; // 9 items (phq9) or 7 items (gad7), each 0-3
}Response: 201 Created
{
"id": "screening_id",
"type": "phq9",
"totalScore": 12,
"severity": "moderate"
}Features:
- Severity bands:
minimal|mild|moderate|moderately-severe|severe - PHQ-9 item 9 (self-harm/suicidal ideation) always feeds the crisis-response path (see Risk Detection) independent of the total score
- Errors:
400invalidtypeoranswersdon't match the instrument's question count / 0-3 range,401unauthorized
Get the authenticated user's screening history (most recent first, max 50).
Headers: Authorization: Bearer <token>
Query Parameters:
type?: 'phq9' | 'gad7' // Filter to one instrument
Response: 200 OK
{
"history": [
{ "type": "phq9", "totalScore": 12, "severity": "moderate", "createdAt": "2026-03-01T00:00:00.000Z" }
]
}Retrieve user's journal entries with search functionality.
Headers: Authorization: Bearer <token>
Query Parameters:
search?: string; // Optional: Search in content and moodResponse: 200 OK
[
{
"_id": "entry_id",
"userId": "user_id",
"mood": "happy",
"score": 8,
"comment": "AI supportive comment",
"content": "decrypted_content",
"imgUrl": "image_url",
"todo": ["todo_item"],
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Features:
- Automatic content decryption
- Search across mood and content
- Sorted by creation date (newest first)
Get a specific journal entry by ID.
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"_id": "entry_id",
"userId": "user_id",
"mood": "happy",
"score": 8,
"comment": "AI supportive comment",
"content": "decrypted_content",
"imgUrl": "image_url",
"createdAt": "2024-01-01T00:00:00.000Z"
}Delete a specific journal entry.
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"message": "Entry deleted successfully"
}Update a specific journal entry.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
mood?: string;
score?: number;
comment?: string;
content?: string;
imgUrl?: string;
}Send a message to Echo AI companion.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
message: string; // Required: User message
chatId?: string; // Optional: Existing chat ID
}Response: 200 OK
{
"message": "AI response text",
"chatId": "chat_session_id"
}Features:
- Context-aware conversations
- Empathetic AI responses
- Chat session management
- Previous conversation summaries
Get user's chat history.
Headers: Authorization: Bearer <token>
Response: 200 OK
[
{
"_id": "chat_id",
"userId": "user_id",
"messages": [
{
"role": "user",
"text": "Hello",
"timestamp": "2024-01-01T00:00:00.000Z"
},
{
"role": "ai",
"text": "Hi there! How are you feeling today?",
"timestamp": "2024-01-01T00:00:00.000Z"
}
],
"threadSummary": "User greeting conversation",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
]Get a specific chat session.
Headers: Authorization: Bearer <token>
Get user profile information.
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"success": true,
"user": {
"_id": "user_id",
"name": "User Name",
"email": "user@example.com",
"image": "profile_image_url",
"subscription": "free",
"badge": ["Echo Sunshine", "Pen Whisperer"],
"wantsWeeklyReport": true,
"createdAt": "2024-01-01T00:00:00.000Z"
}
}Update user profile information.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
name?: string;
image?: string;
currentPassword?: string; // Required if changing password
newPassword?: string; // Requires currentPassword
wantsWeeklyReport?: boolean;
}Get user's todo items.
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"todos": [
{
"_id": "todo_id",
"userId": "user_id",
"todo": "Take a walk",
"type": "mood_suggestion",
"status": "pending",
"createdAt": "2024-01-01T00:00:00.000Z"
}
]
}Update todo item status.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
status: "completed" | "pending" | "cancelled";
}Delete a todo item.
Headers: Authorization: Bearer <token>
Retrieve published blog posts.
Query Parameters:
all?: string; // Optional: If "true", returns all posts (Admin only)Response: 200 OK
[
{
"_id": "post_id",
"title": "Post Title",
"slug": "post-slug",
"content": "Post content...",
"excerpt": "Short summary",
"coverImage": "image_url",
"author": "Admin",
"tags": ["tag1", "tag2"],
"published": true,
"createdAt": "2024-01-01T00:00:00.000Z"
}
]Create a new blog post.
Headers: Authorization: Bearer <token>
Required: Admin subscription level
Request Body: application/json
{
title: string;
slug: string;
content: string;
excerpt: string;
coverImage?: string;
tags?: string[];
published?: boolean;
}Get a specific blog post by ID or slug.
Response: 200 OK
Update a blog post.
Headers: Authorization: Bearer <token>
Required: Admin subscription level
Delete a blog post.
Headers: Authorization: Bearer <token>
Required: Admin subscription level
Check drawing status and rate limits for the current user.
Headers: Authorization: Bearer <token>
Response: 200 OK
{
"drawCount": 1,
"canDraw": true,
"requiresMessage": true,
"nextAvailableAt": "2024-01-01T00:05:00.000Z"
}Record a drawing activity in the sanctuary.
Headers: Authorization: Bearer <token>
Features:
- Rate limited to 2 draws every 5 minutes.
- Second draw requires posting a positive message first.
Retrieve a random positive message from another user.
Headers: Authorization: Bearer <token>
Post a new positive message to the sanctuary.
Headers: Authorization: Bearer <token>
Request Body: application/json
{
content: string; // Required: Message text (min 5 chars)
}Get the top contributors to the Ethereal Sanctuary.
Response: 200 OK
{
"data": [
{
"_id": "user_id",
"count": 15,
"name": "User Name",
"image": "profile_image_url"
}
]
}Get admin dashboard statistics.
Headers: Authorization: Bearer <token>
Required: Admin subscription level
Response: 200 OK
{
"users": [
{
"_id": "user_id",
"name": "User Name",
"email": "user@example.com",
"subscription": "free",
"entryCount": 25,
"createdAt": "2024-01-01T00:00:00.000Z"
}
],
"entries": 1250,
"mood": [
{
"mood": "happy",
"_id": "mood_id"
}
]
}Update user information (admin only).
Delete user account (admin only).
Send weekly mood reports to users.
Features:
- Sends personalized HTML email reports
- Includes mood statistics and insights
- Respects user preferences (
wantsWeeklyReport) - Rate-limited for email service
Send daily journaling reminders.
interface IUser {
name: string;
email: string; // Unique
password: string; // Hashed with bcrypt
image: string; // Profile image URL
subscription: 'free' | 'plus' | 'admin';
badge: string[]; // Achievement badges
resetPasswordCode?: string;
resetPasswordExpires?: Date;
wantsWeeklyReport?: boolean;
createdAt: Date;
updatedAt: Date;
}interface IMood {
userId: ObjectId; // References User
mood: string; // AI-analyzed mood
score: number; // Mood score (1-10)
comment: string; // AI supportive comment
content: string; // Encrypted journal entry
imgUrl?: string; // Optional image
todo?: string[]; // AI suggestions
createdAt: Date;
}interface IChat {
userId: string;
messages: IMessage[];
threadSummary?: string; // AI-generated summary
createdAt: Date;
updatedAt: Date;
}
interface IMessage {
role: 'user' | 'ai';
text: string;
timestamp: Date;
}interface ITodo {
userId: ObjectId;
todo: string;
type: 'mood_suggestion' | 'manual';
status: 'pending' | 'completed' | 'cancelled';
createdAt: Date;
}interface IScreening {
userId: ObjectId;
type: 'phq9' | 'gad7';
answers: number[];
totalScore: number;
severity: 'minimal' | 'mild' | 'moderate' | 'moderately-severe' | 'severe';
createdAt: Date;
}interface IPost {
title: string;
slug: string;
content: string;
excerpt: string;
coverImage: string;
author: string;
tags: string[];
published: boolean;
createdAt: Date;
updatedAt: Date;
}interface ISpaceMessage {
content: string;
author: ObjectId;
createdAt: Date;
updatedAt: Date;
}interface ISpaceDraw {
user: ObjectId;
timestamp: Date;
contributionId?: ObjectId; // Link to message that unlocked draw
}Echo features a progressive badge system to encourage consistent journaling:
- Echo Sunshine - Default badge (0+ entries)
- Pen Whisperer - 7+ entries
- Mindful Scribe - 30+ entries
- Thought Architect - 45+ entries
- Guardian of Inked Wisdom - 60+ entries
- Journal content encrypted at rest with AES-256-GCM under a server-held key (not end-to-end β see SECURITY.md)
- Encrypted data stored in MongoDB
- Automatic decryption on retrieval
- JWT tokens with 30-day expiration
- Secure password hashing with bcrypt
- Session management via signed JWT cookies (
jose)
- Route-level authentication checks
- User-specific data isolation
- Admin-only endpoint protection
- A
RiskAlertis recorded whenever a journal entry or/screeningsubmission carries a risk indicator (e.g. PHQ-9 item 9 self-harm ideation) - High severity notifies the user immediately (subject to a 6-hour cooldown); low/moderate severity notifies once 3+ flags land within a 14-day window
- Notification is a push message pointing to crisis resources (findahelpline.com, 988 in the US) β never a diagnosis or clinical claim
- This logic fails soft: an error here is logged, never allowed to block saving the entry/screening
- Email services respect provider limits (Resend: 2 req/sec)
- Batch processing for bulk operations
- Graceful error handling for rate limits
All endpoints return consistent error responses:
{
"error": "Error message",
"message": "Detailed error description"
}Common HTTP status codes:
200- Success400- Bad Request (validation errors)401- Unauthorized (missing/invalid token)403- Forbidden (insufficient permissions)404- Not Found500- Internal Server Error
- Any OpenAI-compatible endpoint - configured via
AI_BASE_URL/AI_MODEL - OpenRouter - the hosted default when only
OPENROUTER_API_KEYis set - Local inference (Ollama, llama.cpp, vLLM) - point
AI_BASE_URLat it
- Resend - Email delivery for reports and notifications
- UI Avatars - Default profile image generation
- Google OAuth - Social authentication
JWT_SECRET=your_jwt_secret
ENCRYPTION_SECRET_KEY=your_encryption_key
CRON_SECRET=your_cron_secret
OPENROUTER_API_KEY=your_openrouter_key # or AI_BASE_URL / AI_MODEL / AI_API_KEY
RESEND_API_KEY=your_resend_key
MONGODB_URI=your_mongodb_connection
GOOGLE_CLIENT_ID=your_google_oauth_id
GOOGLE_CLIENT_SECRET=your_google_oauth_secret- Framework: Next.js 16 (App Router)
- Database: MongoDB with Mongoose ODM
- Authentication: Custom JWT with
jose - AI: Any OpenAI-compatible provider (OpenRouter by default)
- Email: Resend
- Encryption: Node.js crypto module (AES-256-GCM, CBC read-only for legacy data)
- Deployment: Vercel
This API documentation provides a complete reference for integrating with the Echo platform's backend services.