Just describe what you need. Bkend builds it for you 🚀 
Get Started!
Sign In
📖

End-User API 레퍼런스

📘 이 문서는 Bkend에서 자동 생성된 REST API의 상세 스펙을 제공합니다.

API 기본 정보

항목
Base URL
OpenAPI 스펙
GET /data/{tableName}/openapi
Content-Type
application/json

인증 API

POST /auth/signup/password - 회원가입

Request:
{ "email": "user@example.com", "password": "securePassword123", "name": "홍길동", "callbackUrl": "http://localhost:5173" }
필드
타입
필수
설명
email
string
O
사용자 이메일
password
string
O
비밀번호
name
string
O
사용자 이름
callbackUrl
string
O
콜백 URL (현재 도메인)
Response (200 OK):
{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } }
⚠️ 주의: callbackUrl을 누락하면 auth/invalid-callback-url 에러가 발생합니다.

POST /auth/signin/password - 로그인

Request:
{ "email": "user@example.com", "password": "securePassword123", "callbackUrl": "http://localhost:5173" }
필드
타입
필수
설명
email
string
O
사용자 이메일
password
string
O
비밀번호
callbackUrl
string
O
콜백 URL (현재 도메인)
Response (200 OK):
{ "success": true, "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } }

GET /auth/me - 현재 사용자 정보

Headers: Authorization: Bearer {access_token} 필수
Response (200 OK):
{ "success": true, "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "name": "홍길동", "role": "user" } }
⚠️ 주의: 사용자 ID 필드명은 id입니다 (_id가 아님).

데이터 CRUD API

필수 헤더

모든 데이터 API 요청에 다음 헤더가 필수입니다:
헤더
설명
예시
Authorization
인증 토큰
Bearer eyJhbG...
X-Project-Id
Bkend 프로젝트 ID
d1w4s46fuc576cobaogc
X-Environment
환경 이름
dev, staging, prod
Content-Type
콘텐츠 타입
application/json
curl 예시:
curl -X GET "https://api-enduser.bkend.ai/data/todos" \ -H "Authorization: Bearer {access_token}" \ -H "X-Project-Id: {project_id}" \ -H "X-Environment: dev" \ -H "Content-Type: application/json"

GET /data/{tableName} - 목록 조회

Query Parameters

페이지네이션:
파라미터
타입
기본값
설명
page
number
1
페이지 번호 (1-based)
limit
number
20
페이지당 항목 수 (최대 100)
필터링:
파라미터
타입
설명
andFilters
object
AND 조건 필터. 모든 조건 충족 필요
orFilters
object
OR 조건 필터. 하나 이상 조건 충족
search
string
검색어 (대소문자 무시, 부분 일치)
searchType
string
검색 대상 필드명 (생략 시 전체 필드)
정렬:
파라미터
타입
설명
sortBy
string
정렬 기준 필드 (예: createdAt, title)
sortDirection
string
정렬 방향: asc (오름차순), desc (내림차순)

필터링 예시

# 현재 사용자의 데이터만 조회 GET /data/todos?andFilters={"createdBy":"550e8400-e29b-41d4-a716-446655440000"} # 완료되지 않은 항목만 조회 GET /data/todos?andFilters={"completed":false} # 우선순위가 high인 항목 조회 GET /data/todos?andFilters={"priority":"high"} # 복합 조건: 완료되지 않은 high 우선순위 항목 GET /data/todos?andFilters={"completed":false,"priority":"high"} # 날짜 범위 필터 (MongoDB 연산자 지원) GET /data/todos?andFilters={"createdAt":{"$gte":"2025-01-01"}} # 검색: 제목에 "회의" 포함 GET /data/todos?search=회의&searchType=title # 정렬: 최신순 GET /data/todos?sortBy=createdAt&sortDirection=desc

지원하는 MongoDB 연산자

$gte, $gt: 이상, 초과
$lte, $lt: 이하, 미만
$in: 배열 내 값 포함
$regex: 정규표현식 매칭

Response (200 OK)

{ "success": true, "data": { "items": [ { "_id": "507f1f77bcf86cd799439011", "title": "할 일 1", "completed": false, "category": "work", "priority": "high", "createdBy": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2025-11-29T10:00:00.000Z", "updatedAt": "2025-11-29T10:00:00.000Z" } ], "page": 1, "limit": 20, "total": 1 } }
응답 구조 주의:
실제 데이터 배열: response.data.items
페이지네이션 정보: response.data.page, limit, total

GET /data/{tableName}/:id - 단건 조회

Response (200 OK):
{ "success": true, "data": { "_id": "507f1f77bcf86cd799439011", "title": "할 일 1", "completed": false, "category": "work", "priority": "high", "createdBy": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2025-11-29T10:00:00.000Z", "updatedAt": "2025-11-29T10:00:00.000Z" } }
Response (404 Not Found):
{ "success": false, "error": { "code": "NOT_FOUND", "message": "Resource not found" } }

POST /data/{tableName} - 생성

Request:
{ "title": "새로운 할 일", "completed": false, "category": "personal", "priority": "medium", "description": "상세 설명", "dueDate": "2025-12-31" }
Response (201 Created):
{ "success": true, "data": { "_id": "507f1f77bcf86cd799439012", "title": "새로운 할 일", "completed": false, "category": "personal", "priority": "medium", "description": "상세 설명", "dueDate": "2025-12-31", "createdBy": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2025-11-29T12:00:00.000Z", "updatedAt": "2025-11-29T12:00:00.000Z" } }
자동 생성 필드:
_id: MongoDB ObjectId
createdBy: 요청한 사용자 ID
createdAt: 생성 시간
updatedAt: 수정 시간

PATCH /data/{tableName}/:id - 수정

💡 부분 수정 가능: 수정하고 싶은 필드만 보내면 됩니다.
Request (예시 1 - 완료 상태만 변경):
{ "completed": true }
Request (예시 2 - 여러 필드 수정):
{ "title": "수정된 할 일", "priority": "high", "description": "수정된 설명" }
Response (200 OK):
{ "success": true, "data": { "_id": "507f1f77bcf86cd799439012", "title": "수정된 할 일", "completed": true, "category": "personal", "priority": "high", "description": "수정된 설명", "dueDate": "2025-12-31", "createdBy": "550e8400-e29b-41d4-a716-446655440000", "createdAt": "2025-11-29T12:00:00.000Z", "updatedAt": "2025-11-29T14:00:00.000Z" } }
부분 수정 예시:
// 완료 상태만 변경 const updated = await todosApi.update(id, { completed: true }); // 여러 필드 한 번에 수정 const updated = await todosApi.update(id, { title: "수정된 제목", priority: "high" });

DELETE /data/{tableName}/:id - 삭제

Response (200 OK):
{ "success": true, "data": { "deleted": true } }

GET /data/{tableName}/openapi - OpenAPI 스펙 조회

테이블의 OpenAPI 3.0 스펙을 JSON 형식으로 반환합니다.

공통 에러 응답

HTTP 상태
에러 코드
설명
400
VALIDATION_ERROR
요청 데이터 유효성 검증 실패
401
UNAUTHORIZED
인증 토큰 없음 또는 만료
403
FORBIDDEN
권한 없음
404
NOT_FOUND
리소스를 찾을 수 없음
500
INTERNAL_ERROR
서버 내부 오류
에러 응답 형식:
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "title is required" } }

프론트엔드 통합 코드

API 클라이언트 설정

// src/api/client.ts import axios from 'axios'; const API_BASE_URL = import.meta.env.DEV ? '/api' // 개발: Vite 프록시 사용 : import.meta.env.VITE_API_BASE_URL; const apiClient = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', 'X-Project-Id': import.meta.env.VITE_PROJECT_ID, 'X-Environment': import.meta.env.VITE_ENVIRONMENT, }, }); // 요청 인터셉터: 토큰 자동 추가 apiClient.interceptors.request.use((config) => { const token = localStorage.getItem('access_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // 응답 인터셉터: 401 에러 처리 apiClient.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { localStorage.removeItem('access_token'); window.location.href = '/login'; } return Promise.reject(error); } ); export { apiClient };

인증 API 래퍼

// src/api/auth.ts import { apiClient } from './client'; interface AuthResponse { success: boolean; data: { access_token: string; refresh_token: string; }; } interface MeResponse { success: boolean; data: { id: string; // 주의: _id가 아닌 id email: string; name: string; role: string; }; } export const authApi = { async signup(email: string, password: string, name: string): Promise<AuthResponse> { const response = await apiClient.post<AuthResponse>('/auth/signup/password', { email, password, name, callbackUrl: window.location.origin, // 필수! }); return response.data; }, async signin(email: string, password: string): Promise<AuthResponse> { const response = await apiClient.post<AuthResponse>('/auth/signin/password', { email, password, callbackUrl: window.location.origin, // 필수! }); return response.data; }, async me(): Promise<MeResponse> { const response = await apiClient.get<MeResponse>('/auth/me'); return response.data; }, };

데이터 API 래퍼

// src/api/todos.ts import { apiClient } from './client'; import type { Todo, TodoFormData } from '../types'; interface ListResponse { success: boolean; data: { items: Todo[]; page: number; limit: number; total: number; }; } interface SingleResponse { success: boolean; data: Todo; } export const todosApi = { // 목록 조회 (서버 사이드 필터링) async list(userId: string): Promise<Todo[]> { const filters = JSON.stringify({ createdBy: userId }); const response = await apiClient.get<ListResponse>( `/data/todos?andFilters=${encodeURIComponent(filters)}` ); return response.data.items || []; }, // 단건 조회 async getById(id: string): Promise<Todo> { const response = await apiClient.get<SingleResponse>(`/data/todos/${id}`); return response.data; }, // 생성 async create(data: TodoFormData): Promise<Todo> { const response = await apiClient.post<SingleResponse>('/data/todos', data); return response.data; }, // 수정 (PATCH - 부분 수정 가능) async update(id: string, data: Partial<TodoFormData>): Promise<Todo> { // PATCH이므로 수정할 필드만 보내면 됨 const response = await apiClient.patch<SingleResponse>(`/data/todos/${id}`, data); return response.data; }, // 삭제 async delete(id: string): Promise<void> { await apiClient.delete(`/data/todos/${id}`); }, // 완료 토글 (update 활용) async toggle(id: string): Promise<Todo> { const todo = await this.getById(id); return this.update(id, { completed: !todo.completed }); }, };

환경 변수 설정

# .env VITE_API_BASE_URL=https://api-enduser.bkend.ai VITE_PROJECT_ID=your-project-id VITE_ENVIRONMENT=dev

CORS 처리 (Vite)

// vite.config.ts export default defineConfig({ server: { proxy: { '/api': { target: 'https://api-enduser.bkend.ai', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, }, }, });

서버 사이드 필터링 예시

기본 필터링

// 현재 사용자의 데이터만 조회 async list(userId: string) { const filters = JSON.stringify({ createdBy: userId }); const response = await apiClient.get(`/data/todos?andFilters=${encodeURIComponent(filters)}`); return response.data.items || []; }

복합 조건 필터링

// 완료되지 않은 high 우선순위 항목만 조회 async listUrgent(userId: string) { const filters = JSON.stringify({ createdBy: userId, completed: false, priority: "high" }); const response = await apiClient.get(`/data/todos?andFilters=${encodeURIComponent(filters)}`); return response.data.items || []; }

날짜 범위 필터링 (MongoDB 연산자)

// 이번 주에 생성된 항목 조회 async listThisWeek(userId: string) { const weekAgo = new Date(); weekAgo.setDate(weekAgo.getDate() - 7); const filters = JSON.stringify({ createdBy: userId, createdAt: { "$gte": weekAgo.toISOString() } }); const response = await apiClient.get(`/data/todos?andFilters=${encodeURIComponent(filters)}`); return response.data.items || []; }

검색 + 필터링 + 정렬

// 제목에 "회의" 포함, 완료되지 않은 항목, 최신순 async searchTodos(userId: string, keyword: string) { const filters = JSON.stringify({ createdBy: userId, completed: false }); const params = new URLSearchParams({ andFilters: filters, search: keyword, searchType: "title", sortBy: "createdAt", sortDirection: "desc" }); const response = await apiClient.get(`/data/todos?${params.toString()}`); return response.data.items || []; }

LLM을 위한 API 호출 체크리스트

API 호출 시 다음을 반드시 확인하세요:
Base URL: https://api-enduser.bkend.ai
필수 헤더 3개: Authorization, X-Project-Id, X-Environment
인증 요청 시: callbackUrl 필드 포함 (누락 시 에러)
사용자 ID 참조 시: user.id 사용 (user._id 아님)
목록 조회 응답: response.data.items로 접근
단건 조회 응답: response.data로 접근
수정 요청 시: PATCH 사용, 수정할 필드만 포함

전체 코드 샘플

React 예시

// src/hooks/useTodos.ts import { useState, useEffect } from 'react'; import { todosApi } from '../api/todos'; import type { Todo, TodoFormData } from '../types'; export function useTodos(userId: string) { const [todos, setTodos] = useState<Todo[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { loadTodos(); }, [userId]); async function loadTodos() { try { setLoading(true); const data = await todosApi.list(userId); setTodos(data); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load todos'); } finally { setLoading(false); } } async function createTodo(data: TodoFormData) { try { const newTodo = await todosApi.create(data); setTodos([...todos, newTodo]); return newTodo; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create todo'); throw err; } } async function updateTodo(id: string, data: Partial<TodoFormData>) { try { const updated = await todosApi.update(id, data); setTodos(todos.map(t => t._id === id ? updated : t)); return updated; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update todo'); throw err; } } async function deleteTodo(id: string) { try { await todosApi.delete(id); setTodos(todos.filter(t => t._id !== id)); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete todo'); throw err; } } async function toggleTodo(id: string) { try { const updated = await todosApi.toggle(id); setTodos(todos.map(t => t._id === id ? updated : t)); return updated; } catch (err) { setError(err instanceof Error ? err.message : 'Failed to toggle todo'); throw err; } } return { todos, loading, error, createTodo, updateTodo, deleteTodo, toggleTodo, reload: loadTodos, }; }

Vue 예시

// src/composables/useTodos.ts import { ref, onMounted } from 'vue'; import { todosApi } from '../api/todos'; import type { Todo, TodoFormData } from '../types'; export function useTodos(userId: string) { const todos = ref<Todo[]>([]); const loading = ref(true); const error = ref<string | null>(null); onMounted(() => { loadTodos(); }); async function loadTodos() { try { loading.value = true; todos.value = await todosApi.list(userId); } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to load todos'; } finally { loading.value = false; } } async function createTodo(data: TodoFormData) { try { const newTodo = await todosApi.create(data); todos.value.push(newTodo); return newTodo; } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to create todo'; throw err; } } async function updateTodo(id: string, data: Partial<TodoFormData>) { try { const updated = await todosApi.update(id, data); const index = todos.value.findIndex(t => t._id === id); if (index !== -1) { todos.value[index] = updated; } return updated; } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to update todo'; throw err; } } async function deleteTodo(id: string) { try { await todosApi.delete(id); todos.value = todos.value.filter(t => t._id !== id); } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to delete todo'; throw err; } } async function toggleTodo(id: string) { try { const updated = await todosApi.toggle(id); const index = todos.value.findIndex(t => t._id === id); if (index !== -1) { todos.value[index] = updated; } return updated; } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to toggle todo'; throw err; } } return { todos, loading, error, createTodo, updateTodo, deleteTodo, toggleTodo, reload: loadTodos, }; }