API Documentation

Welcome to the Disciply API documentation. This guide will help you integrate with our RESTful API to manage discipleship programs, groups, challenges, and more.


Getting Started

The Disciply API provides programmatic access to your discipleship management platform. To get started with the API, you'll need to:

  1. Register for an account at https://disciply.app/register
  2. Log in to your Disciply account
  3. Subscribe to a Pro plan - API access requires an active Pro subscription
  4. Generate your API token - Follow the authentication steps below
Note: All API requests must be made over HTTPS. Requests made over plain HTTP will fail. API requests without authentication will also fail.

Base URL

https://disciply.app/api

Authentication

The Disciply API uses Laravel Sanctum for authentication. You'll need to include a Bearer token in the Authorization header of your API requests.

Generating Your API Token

To generate a Bearer token for API access:

  1. Log in to your account by making a POST request to the login endpoint:
POST /api/login
POST https://disciply.app/api/login
Request Body
{ "email": "your-email@example.com", "password": "your-password" }
Response
{ "user": { "id": 1, "name": "John Doe", "email": "your-email@example.com" }, "token": "1|abcdefghijklmnopqrstuvwxyz1234567890" }

Using Your API Token

Include the token in the Authorization header of all API requests:

Authorization: Bearer 1|abcdefghijklmnopqrstuvwxyz1234567890

Example Request with cURL

curl -X GET https://disciply.app/api/groups \ -H "Authorization: Bearer YOUR_TOKEN_HERE" \ -H "Content-Type: application/json" \ -H "Accept: application/json"

Groups API

Manage discipleship groups, memberships, and group activities.

🔄 Important Update - October 2025: The Groups API has been updated to include a new group_type field. All groups now have a type of either 'group' (regular groups) or 'global' (organization-wide groups). Please update your mobile apps to handle these types appropriately.
🔒 New Feature - December 2025: Groups now support open/closed join types. Closed groups require admin approval before members can join. See the join_type field and join request endpoints below.

Group Types

Groups in Disciply are now categorized into two types:

  • Regular Groups (group_type: 'group'): Traditional small groups with specific members, leaders, and full functionality
  • Global Groups (group_type: 'global'): Organization-wide groups where all members of an organization are automatically enrolled

Global Group Characteristics

Feature Regular Groups Global Groups
Membership Invite/join based Auto-enrolled (all org members)
Who can post All members Organization admins only
Comments allowed Yes No
Member list visible Yes Count only (privacy)
Can edit/delete Yes (with permissions) No (system-managed)
Displayed in app Mixed with other groups Sorted to top of list
Mobile Implementation Note: Global groups should be displayed separately at the top of your groups list in a dedicated "Global" or "Organization-wide" section. They function as announcement boards where only admins can post updates to all members.
GET List All Groups
GET /api/groups

Retrieve a list of all groups the authenticated user is a member of. Global groups are automatically included and sorted to the top.

Response Example
{ "data": [ { "id": 25, "name": "General", "code": "general-abc123", "purpose": "Welcome! This is our messaging board for all members.", "organization_id": 1, "group_type": "global", "join_type": "open", "type": null, "status": "active", "member_count": 45, "leadership": { "id": 5, "name": "Jane Smith", "email": "jane@example.com" }, "created_at": "2024-01-15T10:30:00.000000Z" }, { "id": 12, "name": "Youth Discipleship Group", "code": "YDG-xyz789", "purpose": "Weekly meeting for young adults", "organization_id": 1, "group_type": "group", "join_type": "closed", "type": "Discipleship", "status": "active", "member_count": 12, "leadership": { "id": 8, "name": "John Doe", "email": "john@example.com" }, "created_at": "2024-02-20T14:30:00.000000Z" } ] }
Important Fields
Field Type Description
group_type string NEW: Either 'group' or 'global'. Use this to determine group behavior.
join_type string NEW: Either 'open' or 'closed'. Closed groups require admin approval to join.
type string|null Group category (e.g., "Discipleship", "Bible Study"). Only used for regular groups.
status string Group status: 'active', 'draft', or 'inactive'
Breaking Change: The legacy virtual group (ID: 0) is no longer returned by this endpoint. All organizations now have a real global group with an actual database ID. Update your code to remove any special handling for group ID 0.
GET Get Available Groups
GET /api/groups/available

Retrieve a list of groups available for the user to join.

GET Get Single Group
GET /api/groups/{group}

Retrieve detailed information about a specific group.

Parameters
Parameter Type Required Description
group integer REQUIRED The ID of the group
POST Create Group
POST /api/groups

Create a new discipleship group.

Request Body
{ "name": "New Group Name", "description": "Group description", "organization_id": 1, "type": "discipleship" }
PUT Update Group
PUT /api/groups/{group}

Update an existing group's information.

