본문으로 건너뛰기

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 books
  • GET /api/books/{id}/ - Book details
  • GET /api/books/{book_id}/episodes/{episode_id}/ - Episode details (free episodes only)
  • GET /api/books/categories/ - List categories
  • GET /api/books/popular/ - Popular books
  • POST /api/books/search/ - Search books
  • GET /api/home/ - Homepage data
  • GET /api/home/characters/ - Characters list
  • GET /api/events-news/ - List events
  • GET /api/events-news/{id}/ - Event details
  • POST /api/users/login/ - User login
  • POST /api/users/register/ - User registration (requires password_confirm)
  • GET /api/health/ - Health check
  • GET /api/docs/ - Swagger documentation
  • GET /api/schema/ - OpenAPI schema

Authenticated Endpoints (Requires Bearer Token):

  • GET /api/users/profile/ - User profile
  • GET /api/books/bookmarks/ - List bookmarks
  • POST /api/books/bookmarks/ - Create bookmark
  • GET /api/books/reading-history/ - Reading history
  • GET /api/rewards/notifications/ - Notifications
  • POST /api/events-news/participate/ - Event participation
  • POST /api/stickers/earn/{episode_id}/ - Earn sticker

Endpoints with Special Requirements

  • POST /api/users/register/ - Requires password_confirm field (not documented)

Recently Fixed Endpoints

  • GET /api/health/ - Health check ✅ (now working)
  • GET /api/tags/ or /api/books/tags/ - Tag management
  • GET /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

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

  1. Query parameter: ?lang=en
  2. Accept-Language header
  3. User's profile language setting
  4. Default language (Korean)

Example with Language Parameter

GET /api/books/1/?lang=en
Authorization: Bearer <access_token>

Supported Language Codes

  • ko - Korean
  • en - English
  • ja - Japanese
  • es - 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 ID
  • series - Filter by series ID
  • tag - Filter by tag name
  • is_free - Filter free content
  • date_from - Start date filter
  • date_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 popularity
  • ordering=rating - Sort by rating

Webhooks

Available Webhook Events

  • user.registered - New user registration
  • subscription.created - New subscription
  • subscription.cancelled - Subscription cancelled
  • episode.released - New episode released
  • payment.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

  1. Always use HTTPS in production
  2. Include proper Accept-Language headers for internationalization
  3. Handle token refresh gracefully in client applications
  4. Implement proper error handling for all API responses
  5. Use pagination for list endpoints
  6. Cache responses where appropriate
  7. Monitor rate limits to avoid service disruption

Support

For API support and questions: