오늘의 팁
현재 강남구 재물운 상승 중 ↑
본문으로 건너뛰기
뒤로
문서

API Reference - Secret Saju

문서: api-reference|사주 사전

API Reference - Secret Saju

Complete API Documentation


📋 Overview

Secret Saju uses RESTful API endpoints built with Next.js App Router. All endpoints return JSON and require authentication unless specified.

Base URL: https://secretsaju.com/api (Production)
Dev URL: http://localhost:3000/api


🔐 Authentication

All API requests (except auth endpoints) require a valid session cookie obtained through Kakao Login.

Authentication Flow

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant Kakao
    participant Supabase
    
    User->>Frontend: Click "Kakao Login"
    Frontend->>Kakao: Redirect to OAuth
    Kakao->>Frontend: Callback with code
    Frontend->>API: POST /api/auth/kakao/callback
    API->>Kakao: Exchange code for token
    Kakao->>API: Access token
    API->>Supabase: Create/update user
    Supabase->>API: User ID
    API->>Frontend: Set session cookie
    Frontend->>User: Redirect to dashboard

Headers

Cookie: secret-saju-session=<JWT_TOKEN>
Content-Type: application/json

📍 Endpoints

Saju Calculation

POST /api/saju/calculate

Calculate high-precision saju (Four Pillars).

Request Body:

{
  birthDate: string;      // ISO 8601, e.g. "1990-01-15T00:00:00Z"
  birthTime: string;      // "HH:mm", e.g. "14:30"
  gender: "M" | "F";
  calendarType?: "solar" | "lunar";  // Default: "solar"
  location?: {
    latitude: number;
    longitude: number;
    timezone: string;
  };
}

Response (200 OK):

{
  fourPillars: {
    year: { stem: "甲", branch: "子" },
    month: { stem: "乙", branch: "丑" },
    day: { stem: "丙", branch: "寅" },
    hour: { stem: "丁", branch: "卯" }
  },
  trueSolarTime: "1990-01-15T14:32:15Z",
  gender: "M",
  elements: {
    wood: 2, fire: 1, earth: 0, metal: 1, water: 0
  },
  sinsal: [...],
  sipsong: {...},
  gyeokguk: {...},
  daewun: {...}
}

Errors:

  • 400 Bad Request: Invalid input (future date, missing fields)
  • 401 Unauthorized: No session
  • 500 Internal Server Error: Calculation failed

Example:

curl -X POST https://secretsaju.com/api/saju/calculate \
  -H "Content-Type: application/json" \
  -H "Cookie: secret-saju-session=..." \
  -d '{
    "birthDate": "1990-01-15T00:00:00Z",
    "birthTime": "14:30",
    "gender": "M",
    "calendarType": "solar"
  }'

Profile Management

POST /api/saju/create

Save a saju profile to user's account.

Request Body:

{
  name: string;          // "나", "엄마", etc.
  relationship: "self" | "spouse" | "child" | "parent" | "friend" | "lover" | "other";
  birthdate: string;     // "YYYY-MM-DD"
  birthTime?: string;    // "HH:mm"
  isTimeUnknown?: boolean;
  calendarType: "solar" | "lunar";
  gender: "female" | "male";
}

Response (201 Created):

{
  id: "uuid",
  userId: "uuid",
  name: "나",
  relationship: "self",
  birthdate: "1990-01-15",
  birthTime: "14:30",
  calendarType: "solar",
  gender: "female",
  createdAt: "2026-01-31T12:00:00Z",
  updatedAt: "2026-01-31T12:00:00Z"
}

GET /api/saju/list

Get all saved profiles for authenticated user.

Response (200 OK):

{
  profiles: [
    {
      id: "uuid",
      name: "나",
      relationship: "self",
      birthdate: "1990-01-15",
      ...
    }
  ],
  total: 3
}

DELETE /api/saju/delete?id=<profile_id>

Delete a saved profile.

Response (200 OK):

{
  success: true,
  deletedId: "uuid"
}

Payment & Wallet

POST /api/payment/verify

Verify a Toss payment and credit jellies.