Parameters
Parameter Type Required Description
group integer REQUIRED The ID of the group
name string OPTIONAL Updated group name
description string OPTIONAL Updated description
DELETE Delete Group
DELETE /api/groups/{group}

Delete a group. Requires group leader or admin privileges.

POST Join Group
POST /api/groups/join

Join a discipleship group using a join code. For open groups, the user is immediately added. For closed groups, the response indicates approval is required.

Request Body
{ "joinCode": "ABC123" }
Response - Open Group (Success 200)
{ "message": "User has joined the group", "group_id": 123, "organization_id": 456 }
Response - Closed Group (422)
{ "message": "This group requires approval to join", "error_type": "closed_group", "group_id": 123, "group": { "id": 123, "name": "Youth Group", "purpose": "Weekly youth fellowship...", "location": "Church Building Room 101" } }
Response - Existing Pending Request (422)
{ "message": "You already have a pending request for this group", "error_type": "request_pending", "group_id": 123, "request_id": 789 }
Implementation Flow
  1. User scans or enters group join code
  2. Call this endpoint with the join code
  3. If error_type === 'closed_group', show join request form (see Submit Join Request endpoint)
  4. If successful (200), user has joined and can access the group
POST Submit Join Request
POST /api/groups/join-request

Submit a request to join a closed group. The group admin will need to approve before the user can join.

