Penta Backend API Documentation
Overview
Penta is a Disney children's book service with vertical scroll format supporting multiple languages. This document covers the complete REST API built with Django REST Framework.
Architecture
- Framework: Django 4.2+ with Django REST Framework
- Authentication: Custom JWT implementation with access/refresh tokens
- Internationalization: Django-parler for multi-language support
- Database: SQLite (development), PostgreSQL (production)
- API Documentation: Swagger UI, ReDoc, OpenAPI Schema
Base URL
Development: http://localhost:8080/api
Production: https://api.penta-service.com/api
Supported Languages
- Korean (ko) - Default/Fallback
- English (en)
- Japanese (ja)
- Spanish (es)
API Endpoint Status
Currently Implemented Endpoints
The following endpoints are confirmed to be working:
Public Endpoints (No Authentication Required):
GET /api/books/- List booksGET /api/books/{id}/- Book detailsGET /api/books/{book_id}/episodes/{episode_id}/- Episode details (free episodes only)GET /api/books/categories/- List categoriesGET /api/books/popular/- Popular booksPOST /api/books/search/- Search booksGET /api/home/- Homepage dataGET /api/home/characters/- Characters listGET /api/events-news/- List eventsGET /api/events-news/{id}/- Event detailsPOST /api/users/login/- User loginPOST /api/users/register/- User registration (requires password_confirm)GET /api/health/- Health checkGET /api/docs/- Swagger documentationGET /api/schema/- OpenAPI schema
Authenticated Endpoints (Requires Bearer Token):
GET /api/users/profile/- User profileGET /api/books/bookmarks/- List bookmarksPOST /api/books/bookmarks/- Create bookmarkGET /api/books/reading-history/- Reading historyGET /api/rewards/notifications/- NotificationsPOST /api/events-news/participate/- Event participationPOST /api/stickers/earn/{episode_id}/- Earn sticker
Endpoints with Special Requirements
POST /api/users/register/- Requirespassword_confirmfield (not documented)
Recently Fixed Endpoints
GET /api/health/- Health check ✅ (now working)GET /api/tags/or/api/books/tags/- Tag managementGET /api/subscriptions/plans/- Subscription plans- Some endpoints use
/api/events-news/instead of/api/events/ - Some endpoints use
/api/books/categories/instead of/api/categories/
Authentication
JWT Authentication System
Token Types
- Access Token: Valid for 24 hours
- Refresh Token: Valid for 7 days
Login Endpoint
POST /api/users/login/
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123"
}
Response (200 OK):
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user": {
"id": 1,
"email": "user@example.com",
"nickname": "User123",
"profile_image": "http://example.com/media/profile.jpg"
}
}
Token Refresh
POST /api/users/token/refresh/
Content-Type: application/json
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Response (200 OK):
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Using Authentication
Include the access token in the Authorization header:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
API Endpoints
User Management
1. User Registration
POST /api/users/register/
Content-Type: application/json
{
"email": "newuser@example.com",
"password": "SecurePass123!",
"password_confirm": "SecurePass123!",
"nickname": "NewUser",
"birthdate": "2010-01-01",
"country": "KR",
"language": "ko"
}
Note: The password_confirm field is required but was not previously documented.
Response (201 Created):
{
"id": 2,
"email": "newuser@example.com",
"nickname": "NewUser",
"birthdate": "2010-01-01",
"country": "KR",
"language": "ko",
"created_at": "2025-01-15T10:30:00Z"
}
2. User Profile
GET /api/users/profile/
Authorization: Bearer <access_token>
Response (200 OK):
{
"id": 1,
"email": "user@example.com",
"nickname": "User123",
"profile_image": "http://example.com/media/profiles/user123.jpg",
"birthdate": "2010-05-15",
"country": "KR",
"language": "ko",
"is_subscribed": true,
"subscription_end_date": "2025-12-31",
"created_at": "2024-01-01T00:00:00Z"
}
3. Update Profile
PATCH /api/users/profile/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"nickname": "UpdatedNickname",
"language": "en",
"profile_image": "<base64_image_data>"
}
4. Device Management
POST /api/users/devices/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"device_token": "fcm_token_here",
"device_type": "ios",
"device_name": "iPhone 14"
}
Book Management
5. Book List
GET /api/books/?lang=ko&category=1&series=2&tag=adventure
Authorization: Bearer <access_token>
Response (200 OK):
{
"count": 30,
"next": "http://localhost:8080/api/books/?page=2",
"previous": null,
"results": [
{
"id": 1,
"title": "모험의 시작",
"author": "김작가",
"description": "신나는 모험 이야기",
"cover_image": "http://example.com/media/covers/book1.jpg",
"preview_length": 7000,
"is_free": false,
"category": {
"id": 1,
"name": "모험"
},
"series": {
"id": 1,
"name": "판타지 시리즈"
},
"tags": ["adventure", "fantasy"],
"episode_count": 10,
"total_views": 15000,
"rating": 4.5
}
]
}
6. Book Detail
GET /api/books/1/?lang=ko
Authorization: Bearer <access_token>
Response (200 OK):
{
"id": 1,
"title": "모험의 시작",
"author": "김작가",
"publisher": "펜타 출판사",
"synopsis": "신나는 모험 이야기의 시작...",
"description": "용감한 주인공이 펼치는 환상적인 모험",
"age_range": "4-7",
"genre": "webtoon",
"status": "ongoing",
"thumbnail_url": "http://example.com/media/covers/book1.jpg",
"views": 15000,
"is_free": false,
"is_new": true,
"is_exclusive": true,
"is_featured": true,
"is_active": true,
"average_rating": 4.5,
"total_ratings": 245,
"pv_score": 98.5,
"published_date": "2024-01-01",
"metadata": {
"total_pages": 150,
"reading_time": "30 minutes"
},
"created_at": "2024-01-01T09:00:00Z",
"categories": [
{
"id": 1,
"name": "모험",
"type": "genre",
"parent": null,
"name_translations": {
"ko": "모험",
"en": "Adventure",
"ja": "冒険",
"es": "Aventura"
},
"children": []
}
],
"series": [
{
"id": 1,
"series_name": "판타지 시리즈",
"series_type": "franchise",
"display_order": 1
}
],
"tags": [
{
"id": 1,
"tag_name": "모험",
"tag_type": "theme",
"tag_translations": {
"ko": "모험",
"en": "Adventure",
"ja": "冒険",
"es": "Aventura"
}
}
],
"characters": [
{
"id": 1,
"character_name": "주인공",
"character_type": "main",
"description": "용감한 주인공",
"image_url": "https://example.com/characters/hero.png",
"name_translations": {
"ko": "주인공",
"en": "Hero",
"ja": "主人公",
"es": "Héroe"
},
"display_order": 1
}
],
"episodes": [
{
"id": 1,
"episode_number": 1,
"title": "첫 번째 에피소드",
"cover_image_url": "https://example.com/episodes/1/cover.jpg",
"is_free": true,
"published_date": "2024-01-01",
"sticker": {
"id": 1,
"name": "첫 모험 스티커",
"image_url": "https://example.com/stickers/1.png",
"is_earned": false
}
},
{
"id": 2,
"episode_number": 2,
"title": "두 번째 에피소드",
"cover_image_url": "https://example.com/episodes/2/cover.jpg",
"is_free": false,
"published_date": "2024-01-08",
"sticker": {
"id": 2,
"name": "용기의 스티커",
"image_url": "https://example.com/stickers/2.png",
"is_earned": true
}
}
],
"sticker_urls": [
"https://example.com/stickers/1.png",
"https://example.com/stickers/2.png"
],
"is_bookmarked": true,
"reading_progress": {
"last_position": 500,
"is_completed": false,
"last_read_at": "2024-03-15T14:30:00Z",
"episode_id": 2
},
"title_translations": {
"ko": "모험의 시작",
"en": "The Beginning of Adventure",
"ja": "冒険の始まり",
"es": "El Comienzo de la Aventura"
},
"author_translations": {
"ko": "김작가",
"en": "Kim Author",
"ja": "キム作家",
"es": "Autor Kim"
},
"publisher_translations": {
"ko": "펜타 출판사",
"en": "Penta Publishing",
"ja": "ペンタ出版",
"es": "Editorial Penta"
},
"synopsis_translations": {
"ko": "신나는 모험 이야기의 시작...",
"en": "The beginning of an exciting adventure story...",
"ja": "わくわくする冒険物語の始まり...",
"es": "El comienzo de una emocionante historia de aventuras..."
},
"description_translations": {
"ko": "용감한 주인공이 펼치는 환상적인 모험",
"en": "A fantastic adventure by a brave hero",
"ja": "勇敢な主人公が繰り広げる幻想的な冒険",
"es": "Una fantástica aventura de un héroe valiente"
}
}
7. Episode Detail
GET /api/books/{book_id}/episodes/{episode_id}/?lang=ko
Authorization: Bearer <access_token> # Required for premium episodes
Response (200 OK):
{
"id": 1,
"episode_number": 1,
"title": "첫 번째 에피소드",
"cover_image_url": "https://example.com/episodes/1/cover.jpg",
"is_free": true,
"published_date": "2024-01-01",
"sticker": {
"id": 1,
"name": "첫 에피소드 스티커",
"image_url": "https://example.com/sticker1.png",
"is_earned": false
},
"pages": [
{
"id": 1,
"page_number": 1,
"image_url": "https://example.com/episode1/page1.jpg",
"width": 720,
"height": 1024,
"file_size": 512000
},
{
"id": 2,
"page_number": 2,
"image_url": "https://example.com/episode1/page2.jpg",
"width": 720,
"height": 1024,
"file_size": 489000
}
],
"book_info": {
"id": 1,
"title": "책 제목",
"author": "작가명",
"thumbnail_url": "https://example.com/book1.jpg"
},
"next_episode": {
"id": 2,
"episode_number": 2,
"title": "두 번째 에피소드"
},
"previous_episode": null
}
Error Responses:
- 401 Unauthorized: Authentication required for premium content
- 403 Forbidden: Active subscription required
- 404 Not Found: Episode not found or doesn't belong to the specified book
8. Reading Progress Update
POST /api/books/reading-progress/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"book": 1,
"episode": 1,
"last_position": 5000,
"is_completed": true
}
Note: When is_completed is true and the episode has a sticker, the response will include sticker earned information:
{
"message": "Reading progress updated",
"sticker_earned": true,
"sticker": {
"id": 1,
"name": "첫 걸음",
"image": "http://example.com/media/stickers/sticker1.png"
},
"is_read": true,
"reading_progress": 100
}
9. Reading History
POST /api/reading-history/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"episode": 1,
"progress": 75,
"reading_time": 450
}
10. Bookmark Management
POST /api/bookmarks/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"book": 1
}
DELETE /api/bookmarks/1/
Authorization: Bearer <access_token>
Category & Tag Management
11. Category List
GET /api/books/categories/?lang=ko
Response (200 OK):
[
{
"id": 1,
"name": "모험",
"slug": "adventure",
"description": "스릴 넘치는 모험 이야기",
"translations": {
"ko": {"name": "모험", "description": "스릴 넘치는 모험 이야기"},
"en": {"name": "Adventure", "description": "Thrilling adventure stories"},
"ja": {"name": "冒険", "description": "スリリングな冒険物語"},
"es": {"name": "Aventura", "description": "Historias de aventuras emocionantes"}
}
}
]
12. Tag List
GET /api/books/tags/?lang=ko # Note: This endpoint may not be implemented yet
Home & Discovery
13. Home Page Data
GET /api/home/?lang=ko
Authorization: Bearer <access_token>
Response (200 OK):
{
"sections": [
{
"id": 1,
"section_type": "featured",
"title": "추천 도서",
"display_order": 1,
"books": [
{
"id": 1,
"title": "모험의 시작",
"cover_image": "http://example.com/media/covers/book1.jpg",
"author": "김작가"
}
]
},
{
"id": 2,
"section_type": "new_releases",
"title": "신작",
"display_order": 2,
"books": [...]
}
],
"filters": [
{
"id": 1,
"name": "인기순",
"filter_type": "popularity"
}
]
}
14. Characters List
GET /api/home/characters/?lang=ko
Response (200 OK):
{
"characters": [
{
"id": 1,
"character": {
"id": 57,
"translations": {
"ko": {
"character_name": "캐릭터 1",
"description": "캐릭터 1의 설명입니다."
}
},
"image_url": "https://picsum.photos/300/300?random=char57"
},
"display_order": 1,
"is_featured": true,
"translations": {
"ko": {
"featured_message": "이번 주 추천 캐릭터입니다!"
},
"en": {
"featured_message": "This week's featured character!"
}
},
"book_count": 0
}
],
"character_count": 5
}
15. Real-time Rankings
GET /api/rankings/?period=daily&lang=ko
Response (200 OK):
{
"period": "daily",
"updated_at": "2025-01-15T12:00:00Z",
"rankings": [
{
"rank": 1,
"book": {
"id": 1,
"title": "모험의 시작",
"cover_image": "http://example.com/media/covers/book1.jpg"
},
"score": 9500,
"change": "up",
"previous_rank": 3
}
]
}
Event Management
15. Event List
GET /api/events-news/?lang=ko&status=active
Response (200 OK):
{
"count": 10,
"results": [
{
"id": 1,
"title": "신년 이벤트",
"description": "새해를 맞아 특별 이벤트",
"event_type": "promotion",
"start_date": "2025-01-01",
"end_date": "2025-01-31",
"is_active": true,
"banner_image": "http://example.com/media/events/banner1.jpg",
"participation_count": 1500
}
]
}
16. Event Participation
POST /api/events-news/participate/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"event_id": 1
}
Sticker System
17. Available Stickers
GET /api/stickers/?lang=ko
Response (200 OK):
[
{
"id": 1,
"name": "첫 걸음",
"description": "첫 에피소드 완료",
"image": "http://example.com/media/stickers/sticker1.png",
"rarity": "common",
"category": "achievement",
"unlock_condition": "complete_first_episode"
}
]
18. User Sticker Collection
GET /api/user-stickers/
Authorization: Bearer <access_token>
Response (200 OK):
{
"total_collected": 25,
"stickers": [
{
"id": 1,
"sticker": {
"id": 1,
"name": "첫 걸음",
"image": "http://example.com/media/stickers/sticker1.png"
},
"earned_at": "2025-01-10T15:30:00Z",
"episode": {
"id": 1,
"title": "첫 번째 에피소드"
}
}
]
}
19. Earn Sticker
POST /api/stickers/earn/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"episode": 1,
"reading_time": 600
}
Reward & Notification System
20. User Notifications
GET /api/notifications/
Authorization: Bearer <access_token>
Response (200 OK):
[
{
"id": 1,
"title": "새로운 에피소드 출시",
"message": "좋아하는 시리즈의 새 에피소드가 출시되었습니다",
"notification_type": "new_episode",
"is_read": false,
"created_at": "2025-01-15T09:00:00Z",
"data": {
"book_id": 1,
"episode_id": 11
}
}
]
21. Mark Notification as Read
PATCH /api/notifications/1/read/
Authorization: Bearer <access_token>
Payment & Subscription
22. Subscription Plans
GET /api/subscriptions/plans/ # Note: This endpoint may not be implemented yet
Response (200 OK):
[
{
"id": 1,
"name": "월간 구독",
"duration_days": 30,
"price": 9900,
"currency": "KRW",
"features": [
"모든 도서 무제한 읽기",
"신작 우선 공개",
"광고 제거"
]
}
]
23. Create Subscription
POST /api/subscriptions/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"plan": 1,
"payment_method": "card",
"promo_code": "NEWYEAR2025"
}
24. Payment History
GET /api/payments/history/
Authorization: Bearer <access_token>
Response (200 OK):
[
{
"id": 1,
"transaction_id": "TXN_20250115_001",
"amount": 9900,
"currency": "KRW",
"status": "completed",
"payment_method": "card",
"created_at": "2025-01-15T10:00:00Z",
"subscription": {
"plan": "월간 구독",
"start_date": "2025-01-15",
"end_date": "2025-02-14"
}
}
]
25. Apply Promo Code
POST /api/promo-codes/apply/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"code": "NEWYEAR2025"
}
Search & Discovery
26. Global Search
GET /api/search/?q=모험&lang=ko&type=all
Authorization: Bearer <access_token>
Response (200 OK):
{
"books": [
{
"id": 1,
"title": "모험의 시작",
"author": "김작가",
"match_type": "title"
}
],
"characters": [
{
"id": 3,
"name": "모험가",
"book_title": "판타지 세계"
}
],
"tags": [
{
"id": 1,
"name": "adventure"
}
]
}
Statistics & Analytics
27. User Reading Stats
GET /api/stats/reading/
Authorization: Bearer <access_token>
Response (200 OK):
{
"total_books_read": 15,
"total_episodes_read": 125,
"total_reading_time": 45000,
"favorite_category": "adventure",
"reading_streak": 7,
"monthly_stats": [
{
"month": "2025-01",
"books_read": 3,
"episodes_read": 25,
"reading_time": 9000
}
]
}
28. Book Statistics
GET /api/books/1/stats/
Authorization: Bearer <access_token>
Social Features
29. Share Book
POST /api/books/1/share/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"platform": "kakao",
"message": "이 책 정말 재미있어요!"
}
30. User Reviews
POST /api/books/1/reviews/
Authorization: Bearer <access_token>
Content-Type: application/json
{
"rating": 5,
"comment": "정말 재미있게 읽었습니다!"
}
Admin Features
31. Content Moderation
GET /api/admin/content/pending/
Authorization: Bearer <admin_token>
32. User Management
GET /api/admin/users/
Authorization: Bearer <admin_token>
33. Analytics Dashboard
GET /api/admin/analytics/
Authorization: Bearer <admin_token>
34. System Health
GET /api/health/
Response (200 OK):
{
"status": "healthy",
"services": {
"database": true,
"application": true
},
"response_time_ms": 0.28
}
Error Handling
Standard Error Response Format
{
"error": {
"code": "AUTHENTICATION_FAILED",
"message": "인증에 실패했습니다",
"details": {
"field": "token",
"reason": "expired"
}
}
}
Common Error Codes
AUTHENTICATION_FAILED- 인증 실패PERMISSION_DENIED- 권한 없음NOT_FOUND- 리소스를 찾을 수 없음VALIDATION_ERROR- 입력값 검증 실패SUBSCRIPTION_REQUIRED- 구독 필요RATE_LIMIT_EXCEEDED- 요청 한도 초과SERVER_ERROR- 서버 오류
HTTP Status Codes
200 OK- 요청 성공201 Created- 리소스 생성 성공204 No Content- 성공했지만 반환할 내용 없음400 Bad Request- 잘못된 요청401 Unauthorized- 인증 필요403 Forbidden- 권한 없음404 Not Found- 리소스 없음429 Too Many Requests- 요청 한도 초과500 Internal Server Error- 서버 오류
Multi-language Support
Language Selection Priority
- Query parameter:
?lang=en - Accept-Language header
- User's profile language setting
- Default language (Korean)
Example with Language Parameter
GET /api/books/1/?lang=en
Authorization: Bearer <access_token>
Supported Language Codes
ko- Koreanen- Englishja- Japanesees- Spanish
Rate Limiting
- Anonymous users: 100 requests per hour
- Authenticated users: 1000 requests per hour
- Premium subscribers: 5000 requests per hour
Rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1673784000
Pagination
Standard pagination format:
{
"count": 100,
"next": "http://api.example.com/resource/?page=2",
"previous": null,
"results": [...]
}
Query parameters:
page- Page number (default: 1)page_size- Items per page (default: 20, max: 100)
Filtering and Sorting
Common Filters
category- Filter by category IDseries- Filter by series IDtag- Filter by tag nameis_free- Filter free contentdate_from- Start date filterdate_to- End date filter
Sorting Options
ordering=created_at- Sort by creation date (ascending)ordering=-created_at- Sort by creation date (descending)ordering=popularity- Sort by popularityordering=rating- Sort by rating
Webhooks
Available Webhook Events
user.registered- New user registrationsubscription.created- New subscriptionsubscription.cancelled- Subscription cancelledepisode.released- New episode releasedpayment.completed- Payment successful
Webhook Payload Format
{
"event": "user.registered",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"user_id": 123,
"email": "user@example.com"
}
}
API Versioning
Current version: v1
Version is included in the URL path:
/api/v1/books/
Development Tools
Swagger UI
Interactive API documentation:
http://localhost:8080/api/docs/
ReDoc
Clean API documentation:
http://localhost:8080/api/redoc/
OpenAPI Schema
Machine-readable API specification:
http://localhost:8080/api/schema/
Testing
Test Credentials
- Regular User:
testuser@example.com/testpassword123 - Admin User:
admin@example.com/admin123
Test Payment Cards
- Success:
4242-4242-4242-4242 - Decline:
4000-0000-0000-0002 - Insufficient Funds:
4000-0000-0000-9995
Best Practices
- Always use HTTPS in production
- Include proper Accept-Language headers for internationalization
- Handle token refresh gracefully in client applications
- Implement proper error handling for all API responses
- Use pagination for list endpoints
- Cache responses where appropriate
- Monitor rate limits to avoid service disruption
Support
For API support and questions:
- Email: api-support@penta-service.com
- Developer Portal: https://developers.penta-service.com
- Status Page: https://status.penta-service.com