{"openapi":"3.1.0","info":{"title":"LIG 룸 예약 API","version":"1.0.0","description":"책상 공유 공간의 예약·승인 API. 모든 시각은 KST(+09:00) 기준 ISO 8601이다. 키 발급은 https://lig-room.vercel.app/account. MCP 서버는 POST /api/mcp, 서비스 소개는 /llms.txt."},"servers":[{"url":"https://lig-room.vercel.app"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"read","description":"조회 (reservations:read)"},{"name":"write","description":"본인 예약 신청·취소 (reservations:write)"},{"name":"admin","description":"승인·거절·재배정 (admin:approve + ADMIN 계정)"}],"paths":{"/api/v1/me":{"get":{"tags":["read"],"summary":"내 계정·키 스코프·호출 가능한 엔드포인트","description":"스코프를 요구하지 않는다. 에이전트가 처음 붙어 무엇을 할 수 있는지 확인하는 입구.","responses":{"200":{"description":"계정·키 정보","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/desks":{"get":{"tags":["read"],"summary":"책상 목록","description":"type=FLEX(예약석)만 예약할 수 있다. FIXED는 지정석이라 배정자 전용.","responses":{"200":{"description":"책상 배열 (id, name, type, reservable, assignedTo, note, x, y)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/availability":{"get":{"tags":["read"],"summary":"빈 시간대 조회","description":"빈 시간(slots)은 승인 완료(APPROVED) 예약만 뺀 결과다. 승인 대기(PENDING)는 확정이 아니라 slots에 남아 있고 pending 배열로 따로 알려준다.","parameters":[{"name":"from","in":"query","schema":{"type":"string","description":"조회 시작 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"description":"생략하면 오늘 KST 자정"},{"name":"to","in":"query","schema":{"type":"string","description":"조회 종료 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"description":"생략하면 from+7일. 구간 최대 31일"},{"name":"deskId","in":"query","schema":{"type":"string"},"description":"특정 책상만 볼 때. 생략하면 예약석 전체"}],"responses":{"200":{"description":"{ from, to, desks: [{ deskId, deskName, slots, booked, pending }] }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/reservations":{"get":{"tags":["read"],"summary":"예약 목록 (기본은 내 예약)","parameters":[{"name":"status","in":"query","schema":{"type":"string","example":"PENDING,APPROVED"},"description":"PENDING | APPROVED | REJECTED | CANCELLED 를 콤마로 구분. 생략하면 전체"},{"name":"from","in":"query","schema":{"type":"string","description":"조회 시작 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"}},{"name":"to","in":"query","schema":{"type":"string","description":"조회 종료 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"description":"생략하면 from+30일. 구간 최대 31일"},{"name":"all","in":"query","schema":{"type":"boolean"},"description":"true면 전체 예약. ADMIN 계정의 admin:approve 스코프 키만 쓸 수 있다"}],"responses":{"200":{"description":"{ scope, from, to, reservations: [...] }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["write"],"summary":"예약 신청","description":"성공 응답이 확정을 뜻하지 않는다. data.status(PENDING|APPROVED|MIXED)와 data.needsApproval, data.overlaps를 반드시 확인한다. 일반 회원은 승인 대기로 들어가고, SUPER·ADMIN은 자동 승인되지만 겹치는 회차는 대기로 남는다. 반복은 weekdays(요일 복수 선택) + repeatWeeks(N주간) 또는 until(종료일까지)로 지정한다. repeatWeeks와 until은 함께 쓸 수 없고 한 번에 40건까지다. 예: 매주 월·수·금 4주간 = {weekdays:[1,3,5], repeatWeeks:4} (12건).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["deskId","startsAt","endsAt"],"properties":{"deskId":{"type":"string","description":"예약석(type=FLEX) id"},"startsAt":{"type":"string","description":"예약 시작 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"endsAt":{"type":"string","description":"예약 종료 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"repeatWeeks":{"type":"integer","minimum":2,"maximum":26,"description":"반복할 주 수(시작 시각이 속한 주 포함). until과 함께 쓸 수 없고, 단발이면 생략"},"weekdays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6},"description":"반복할 요일(0=일 ~ 6=토). 예: 매주 월·수·금이면 [1,3,5]. 생략하면 startsAt의 요일 하나. 시작 시각보다 이른 회차는 만들지 않는다"},"until":{"type":"string","description":"그 날짜(KST)까지 포함해 생성한다. repeatWeeks와 함께 쓸 수 없다","example":"2026-08-25T14:00:00+09:00"}}},"example":{"deskId":"ck...","startsAt":"2026-08-25T14:00:00+09:00","endsAt":"2026-08-25T16:00:00+09:00"}}}},"responses":{"200":{"description":"{ status, needsApproval, reservations, overlaps, hint, ... }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/reservations/{id}":{"delete":{"tags":["write"],"summary":"내 예약 취소","description":"본인 예약만 취소할 수 있다. 남의 예약 id를 주면 403이 아니라 404다(예약 id를 훑어 존재를 캐낼 수 없게 일부러 구분하지 않는다).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"예약 id"},{"name":"series","in":"query","schema":{"type":"string","enum":["after"]},"description":"\"after\"면 그 회차 이후 같은 반복 묶음 전체를 취소. 지난 회차는 그대로 둔다"}],"responses":{"200":{"description":"{ cancelled, series, reservation }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"없는 예약이거나 본인 예약이 아님 (NOT_FOUND)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/pending":{"get":{"tags":["admin"],"summary":"승인 대기 목록 (반복은 묶음 요약)","description":"occurrences[].overlapsApproved가 true면 이미 승인된 예약과 겹쳐 그대로는 승인되지 않는다.","responses":{"200":{"description":"{ count, groups: [{ recurrenceId, reservationId, occurrences, ... }] }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/reservations/{id}/approve":{"post":{"tags":["admin"],"summary":"승인 / 재배정 승인 / 묶음 승인","description":"body가 비면 그대로 승인. deskId·startsAt·endsAt 중 하나라도 주면 재배정 승인(안 준 값은 원래 값 유지). series=true면 같은 반복 묶음의 대기 회차를 한 번에 승인(최대 20건, 겹치는 회차는 건너뛴다). series와 재배정은 함께 쓸 수 없다. 승인·재배정 시 신청자에게 알림이 발송된다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"예약 id"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deskId":{"type":"string","description":"재배정할 책상 id"},"startsAt":{"type":"string","description":"재배정할 시작 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"endsAt":{"type":"string","description":"재배정할 종료 시각. KST(+09:00) 기준 ISO 8601. 오프셋을 생략하면 KST로 해석하고, 날짜만(\"2026-08-25\") 주면 그날 KST 자정.","example":"2026-08-25T14:00:00+09:00"},"series":{"type":"boolean","description":"반복 묶음 일괄 승인"}}}}}},"responses":{"200":{"description":"{ reservationId, status:'APPROVED', applicant, hint } 또는 묶음/재배정 결과","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"예약 없음 (NOT_FOUND)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"이미 승인된 예약과 겹침 (OVERLAP). 재배정 승인하거나 거절해야 한다","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/reservations/{id}/reject":{"post":{"tags":["admin"],"summary":"거절 / 묶음 거절","description":"신청자에게 거절 알림이 발송되며 되돌릴 수 없다(다시 하려면 신청자가 새로 신청해야 한다).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"예약 id"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"거절 사유. 알림과 감사 로그에 함께 남는다"},"series":{"type":"boolean","description":"반복 묶음 일괄 거절 (최대 20건)"}}},"example":{"reason":"그 시간대는 정기 회의가 있습니다."}}}},"responses":{"200":{"description":"{ reservationId, status:'REJECTED', reason, applicant, hint }","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"400":{"description":"잘못된 요청(BAD_REQUEST·INVALID_TIME·PAST·INVALID_WEEKS 등)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"키 없음·알 수 없는 키·폐기·만료 (MISSING_TOKEN·INVALID_KEY·KEY_REVOKED·KEY_EXPIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"스코프 부족·관리자 아님·임시 비밀번호 상태 (SCOPE_REQUIRED·ADMIN_REQUIRED·PASSWORD_CHANGE_REQUIRED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"예약 없음 (NOT_FOUND)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"레이트 리밋 초과 (RATE_LIMITED). 분당 전체 60회, 쓰기 10회. error.retryAfter 이후에 다시 호출한다.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Authorization: Bearer lig_sk_... — 키 발급은 https://lig-room.vercel.app/account"}},"schemas":{"Ok":{"type":"object","required":["ok","data","message"],"properties":{"ok":{"const":true},"data":{"description":"엔드포인트별 결과"},"message":{"type":"string","description":"사람이 그대로 읽어줄 수 있는 한국어 요약"},"_links":{"type":"object","description":"이어서 부를 만한 엔드포인트","additionalProperties":{"type":"string"}}}},"Error":{"type":"object","required":["ok","error"],"properties":{"ok":{"const":false},"error":{"type":"object","required":["code","message","hint"],"properties":{"code":{"type":"string","example":"OVERLAP"},"message":{"type":"string","description":"무엇이 잘못됐는지"},"hint":{"type":"string","description":"다음에 무엇을 할지. 에이전트는 이걸 먼저 읽는다"},"retryAfter":{"type":"string","description":"RATE_LIMITED일 때만. 이 시각 이후 재시도","example":"2026-08-25T14:00:00+09:00"}}}}}}}}