Request Body:

{
  paymentKey: string;
  orderId: string;
  amount: number;  // In KRW
}

Response (200 OK):

{
  success: true,
  jellies: 3,
  transactionId: "uuid",
  newBalance: 10
}

GET /api/wallet/balance

Get user's current jelly balance.

Response (200 OK):

{
  userId: "uuid",
  balance: 7,
  totalPurchased: 10,
  totalConsumed: 3
}

GET /api/wallet/history

Get transaction history.

Response (200 OK):

{
  transactions: [
    {
      id: "uuid",
      type: "purchase" | "consume" | "gift",
      jellies: 3,
      amount: 2900,
      purpose: "SMART 패키지 구매",
      createdAt: "2026-01-31T10:00:00Z"
    }
  ],
  total: 5
}

Recommendations

GET /api/recommendations?code=<ganji_code>&age_group=<20s|30s|40s>

Get personalized food/product recommendations based on saju.

Response (200 OK):

{
  code: "甲子",
  ageGroup: "20s",
  food: [
    {
      name: "녹차",
      reason: "木 기운 강화",
      emoji: "🍵"
    }
  ],
  products: [
    {
      name: "숲 테라피 향수",
      category: "향수",
      reason: "木 element 보충",
      emoji: "🌲",
      link: "https://..."
    }
  ]
}

Miscellaneous

GET /api/daily-fortune?profileId=<uuid>

Get today's fortune for a saved profile.

Response (200 OK):

{
  fortune: "오늘은 재물운이 좋은 날입니다...",
  date: "2026-01-31"
}

GET /api/celebrity-match?profileId=<uuid>

Find celebrities with similar saju.

Response (200 OK):

{
  matches: [
    {
      name: "아이유",
      code: "甲子",
      description: "...",
      match Percentage: 85,
      matchReason: "..."
    }
  ]
}

🚨 Error Handling

All errors follow this flat structure, produced by buildErrorResponsePayload() in src/lib/contracts/error-response.ts:

{
  error: string;        // Human-readable message (Korean or English)
  error_code: string;   // Machine code, e.g. "VALIDATION_ERROR", "PAYMENT_MISSING_TIER"
  details?: unknown;    // Optional; key is omitted entirely when no details are passed
}

Field reference:

| Field | Type | Presence | Meaning | |-------|------|----------|---------| | error | string | always | Human-readable message shown to / logged for the caller | | error_code | string | always | Stable machine-readable code for branching and telemetry | | details | unknown | optional | Extra context; the key is absent (not null) when omitted |

The HTTP status code is carried by the response itself (e.g. NextResponse.json(payload, { status })), not as a field inside the body. There is no nested error.code / error.message object and no top-level code or status field in the payload.

Common Error Codes

| Code | HTTP Status | Meaning | |------|-------------|---------| | VALIDATION_ERROR | 400 | Invalid input | | AUTH_REQUIRED | 401 | Not logged in | | FORBIDDEN | 403 | No permission | | NOT_FOUND | 404 | Resource doesn't exist | | PAYMENT_ERROR | 402 | Payment failed | | RATE_LIMIT | 429 | Too many requests | | INTERNAL_ERROR | 500 | Server error |


📊 Rate Limiting

| Endpoint | Limit | Window | |----------|-------|--------| | /api/saju/calculate | 60 requests | 1 minute | | /api/saju/create | 10 requests | 1 minute | | /api/payment/* | 5 requests | 1 minute | | All others | 120 requests | 1 minute |

Exceeded: Returns 429 with Retry-After header.


🧪 Testing

Development

# Use localhost
curl http://localhost:3000/api/saju/calculate \
  -H "Content-Type: application/json" \
  -d '{"birthDate": "1990-01-15T00:00:00Z", "birthTime": "14:30", "gender": "M"}'

Postman Collection

Download: secret-saju-api.postman_collection.json


📚 Related Documentation


Document Owner: Backend Team
Last Updated: 2026-01-31
API Version: 1.0

본 문서는 시크릿사주의 자산이며 무단 복제를 금지합니다.