https://rankly.kr/api/v1
v1https://rankly.kr/api/v1/docshttps://rankly.kr/api/v1/openapi.jsonAuthorization 헤더에 토큰 포함 필요브라우저에서 다음 URL로 접속:
https://rankly.kr/api/v1/docs
Bearer {token} 형식으로 토큰 입력Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...다음 URL에서 OpenAPI 스펙을 JSON 형식으로 다운로드 가능:
https://rankly.kr/api/v1/openapi.json
이 파일을 사용하여: - Postman, Insomnia 등 API 클라이언트에 import - 코드 생성 도구 사용 (예: Swagger Codegen) - API 문서 자동 생성
엔드포인트: POST /api/v1/accounts/login/
요청 예시:
{
"email": "user@example.com",
"password": "password123"
}
응답 예시:
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
모든 인증이 필요한 API 요청 시 Authorization 헤더에 토큰 포함:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
엔드포인트: POST /api/v1/accounts/token/refresh/
요청 예시:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
POST /api/v1/accounts/otp/sendemail: 이메일 주소요청 예시:
{
"email": "user@example.com"
}
POST /api/v1/accounts/otp/verifyemail: 이메일 주소otp: 6자리 OTP 코드요청 예시:
{
"email": "user@example.com",
"otp": "123456"
}
POST /api/v1/accounts/signup/email: 이메일 주소 (OTP 검증 완료된 이메일)otp: OTP 코드 (재검증)name: 이름password: 비밀번호confirm_password: 비밀번호 확인nickname: 닉네임 (미입력 시 자동 생성: 형용사+동물명+숫자3자리, 예: 귀여운토끼123)referral_code: 추천코드요청 예시:
{
"email": "user@example.com",
"otp": "123456",
"name": "홍길동",
"password": "SecurePass123!",
"confirm_password": "SecurePass123!",
"nickname": "귀여운토끼123",
"referral_code": "ABC123"
}
응답 예시:
{
"detail": "회원가입이 완료되었습니다.",
"user_id": 42,
"email": "user@example.com",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_expires_at": "2026-02-24T12:34:56Z",
"refresh_expires_in": 2592000
}
POST /api/v1/accounts/login/email: 이메일 주소 (닉네임 아님!)password: 비밀번호access_token: JWT 액세스 토큰 (유효기간 72시간)refresh_token: 응답 body로 전달 (앱/웹 공용)refresh_expires_at: refresh 만료 시각(UTC)refresh_expires_in: refresh 만료까지 남은 초요청 예시:
{
"email": "user@example.com",
"password": "SecurePass123!"
}
응답 예시:
{
"detail": "로그인 성공",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh_expires_at": "2026-02-24T12:34:56Z",
"refresh_expires_in": 2592000,
"id": 42,
"nickname": "귀여운토끼123",
"name": "홍길동",
"type": 2,
"role": 30,
"parent_user_id": null
}
POST /api/v1/accounts/social/google/POST /api/v1/slots/user/createstart_date 미지정 시 오늘 날짜end_date 미지정 시 100년 후 (무기한 슬롯)detail: 성공 메시지created_slot_ids: 생성된 슬롯 ID 목록slot_count: 생성된 슬롯 수응답 예시:
{
"detail": "광고주가 슬롯 2개 생성 성공",
"created_slot_ids": [101, 102],
"slot_count": 2
}
GET /api/v1/accounts/{user_id}/slots/all/datatableslot_type: 슬롯 상태 (대기중, 진행중, 완료 등)searched: 검색어platform_id: 플랫폼 필터group_id: 특정 그룹의 슬롯만 조회bookmarked: true/false (북마크된 슬롯만)POST /api/v1/slots/delete{ "slot_ids": [101, 102, 103] }
slot_ids에 1개만 넣어서 호출하면 됩니다. (예: { "slot_ids": [101] })POST /api/v1/slot-groups/platform_name: 플랫폼명 (naver, place, coupang, ohou, kakao 등)name: 그룹명 (최대 100자)color: 그룹 색상 (HEX 코드, 기본값: #3498db)description: 그룹 설명요청 예시:
{
"platform_name": "naver",
"name": "A급 상품",
"color": "#ff6b6b",
"description": "주력 상품 그룹"
}
GET /api/v1/slot-groups/platform_name (선택)GET /api/v1/slot-groups/{group_id}PATCH /api/v1/slot-groups/{group_id}DELETE /api/v1/slot-groups/{group_id}POST /api/v1/slot-groups/{group_id}/slotsslot_ids (슬롯 ID 배열, 최대 100개)요청 예시:
{
"slot_ids": [101, 102, 103]
}
DELETE /api/v1/slot-groups/{group_id}/slots/{slot_id}PATCH /api/v1/slot-groups/slots/{slot_id}/bookmarkis_bookmarked (true/false)요청 예시:
{
"is_bookmarked": true
}
GET /api/v1/subscriptions/plans/POST /api/v1/subscriptions/POST /api/v1/subscriptions/renewPATCH /api/v1/subscriptions/{subscription_id}POST /api/v1/subscriptions/{subscription_id}/cancelGET /api/v1/subscriptions/summaryGET /api/v1/boards/notices/POST /api/v1/boards/notices/GET /api/v1/boards/community/posts/POST /api/v1/boards/community/posts/POST /api/v1/boards/promotion/posts/POST /api/v1/boards/community/posts/{post_id}/comments/POST /api/v1/boards/community/posts/{post_id}/likeGET /api/v1/boards/banners/POST /api/v1/notifications/register-devicedevice_token: APNs 토큰 (64자 16진수 문자열)env: 환경 (sandbox 또는 prod)device_name: 기기명 (예: iPhone 14 Pro)device_model: 모델 식별자 (예: iPhone15,2)os_version: OS 버전 (예: iOS 17.2)app_version: 앱 버전 (예: 1.0.0)요청 예시:
{
"device_token": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"env": "prod",
"device_name": "iPhone 14 Pro",
"device_model": "iPhone15,2",
"os_version": "iOS 17.2",
"app_version": "1.0.0"
}
GET /api/v1/notifications/devicesDELETE /api/v1/notifications/devices/{device_id}POST /api/v1/notifications/register-sessionuser_agent: User Agent 문자열browser: 브라우저명 (Chrome, Safari, Firefox 등)os: 운영체제 (Windows 10, macOS 14.0 등)device_type: 기기 유형 (desktop, mobile, tablet)요청 예시:
{
"browser": "Chrome",
"os": "Windows 10",
"device_type": "desktop"
}
GET /api/v1/notifications/sessions?limit=20GET /api/v1/notifications/historynotification_type: 알림 유형 (subscription_expiry, rank_update, payment 등)success: 성공 여부 (true/false)limit: 조회 개수 (최대 100)알림 발송 전략: - ✅ 모든 활성 기기에 발송 (APNs 무료) - ✅ Thread ID로 중복 알림 그룹핑 - ✅ 우선순위별 차등 발송 (CRITICAL/HIGH/NORMAL/LOW) - ✅ 만료된 토큰 자동 비활성화
import Foundation
class APIClient {
static let shared = APIClient()
private let baseURL = "https://rankly.kr/api/v1"
private var accessToken: String?
func setAccessToken(_ token: String) {
self.accessToken = token
}
func request<T: Decodable>(
endpoint: String,
method: String = "GET",
body: Data? = nil
) async throws -> T {
guard let url = URL(string: "\(baseURL)\(endpoint)") else {
throw APIError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let token = accessToken {
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
}
if let body = body {
request.httpBody = body
}
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse
}
guard (200...299).contains(httpResponse.statusCode) else {
throw APIError.httpError(statusCode: httpResponse.statusCode)
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(T.self, from: data)
}
}
struct LoginRequest: Codable {
let email: String
let password: String
}
struct LoginResponse: Codable {
let access: String
let refresh: String
}
func login(email: String, password: String) async throws {
let request = LoginRequest(email: email, password: password)
let body = try JSONEncoder().encode(request)
let response: LoginResponse = try await APIClient.shared.request(
endpoint: "/accounts/login/",
method: "POST",
body: body
)
// 토큰 저장
APIClient.shared.setAccessToken(response.access)
UserDefaults.standard.set(response.refresh, forKey: "refreshToken")
}
struct SlotCreateRequest: Codable {
let advertiser_id: Int
let platform_id: Int
let keyword: String?
let url: String?
let slot_cnt: Int
let start_date: String? // "YYYY-MM-DD" 형식
let end_date: String? // "YYYY-MM-DD" 형식, 선택사항 (미지정 시 무기한)
}
struct SlotCreateResponse: Codable {
let detail: String
let created_ids: [Int]?
}
func createSlot(request: SlotCreateRequest) async throws -> SlotCreateResponse {
let body = try JSONEncoder().encode(request)
return try await APIClient.shared.request(
endpoint: "/slots/user/create",
method: "POST",
body: body
)
}
struct PostListResponse: Codable {
let items: [Post]
let total: Int
let page: Int
let page_size: Int
}
struct Post: Codable {
let id: Int
let title: String
let content: String
let author: Author
let likes_count: Int
let comments_count: Int
let created_at: String
}
func getCommunityPosts(
page: Int = 1,
pageSize: Int = 20,
sort: String = "latest"
) async throws -> PostListResponse {
let endpoint = "/boards/community/posts/?page=\(page)&page_size=\(pageSize)&sort=\(sort)"
return try await APIClient.shared.request(endpoint: endpoint)
}
enum APIError: Error {
case invalidURL
case invalidResponse
case httpError(statusCode: Int)
case decodingError(Error)
case unauthorized
case forbidden
case notFound
case serverError
var localizedDescription: String {
switch self {
case .invalidURL:
return "잘못된 URL입니다."
case .invalidResponse:
return "잘못된 응답입니다."
case .httpError(let code):
return "HTTP 오류: \(code)"
case .decodingError(let error):
return "데이터 파싱 오류: \(error.localizedDescription)"
case .unauthorized:
return "인증이 필요합니다."
case .forbidden:
return "권한이 없습니다."
case .notFound:
return "리소스를 찾을 수 없습니다."
case .serverError:
return "서버 오류가 발생했습니다."
}
}
}
YYYY-MM-DD)YYYY-MM-DD 또는 YYYY-MM-DDTHH:mm:ssZ)대부분의 목록 API는 페이지네이션을 지원합니다:
- page: 페이지 번호 (기본값: 1)
- page_size: 페이지당 항목 수 (기본값: 20)
게시판 목록 API는 다음 정렬 옵션을 지원합니다:
- latest: 최신순 (기본값)
- likes: 좋아요순
- comments: 댓글순
- views: 조회순
이미지가 필요한 경우: - 방법 1: 이미지 URL 제공 (HTTPS, jpg/png만 허용) - 방법 2: Base64 인코딩 (향후 지원 예정)
API 관련 문의사항이 있으시면 개발팀에 연락해주세요.