Request Body
{ "group_id": 123, "note": "I would love to join this group because..." // optional, max 1000 chars }
Response (Success 201)
{ "message": "Join request submitted successfully", "request": { "id": 789, "user_id": 456, "group_id": 123, "organization_id": 1, "note": "I would love to join...", "status": "pending", "created_at": "2025-12-10T19:30:00.000000Z", "updated_at": "2025-12-10T19:30:00.000000Z" } }
Parameters
Parameter Type Required Description
group_id integer REQUIRED The ID of the group to request joining
note string OPTIONAL Optional message to the group admin (max 1000 characters)
GET Get Join Requests for Group
GET /api/groups/{groupId}/join-requests

Retrieve all pending join requests for a group. Requires admin privileges.

Response (Success 200)
[ { "id": 789, "user_id": 456, "group_id": 123, "organization_id": 1, "note": "I would love to join this group...", "status": "pending", "reviewed_by": null, "reviewed_at": null, "created_at": "2025-12-10T19:30:00.000000Z", "updated_at": "2025-12-10T19:30:00.000000Z", "user": { "id": 456, "name": "John Doe", "email": "john@example.com" } } ]
POST Accept Join Request
POST /api/groups/join-requests/{requestId}/accept

Accept a user's request to join a closed group. Requires admin privileges.

Response (Success 200)
{ "message": "Join request accepted successfully", "request": { "id": 789, "user_id": 456, "group_id": 123, "organization_id": 1, "note": "I would love to join...", "status": "accepted", "reviewed_by": 1, "reviewed_at": "2025-12-10T20:00:00.000000Z", "created_at": "2025-12-10T19:30:00.000000Z", "updated_at": "2025-12-10T20:00:00.000000Z" } }
Side Effects
  • User is added to the group
  • User is attached to the organization
  • User receives acceptance email notification
  • User is auto-enrolled in organization's global group
Error Response - Group Full (422)
{ "message": "This group is full (50/50 members)", "error_type": "group_full" }
POST Reject Join Request
POST /api/groups/join-requests/{requestId}/reject

Reject a user's request to join a closed group. Requires admin privileges.

Response (Success 200)
{ "message": "Join request rejected successfully", "request": { "id": 789, "user_id": 456, "group_id": 123, "organization_id": 1, "note": "I would love to join...", "status": "rejected", "reviewed_by": 1, "reviewed_at": "2025-12-10T20:00:00.000000Z", "created_at": "2025-12-10T19:30:00.000000Z", "updated_at": "2025-12-10T20:00:00.000000Z" } }
Side Effects
  • User receives rejection email notification
POST Leave Group
POST /api/groups/{group}/leave

Leave a discipleship group.

GET Get Group Members
GET /api/groups/{group}/members

Retrieve a list of all members in a group.

Response Example
{ "data": [ { "id": 1, "name": "John Doe", "email": "john@example.com", "role": "member", "joined_at": "2024-01-20T14:30:00.000000Z" } ] }
GET Get Group Posts
GET /api/groups/{group}/posts

Retrieve all posts and discussions within a group.

POST Create Group Post
POST /api/groups/{id}/post

Create a new post in a group.

⚠️ Global Group Restriction: For groups with group_type: 'global', only organization administrators can create posts. Regular members attempting to post will receive a 403 Forbidden error.
Request Body
{ "content": "This is my post content", "type": "text" }
Error Response (Global Group - Non-Admin)
{ "error": "Only organization administrators can post to global groups" }
Mobile Implementation Tips
  • Check the group_type field before showing the "Create Post" button
  • For global groups, verify the user is an org admin before allowing post creation
  • Display a helpful message like "Only administrators can post announcements to this group"
POST Comment on Group Post
POST /api/groups/{group_id}/post/comment

Add a comment to a group post.

⚠️ Global Group Restriction: Comments are disabled for all global group posts. Attempting to comment will return a 403 Forbidden error.
Request Body
{ "post_id": 123, "comment": "Great post!", "parent_id": null }
Error Response (Global Group)
{ "error": "Comments are not allowed on global group posts" }
Mobile Implementation Tips
  • Hide the comment input box for posts in global groups
  • Check group_type === 'global' before showing comment UI
  • Display a note like "Comments are disabled for organization-wide announcements"
POST Rate Group
POST /api/groups/{group}/rate

Submit a rating for a group.

Request Body
{ "rating": 5, "comment": "Great group experience!" }

Challenges API

Manage discipleship challenges and track user progress through various courses and activities.

GET List All Challenges
GET /api/challenges

Retrieve a list of all available challenges.

Response Example
{ "data": [ { "id": 1, "title": "30-Day Prayer Challenge", "description": "Develop a consistent prayer habit", "duration_days": 30, "difficulty": "beginner", "participants_count": 145 } ] }
GET Get Available Challenges
GET /api/challenges/get_challenges

Retrieve challenges available for the authenticated user.

POST Create Challenge
POST /api/challenges

Create a new challenge. Requires leadership or admin privileges.

Request Body
{ "title": "Bible Reading Challenge", "description": "Read through the entire Bible in 90 days", "duration_days": 90, "difficulty": "intermediate", "organization_id": 1 }
GET Get User Challenge Progress
GET /api/challenges/progress

Retrieve the authenticated user's progress across all challenges.

Response Example
{ "data": [ { "challenge_id": 1, "title": "30-Day Prayer Challenge", "progress_percentage": 73, "days_completed": 22, "days_total": 30, "started_at": "2024-01-01T00:00:00.000000Z" } ] }
POST Start Challenge
POST /api/challenges/start/{challenge}

Enroll the authenticated user in a challenge.

Parameters
Parameter Type Required Description
challenge integer REQUIRED The ID of the challenge
POST Complete Challenge
POST /api/challenges/complete/{challenge}

Mark a challenge as completed.

Parameters
Parameter Type Required Description
challenge integer REQUIRED The ID of the challenge
GET Get Challenge Statistics
GET /api/manage/challenges/stats

Retrieve statistics for challenges (admin/leader only).

Response Example
{ "total_challenges": 15, "active_participants": 342, "completion_rate": 67.5, "most_popular": { "id": 1, "title": "30-Day Prayer Challenge", "participants": 145 } }
GET Get Challenge Completions
GET /api/manage/challenges/completions

Retrieve a list of recent challenge completions (admin/leader only).


Journal API

Manage personal journal entries for spiritual growth and reflection.

GET List Journal Entries
GET /api/journal

Retrieve all journal entries for the authenticated user.

Response Example
{ "data": [ { "id": 1, "title": "Morning Devotion Reflection", "content": "Today I reflected on...", "created_at": "2024-10-14T08:30:00.000000Z", "updated_at": "2024-10-14T08:30:00.000000Z" } ] }
POST Create Journal Entry
POST /api/journal

Create a new journal entry.

Request Body
{ "title": "My Journal Entry", "content": "Today I learned about...", "date": "2024-10-14" }
Parameters
Parameter Type Required Description
title string REQUIRED Title of the journal entry
content string REQUIRED Content of the journal entry
date date OPTIONAL Date of the entry (defaults to today)
GET Get Single Journal Entry
GET /api/journal/{journal}

Retrieve a specific journal entry.

Parameters
Parameter Type Required Description
journal integer REQUIRED The ID of the journal entry
POST Update Journal Entry
POST /api/journal/{journal}

Update an existing journal entry.

Request Body
{ "title": "Updated Title", "content": "Updated content...", "_method": "PUT" }
POST Delete Journal Entry
POST /api/journal/{journal}/delete

Delete a journal entry.


Journey AI API

Leverage AI-powered insights and guidance for your spiritual journey.

GET Get Journey Data
GET /api/journey

Retrieve the user's spiritual journey data and milestones.

Response Example
{ "data": { "total_days": 127, "milestones_completed": 8, "current_focus": "Prayer and Meditation", "journey_data": [ { "date": "2024-10-14", "activity": "Morning Prayer", "duration_minutes": 30 } ] } }
POST Create Journey Entry
POST /api/journey

Log a new activity or milestone in your spiritual journey.

Request Body
{ "activity_type": "prayer", "duration_minutes": 30, "notes": "Focused on gratitude today", "date": "2024-10-14" }
Parameters
Parameter Type Required Description
activity_type string REQUIRED Type of activity (prayer, study, service, etc.)
duration_minutes integer OPTIONAL Duration in minutes
notes string OPTIONAL Additional notes or reflections
date date OPTIONAL Date of the activity (defaults to today)
GET Get Journey Progress
GET /api/journey/progress

Retrieve AI-powered insights and progress analytics for your journey.

Response Example
{ "data": { "overall_progress": 67, "consistency_score": 8.5, "areas_of_growth": [ "Prayer consistency improved by 45%", "Community engagement increased" ], "ai_recommendations": [ "Consider joining a prayer group", "Continue morning devotion habit" ], "streak_days": 14 } }

Training API

Manage training programs, qualifications, and leadership development with rich media content and AI-generated assessments.

🎓 Enhanced Training Features: Training programs now support multimedia content (audio files, video links), series organization, and AI-generated multiple-choice quizzes for comprehensive assessment.
GET Get Available Qualifications
GET /api/qualifications/get_qualifications

Retrieve all available training qualifications and certifications with full media and assessment details.

Response Example
{ "data": [ { "id": 1, "title": "Small Group Leader Certification", "type": "Leadership Training", "series": "Discipleship 101", "content": "Comprehensive training for leading small groups...", "audio_path": "/storage/qualifications/audio/1/training.mp3", "video_link": "https://youtube.com/watch?v=abc123", "quiz_type": "multiple_choice", "quiz_data": { "questions": [ { "question": "What is the primary role of a small group leader?", "options": [ "To teach only", "To facilitate discussion and spiritual growth", "To enforce rules", "To manage finances" ], "correct_answer_index": 1 } ] }, "visibility": "everyone", "is_required_for_leadership": true, "organization_id": 1, "created_at": "2024-10-14T08:30:00.000000Z" } ] }
Important Fields
Field Type Description
series string|null NEW: Optional series name to group related training programs
audio_path string|null NEW: Path to audio training resource (MP3, WAV, M4A, AAC, OGG)
video_link string|null NEW: URL to video resource (YouTube, Vimeo, Dailymotion, Wistia)
quiz_type string|null NEW: Type of assessment: 'completion_consent' or 'multiple_choice'
quiz_data object|null NEW: Quiz questions with options and correct answers (for multiple_choice type)
visibility string Who can view: 'everyone', 'assigned_only', 'leadership_only', 'members_only'
is_required_for_leadership boolean Whether completion is required to be qualified as a group leader
Quiz Types
  • completion_consent: User simply confirms they have completed the training
  • multiple_choice: AI-generated quiz based on training content with multiple-choice questions
GET Get User Qualifications Progress
GET /api/qualifications/progress

Retrieve the authenticated user's progress across all qualifications, including quiz scores and completion status.

Response Example
{ "data": [ { "qualification_id": 1, "title": "Small Group Leader Certification", "type": "Leadership Training", "series": "Discipleship 101", "progress_percentage": 75, "quiz_score": 85, "quiz_passed": true, "completed": false, "started_at": "2024-08-01T00:00:00.000000Z", "completed_at": null, "has_audio": true, "has_video": true, "quiz_type": "multiple_choice" } ] }
Progress Fields
Field Type Description
quiz_score integer|null User's score on the quiz (0-100) if quiz completed
quiz_passed boolean Whether the user passed the required quiz
has_audio boolean Indicates if training includes audio content
has_video boolean Indicates if training includes video content
quiz_type string|null Type of assessment required
POST Start Qualification
POST /api/qualifications/start/{qualification}

Enroll the authenticated user in a training qualification.

Parameters
Parameter Type Required Description
qualification integer REQUIRED The ID of the qualification
POST Update Qualification Progress
POST /api/qualifications/update-progress/{qualification}

Update progress for a specific qualification module or requirement.

Request Body
{ "module_id": 3, "status": "completed", "notes": "Completed all reading materials" }
Parameters
Parameter Type Required Description
qualification integer REQUIRED The ID of the qualification
module_id integer REQUIRED The module being updated
status string REQUIRED Status (in_progress, completed)
POST Submit Quiz Answers
POST /api/qualifications/{qualification}/submit-quiz

Submit quiz answers for a training qualification and receive immediate scoring.

Request Body - Completion Consent
{ "consent": true }
Request Body - Multiple Choice
{ "answers": [ { "question_index": 0, "selected_answer_index": 1 }, { "question_index": 1, "selected_answer_index": 2 } ] }
Response Example
{ "success": true, "score": 80, "passed": true, "correct_answers": 4, "total_questions": 5, "results": [ { "question_index": 0, "correct": true }, { "question_index": 1, "correct": false, "correct_answer_index": 3 } ] }
Parameters
Parameter Type Required Description
qualification integer REQUIRED The ID of the qualification
consent boolean OPTIONAL For completion_consent type quizzes
answers array OPTIONAL For multiple_choice type quizzes
Passing Score: Multiple choice quizzes require a minimum score of 70% to pass.
POST Complete Qualification
POST /api/qualifications/complete/{qualification}

Mark a qualification as completed and request certification. Note: User must pass required quiz (if any) before completing.

Parameters
Parameter Type Required Description
qualification integer REQUIRED The ID of the qualification
Prerequisites: To complete a qualification with a quiz, the user must first submit quiz answers and achieve a passing score.

Mobile Implementation Guide for Training Features

Best practices for implementing the enhanced training features in your mobile application:

1. Displaying Training Content

// Check for media content if (qualification.audio_path) { // Display audio player <AudioPlayer src={qualification.audio_path} /> } if (qualification.video_link) { // Embed video or show link const embedUrl = getVideoEmbedUrl(qualification.video_link); <VideoPlayer embedUrl={embedUrl} /> } // Show series badge if part of a series if (qualification.series) { <Badge>{qualification.series}</Badge> }

2. Handling Quiz Types

function renderQuiz(qualification) { if (!qualification.quiz_type) { return null; // No quiz required } if (qualification.quiz_type === 'completion_consent') { return ( <Checkbox label="I confirm I have completed this training" onChange={(checked) => submitConsent(checked)} /> ); } if (qualification.quiz_type === 'multiple_choice') { return ( <QuizComponent questions={qualification.quiz_data.questions} onSubmit={(answers) => submitQuizAnswers(answers)} /> ); } }

3. Video Platform Support

The API returns video URLs from various platforms. Your app should support embedding:

  • YouTube: Convert to embed URL format
  • Vimeo: Use player.vimeo.com embed URLs
  • Dailymotion: Convert to embed format
  • Wistia: Use fast.wistia.net embed URLs

4. Audio Playback

Supported audio formats: MP3, WAV, M4A, AAC, OGG. Implement:

  • Play/pause controls
  • Progress bar with seek capability
  • Download option for offline access
  • Playback speed controls (optional)

5. Quiz Submission Flow

async function handleQuizSubmission(qualificationId, answers) { try { const response = await fetch( `/api/qualifications/${qualificationId}/submit-quiz`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ answers }) } ); const result = await response.json(); if (result.passed) { // Show success message alert(`Congratulations! You scored ${result.score}%`); // Allow user to complete the qualification } else { // Show failure message with option to retry alert(`Score: ${result.score}%. You need 70% to pass. Try again?`); } } catch (error) { console.error('Quiz submission failed:', error); } }

6. Series Organization

Group trainings by series for better UX:

const groupedByKey = trainings.reduce((acc, training) => { const key = training.series || 'Other'; if (!acc[key]) acc[key] = []; acc[key].push(training); return acc; }, {}); // Display in sections Object.keys(groupedBySeries).map(series => ( <Section title={series}> {groupedBySeries[series].map(training => ( <TrainingCard training={training} /> ))} </Section> ));

POST Assign Training
POST /api/qualifications/assign

Assign a training qualification to one or more users (admin/leader only).

Request Body
{ "qualification_id": 1, "user_ids": [5, 12, 18], "due_date": "2024-12-31" }
Parameters
Parameter Type Required Description
qualification_id integer REQUIRED The ID of the qualification to assign
user_ids array REQUIRED Array of user IDs to assign training to
due_date date OPTIONAL Due date for completion
GET Get Users with Progress
GET /api/qualifications/users-progress

Retrieve training progress for all users in your organization (admin/leader only).

Response Example
{ "data": [ { "user_id": 5, "name": "John Doe", "qualifications": [ { "qualification_id": 1, "title": "Small Group Leader Certification", "progress_percentage": 75, "status": "in_progress" } ] } ] }

Error Handling

The API uses conventional HTTP response codes to indicate success or failure of requests.

HTTP Status Codes

Code Meaning
200 OK - Request succeeded
201 Created - Resource successfully created
400 Bad Request - Invalid request parameters
401 Unauthorized - Invalid or missing authentication token
403 Forbidden - Authenticated but insufficient permissions
404 Not Found - Resource does not exist
422 Unprocessable Entity - Validation errors
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error - Something went wrong on our end

Standard Error Response Format

{ "message": "The given data was invalid.", "errors": { "email": [ "The email field is required." ] } }

Groups API Specific Error Responses

When joining or managing groups, the API returns specific error codes to help you handle business logic errors appropriately.

Error Types Summary

Error Type Status Code Description
closed_group 422 Group requires admin approval to join
request_pending 422 User already has a pending join request
group_full 422 Group has reached maximum capacity
group_not_live 422 Group is in draft mode and not accepting members
leader_not_qualified 422 Leader hasn't completed required training
leader_limit_reached 422 Leader has reached maximum group assignments
422 Closed Group Error

Returned when attempting to join a group that requires admin approval.

Error Response
{ "message": "This group requires approval to join", "error_type": "closed_group", "group_id": 123, "group": { "id": 123, "name": "Youth Group", "purpose": "Weekly youth fellowship...", "location": "Church Building Room 101" } }
Fields
Field Type Description
error_type string Always "closed_group"
group_id integer The ID of the closed group
group object Basic group information to display to the user
Handling this error: Show the group information and a form where the user can submit a join request with an optional note explaining why they want to join.
422 Request Pending Error

Returned when a user tries to join a closed group but already has a pending join request.

Error Response
{ "message": "You already have a pending request for this group", "error_type": "request_pending", "group_id": 123, "request_id": 789 }
Fields
Field Type Description
error_type string Always "request_pending"
group_id integer The ID of the group
request_id integer The ID of the existing pending request
Handling this error: Display a message informing the user their request is still pending and being reviewed by the group admin.
422 Group Full Error

Returned when attempting to join a group that has reached its maximum member capacity.

Error Response
{ "message": "This group is full (50/50 members)", "error_type": "group_full", "group_id": 123, "current_members": 50, "max_members": 50 }
Fields
Field Type Description
error_type string Always "group_full"
group_id integer The ID of the full group
current_members integer Current number of members in the group
max_members integer Maximum members allowed (configured by system admin)
422 Group Not Live Error

Returned when attempting to join a group that is still in draft mode and not yet accepting members.

Error Response
{ "message": "This group is not yet live. Please contact your organization administrator.", "error_type": "group_not_live", "group_id": 123, "status": "draft" }
Fields
Field Type Description
error_type string Always "group_not_live"
group_id integer The ID of the group
status string Current group status (draft, active, inactive)
Note: Groups must have a qualified leader assigned before they become active and can accept members.
422 Leader Not Qualified Error

Returned when attempting to assign a leader to a group who has not completed required training/qualifications. This only occurs when the organization has enabled the "enforce_leader_qualifications" setting.

Error Response
{ "message": "This leader has not completed their required training yet.", "error_type": "leader_not_qualified", "leader_id": 456, "redirect_url": "/manage/team" }
Fields
Field Type Description
error_type string Always "leader_not_qualified"
leader_id integer The ID of the unqualified leader
redirect_url string Suggested URL to manage qualifications/training
Handling this error: Direct users to the training/qualifications page where they can assign required training to the leader or verify their qualification status.
422 Leader Limit Reached Error

Returned when attempting to assign a leader to a group who has already reached their maximum number of active group assignments.

Error Response
{ "message": "This leader is already assigned to 5 active groups (maximum: 5).", "error_type": "leader_limit_reached", "leader_id": 456, "current_groups": 5, "max_groups": 5 }
Fields
Field Type Description
error_type string Always "leader_limit_reached"
leader_id integer The ID of the leader who has reached their limit
current_groups integer Number of active groups currently assigned to this leader
max_groups integer Maximum number of active groups allowed per leader (configured by system admin)
Note: Only active groups count toward the limit. Draft and inactive groups do not count. System administrators can adjust the global limit in the super-admin settings.

Error Handling Best Practices

When building mobile or web applications, we recommend:

  1. Check error_type field: Use the error_type field to programmatically handle different error scenarios rather than parsing the message text.
  2. Display user-friendly messages: The message field contains human-readable text suitable for displaying to end users.
  3. Handle redirects: When a redirect_url is provided, consider navigating the user to that page for resolution.
  4. Show specific details: Use fields like current_members, max_members, current_groups, etc. to provide context to users.
  5. Implement retry logic: For transient errors (5xx codes), implement exponential backoff retry logic.

Example Error Handling (JavaScript)

try { const response = await fetch('https://disciply.app/api/groups/join', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_TOKEN', 'Content-Type': 'application/json' }, body: JSON.stringify({ joinCode: 'ABC123' }) }); const data = await response.json(); if (!response.ok) { // Handle specific error types switch (data.error_type) { case 'closed_group': // Show join request form with group info showJoinRequestForm(data.group, data.group_id); break; case 'request_pending': alert('Your join request is still pending approval'); break; case 'group_full': alert(`Group is full (${data.current_members}/${data.max_members})`); break; case 'group_not_live': alert('This group is not accepting members yet'); break; case 'leader_not_qualified': // Redirect to training page window.location.href = data.redirect_url; break; case 'leader_limit_reached': alert(`Leader has reached limit: ${data.current_groups}/${data.max_groups}`); break; default: alert(data.message); } return; } // Success case console.log('Joined group:', data); } catch (error) { console.error('Network error:', error); }

Prayer Requests API

The Prayer Requests API allows users to submit prayer requests that are reviewed by organization admins before being posted to the Prayer Requests global group.

Overview

Prayer requests go through an approval workflow:

  1. User submits a prayer request via the mobile app
  2. Request is added to a moderation queue with status "pending"
  3. Organization admin/leader reviews the request in the web dashboard
  4. Admin can approve (posts to Prayer Requests group) or reject the request
  5. If approved, all members receive push notifications about the new prayer request
  6. Users can mark their own approved requests as "resolved"
Anonymous Prayer Requests: Users can choose to submit prayer requests anonymously by setting the anonymous flag to true. When enabled, the mobile app should hide the user's name from the prayer request display. When a prayer request is approved, the anonymous flag is stored in the group post's meta JSON field. The user information is still stored in the database for administrative purposes.

Create Prayer Request

POST /api/prayer-requests

Request Body

Field Type Required Description
title string Brief title for the prayer request (max 255 characters)
request_text string The prayer request content (max 5000 characters)
anonymous boolean If true, the user's name will be hidden from the prayer request (defaults to false)

Example Request

POST /api/prayer-requests Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json { "title": "Prayer for Family Health", "request_text": "Please pray for my family during this difficult time. My mother is recovering from surgery and we need God's healing and strength.", "anonymous": false }

Example Response

{ "message": "Prayer request submitted successfully. It will be reviewed by an admin before being posted.", "request": { "id": 123, "organization_id": 1, "user_id": 456, "title": "Prayer for Family Health", "request_text": "Please pray for my family during this difficult time. My mother is recovering from surgery and we need God's healing and strength.", "anonymous": false, "status": "pending", "reviewed_by": null, "reviewed_at": null, "rejection_reason": null, "group_post_id": null, "created_at": "2025-12-20T16:30:00.000000Z", "updated_at": "2025-12-20T16:30:00.000000Z" } }

Get My Prayer Requests

GET /api/prayer-requests/my-requests

Returns all prayer requests submitted by the authenticated user, including their status.

Example Response

[ { "id": 123, "organization_id": 1, "user_id": 456, "title": "Prayer for Family Health", "request_text": "Please pray for my family...", "anonymous": false, "status": "approved", "reviewed_by": 789, "reviewed_at": "2025-12-20T16:35:00.000000Z", "rejection_reason": null, "group_post_id": 999, "created_at": "2025-12-20T16:30:00.000000Z", "updated_at": "2025-12-20T16:35:00.000000Z", "reviewer": { "id": 789, "name": "Admin User" } }, { "id": 124, "organization_id": 1, "user_id": 456, "title": "Prayers for Healing", "request_text": "Prayers needed for healing...", "anonymous": true, "status": "pending", "reviewed_by": null, "reviewed_at": null, "rejection_reason": null, "group_post_id": null, "created_at": "2025-12-20T17:00:00.000000Z", "updated_at": "2025-12-20T17:00:00.000000Z", "reviewer": null } ]

Mark Prayer Request as Resolved

POST /api/prayer-requests/{id}/resolve

Allows users to mark their own approved prayer requests as resolved. This updates the metadata on the related group post.

Example Response

{ "message": "Prayer request marked as resolved", "request": { "id": 123, "status": "approved", "group_post_id": 999, // ...other fields } }

Admin Endpoints

These endpoints are available to organization admins and leaders only.

Get Pending Requests (Admin)

GET /api/prayer-requests/pending

Returns paginated list of prayer requests awaiting review.

Example Response

{ "current_page": 1, "data": [ { "id": 125, "organization_id": 1, "user_id": 456, "request_text": "Please pray for...", "status": "pending", "created_at": "2025-12-20T17:30:00.000000Z", "user": { "id": 456, "name": "John Doe", "email": "john@example.com" } } ], "total": 5, "per_page": 20, "last_page": 1 }

Approve Prayer Request (Admin)

POST /api/prayer-requests/{id}/approve

Approves a prayer request and posts it to the Prayer Requests global group. Sends push notifications to all group members.

Example Response

{ "message": "Prayer request approved and posted successfully", "post": { "id": 999, "group_id": 42, "user_id": 456, "message": "Please pray for my family...", "likes": 0, "comment_count": 0, "meta": { "prayer_request_status": "active", "is_prayer_request": true }, "created_at": "2025-12-20T17:35:00.000000Z" } }

Reject Prayer Request (Admin)

POST /api/prayer-requests/{id}/reject
Request Body
Field Type Required Description
rejection_reason string Optional reason for rejection (max 1000 characters)

Example Request

POST /api/prayer-requests/125/reject Authorization: Bearer YOUR_API_TOKEN Content-Type: application/json { "rejection_reason": "Request does not meet community guidelines" }

Example Response

{ "message": "Prayer request rejected", "request": { "id": 125, "status": "rejected", "reviewed_by": 789, "reviewed_at": "2025-12-20T17:40:00.000000Z", "rejection_reason": "Request does not meet community guidelines" } }

Prayer Request Statuses

Status Description
pending Request is awaiting admin review
approved Request has been approved and posted to the Prayer Requests group
rejected Request was rejected by an admin

Group Post Metadata

When a prayer request is approved and posted, the group post includes metadata:

{ "id": 999, "group_id": 42, "user_id": 456, "message": "Please pray for...", "meta": { "prayer_request_status": "active", // or "resolved" "is_prayer_request": true } }

The prayer_request_status field within meta can be:

  • active: Prayer request is still active and needs prayers
  • resolved: User has marked this request as resolved/answered

Migration Guide: Global Groups Update

This section provides guidance for mobile developers on migrating to the new global groups architecture.

What Changed

Summary of Changes
  • Added new group_type field to all group objects
  • Removed virtual group with ID 0 from API responses
  • All organizations now have a real global group with an actual database ID
  • Global groups are automatically sorted to the top of group lists
  • Global groups have restricted functionality (admin-only posts, no comments)

Required Changes in Your Mobile App

1. Update Group Model

Add the new group_type field to your group data model:

// Before interface Group { id: number; name: string; type: string | null; // Category like "Bible Study" status: string; // ...other fields } // After interface Group { id: number; name: string; type: string | null; // Category like "Bible Study" group_type: string; // NEW: 'group' or 'global' status: string; // ...other fields }

2. Remove Virtual Group (ID 0) Handling

Search your codebase for any special handling of group ID 0 and remove it:

// ❌ REMOVE THIS CODE if (group.id === 0) { // Special virtual group handling return renderVirtualGroup(group); } // ✅ No special handling needed anymore // All groups including global groups are now real database records

3. Display Global Groups Separately

Update your groups list UI to sort and display global groups at the top:

// Separate groups by type const globalGroups = groups.filter(g => g.group_type === 'global'); const regularGroups = groups.filter(g => g.group_type === 'group'); // Display in UI <View> <Text style={styles.sectionHeader}>Organization Announcements</Text> {globalGroups.map(group => <GlobalGroupCard group={group} />)} <Text style={styles.sectionHeader}>My Groups</Text> {regularGroups.map(group => <GroupCard group={group} />)} </View>

4. Restrict Post Creation for Global Groups

Only show the "Create Post" button for admins when viewing global groups:

function canCreatePost(group, user) { // For global groups, only admins can post if (group.group_type === 'global') { return user.is_org_admin; } // For regular groups, all members can post return true; } // In your UI {canCreatePost(group, currentUser) && ( <Button onPress={handleCreatePost}> Create Post </Button> )}

5. Disable Comments for Global Groups

Hide comment UI for posts in global groups:

function PostComponent({ post, group }) { const commentsDisabled = group.group_type === 'global'; return ( <View> <PostContent post={post} /> {!commentsDisabled && ( <CommentSection post={post} /> )} {commentsDisabled && ( <Text style={styles.infoText}> Comments are disabled for organization-wide announcements </Text> )} </View> ); }

6. Hide Member List for Global Groups

For privacy, only show member count for global groups, not the full member list:

function GroupMembersSection({ group }) { if (group.group_type === 'global') { return ( <Text>{group.member_count} members</Text> ); } // For regular groups, show full member list return ( <MembersList groupId={group.id} /> ); }

7. Prevent Edit/Delete for Global Groups

Global groups are system-managed and cannot be edited or deleted by users:

function GroupActions({ group, user }) { // Hide edit/delete for global groups if (group.group_type === 'global') { return null; } return ( <View> {user.can_edit_group && ( <Button onPress={handleEdit}>Edit Group</Button> )} {user.can_delete_group && ( <Button onPress={handleDelete}>Delete Group</Button> )} </View> ); }

Testing Checklist

Before releasing your updated app, verify the following:

  • ✅ Global groups display at the top of the groups list
  • ✅ Non-admin users cannot see "Create Post" button in global groups
  • ✅ Comment input is hidden for all global group posts
  • ✅ Member list shows count only (not full list) for global groups
  • ✅ Edit/Delete buttons are hidden for global groups
  • ✅ No code references to group ID 0 remain
  • ✅ Group sorting works correctly (global groups first)

Backward Compatibility

Good News

The API maintains backward compatibility for most features. Existing group endpoints continue to work, and the new group_type field is always present in responses. However, the virtual group (ID 0) has been permanently removed, so any code relying on it must be updated.

Need Help?

If you encounter issues during migration or have questions about implementing these changes:


Support

Need help with the API? We're here to assist you:

  • Documentation Issues: If you find errors or have suggestions for improving this documentation, please contact us.
  • Technical Support: For API-related questions or issues, reach out to our support team at support form.
  • Feature Requests: Have ideas for new API endpoints? We'd love to hear from you through our contact form.
Rate Limits

To ensure fair usage, API requests are rate-limited. Pro plan users receive higher rate limits. Contact support if you need increased limits for your use case.