api_spec_v1
작성일: 2026-04-19
기준 문서:
- /home/ubuntu/workspace/autonomous-sales-ops/requirements/requirements_v1.md
- /home/ubuntu/workspace/autonomous-sales-ops/requirements/db_schema_v1.md
상태: v1 초안
1. 목표
이 문서는 주임사 등록 사업자 대상 등록임대 계약신고 반자동 MVP의 서버 API 계약을 고정한다.
핵심 원칙
- 기존 FastAPI 앱(`app/main.py`) 위에 추가하는 방식으로 간다.
- 기존 `/api/intake`, `/api/smart-intake`처럼 단순 JSON 응답 패턴을 유지한다.
- 서버는 판단, 상태 저장, 패키지 생성, 결과 회수를 맡고 실제 로그인/인증/최종 제출은 로컬 실행 에이전트 + 사용자 승인 구간에서 처리한다.
- 인증/권한은 v1에서 간단한 내부 운영 전제로 두되, 나중에 auth 레이어를 추가할 수 있게 엔드포인트 역할을 분리한다.
2. API 범위
포함
- 임대사업자 프로필 등록/조회
- 임대주택 등록/조회
- 계약 사건 생성/조회
- 계약 파일 업로드/조회
- OCR/추출 결과 확인 및 보정
- 신고 필요 여부 판정
- 제출 패키지 생성
- 로컬 에이전트 작업 시작/상태 갱신
- 제출 결과 업로드
- 후속 일정 조회
- 계약 단위 상태 조회
제외
- 정부 사이트 직접 API 제출
- 인증서/비밀번호 서버 저장
- 완전 자동 제출 승인
- 외부 사용자 권한 체계 세분화
- RTMS 전체 별도 업무 자동화
3. 공통 규칙
3.1 Base URL
- 개발 기본값: `http://127.0.0.1:8876`
- 문서/시연 URL: `http://server:8876`
3.2 응답 기본 형식
기존 앱 패턴을 따라 v1도 JSON object를 기본으로 쓴다.
성공 예시:
{
"status": "ok"
}
생성 예시:
{
"status": "created",
"contract_event_id": 12
}
실패 예시:
{
"detail": "contract_event not found"
}
3.3 날짜/시간 규칙
- 날짜 필드: `YYYY-MM-DD`
- 일시 필드: ISO-like text 또는 SQLite 저장용 text
- v1에서는 기존 앱 스타일에 맞춰 `TEXT` 저장을 유지한다.
3.4 파일 규칙
- 업로드는 `multipart/form-data`
- 파일 원본은 서버 저장소에 보관
- DB에는 파일 메타와 저장 경로를 기록
- 파일 종류는 `contract_pdf`, `standard_contract`, `registration_doc`, `change_proof`, `receipt`, `certificate`, `screenshot`, `other` 등으로 구분
3.5 상태 전이 규칙
#### contract_events.event_status
- `draft`
- `classified`
- `ready_for_filing`
- `filed`
- `failed`
- `cancelled`
#### filing_jobs.job_status
- `queued`
- `package_ready`
- `awaiting_login`
- `filling`
- `awaiting_final_approval`
- `submitted`
- `result_collected`
- `failed`
4. 리소스 맵
4.1 landlord_profiles
- 임대사업자 기본 정보
- 핵심 분기: `business_type`, `is_registered_landlord`
4.2 rental_properties
- 주택 단위 정보
- 등록번호/주소/의무임대기간/보증보험 상태 포함
4.3 contract_events
- 계약 사건 본체
- 신규/변경/재계약/묵시적 갱신 저장
4.4 contract_files
- 계약 사건 원본/결과 파일
4.5 filing_jobs
- 신고 작업 단위
- 렌트홈/지자체 제출 진행 상태 저장
4.6 filing_results
- 접수번호/필증/결과 메시지 저장
4.7 followup_tasks
- 신고 이후 후속 일정 저장
4.8 audit_logs
- 변경 이력 저장
5. 엔드포인트 목록
1. `POST /api/landlords`
2. `GET /api/landlords/{landlord_id}`
3. `POST /api/properties`
4. `GET /api/properties/{property_id}`
5. `POST /api/contracts`
6. `GET /api/contracts/{contract_event_id}`
7. `POST /api/contracts/{contract_event_id}/files`
8. `POST /api/contracts/{contract_event_id}/extract`
9. `POST /api/contracts/{contract_event_id}/confirm`
10. `POST /api/contracts/{contract_event_id}/filing-jobs`
11. `GET /api/filing-jobs/{filing_job_id}`
12. `POST /api/filing-jobs/{filing_job_id}/agent-session`
13. `POST /api/filing-jobs/{filing_job_id}/status`
14. `POST /api/filing-jobs/{filing_job_id}/results`
15. `GET /api/contracts/{contract_event_id}/timeline`
16. `GET /api/followups`
6. 엔드포인트 상세
6.1 POST /api/landlords
목적
- 임대사업자 프로필 생성
request body
{
"prospect_id": null,
"business_type": "individual",
"is_registered_landlord": true,
"business_registration_status": "registered",
"business_registration_number": "123-45-67890",
"landlord_name": "홍길동",
"contact_name": "홍길동",
"phone": "010-1111-2222",
"email": "demo@example.com",
"notes": "주임사 등록 사업자"
}
response body
{
"status": "created",
"landlord_profile_id": 1
}
읽고/쓰는 테이블
- write: `landlord_profiles`
- optional read: `prospects`
- write: `audit_logs`
실패 규칙
- `business_type`가 `individual`/`corporate`가 아니면 400
- 필수값 누락이면 422
6.2 GET /api/landlords/{landlord_id}
목적
- 임대사업자 기본 정보 조회
response body
{
"id": 1,
"business_type": "individual",
"is_registered_landlord": true,
"landlord_name": "홍길동",
"phone": "010-1111-2222",
"email": "demo@example.com"
}
읽고/쓰는 테이블
- read: `landlord_profiles`
실패 규칙
- 없으면 404
6.3 POST /api/properties
목적
- 임대주택 생성
request body
{
"landlord_profile_id": 1,
"property_name": "마포구 ○○빌라 301호",
"road_address": "서울특별시 마포구 ...",
"jibun_address": "서울특별시 마포구 ...",
"registered_number": "서울-주임사-0001",
"mandatory_rental_period_start": "2024-01-01",
"mandatory_rental_period_end": "2032-01-01",
"guarantee_insurance_status": "active",
"bookkeeping_registration_status": "done"
}
response body
{
"status": "created",
"rental_property_id": 3
}
읽고/쓰는 테이블
- read: `landlord_profiles`
- write: `rental_properties`
- write: `audit_logs`
실패 규칙
- `landlord_profile_id` 없으면 404
6.4 GET /api/properties/{property_id}
목적
- 임대주택 상세 조회
읽고/쓰는 테이블
- read: `rental_properties`
실패 규칙
- 없으면 404
6.5 POST /api/contracts
목적
- 계약 사건 생성
- 최초 상태는 `draft`
request body
{
"landlord_profile_id": 1,
"rental_property_id": 3,
"event_type": "new_contract",
"contract_signed_at": "2026-04-19",
"lease_start_date": "2026-05-01",
"lease_end_date": "2028-04-30",
"deposit_amount": 50000000,
"monthly_rent": 1200000,
"tenant_name": "김세입",
"tenant_phone": "010-9999-8888",
"change_summary": null,
"previous_contract_event_id": null
}
response body
{
"status": "created",
"contract_event_id": 12,
"event_status": "draft"
}
읽고/쓰는 테이블
- read: `landlord_profiles`, `rental_properties`
- write: `contract_events`
- write: `audit_logs`
실패 규칙
- `event_type` 허용값 외면 400
- 상위 landlord/property가 없으면 404
6.6 GET /api/contracts/{contract_event_id}
목적
- 계약 사건 상세 조회
- 계약 본문 + 파일 목록 + filing summary 포함 가능
response body
{
"id": 12,
"event_type": "new_contract",
"event_status": "draft",
"filing_required": 0,
"filing_required_reason": null,
"rtms_check_required": 0,
"extracted_data_json": null,
"corrected_data_json": null,
"files": [],
"filing_jobs": []
}
읽고/쓰는 테이블
- read: `contract_events`, `contract_files`, `filing_jobs`, `filing_results`
실패 규칙
- 없으면 404
6.7 POST /api/contracts/{contract_event_id}/files
목적
- 계약 사건 파일 업로드
content-type
- `multipart/form-data`
form fields
- `file_kind`: `contract_pdf` 등
- `files`: 1개 이상
response body
{
"status": "uploaded",
"contract_event_id": 12,
"saved_files": [
{
"contract_file_id": 55,
"file_kind": "contract_pdf",
"file_path": "/.../uploads/contracts/12/abc.pdf"
}
]
}
읽고/쓰는 테이블
- read: `contract_events`
- write: `contract_files`
- write: `audit_logs`
실패 규칙
- event가 없으면 404
- 파일이 비어 있으면 400
6.8 POST /api/contracts/{contract_event_id}/extract
목적
- OCR/필드 추출 실행
- v1에서는 실제 OCR 엔진 호출 또는 수동 입력 기반 fallback 둘 다 허용
request body
{
"mode": "auto"
}
response body
{
"status": "classified",
"contract_event_id": 12,
"event_status": "classified",
"extracted_data": {
"road_address": "서울특별시 마포구 ...",
"deposit_amount": 50000000,
"monthly_rent": 1200000,
"lease_start_date": "2026-05-01",
"lease_end_date": "2028-04-30",
"tenant_name": "김세입"
}
}
읽고/쓰는 테이블
- read: `contract_events`, `contract_files`
- write: `contract_files.ocr_status`, `contract_files.ocr_text`
- write: `contract_events.extracted_data_json`
- write: `contract_events.event_status`
- write: `audit_logs`
실패 규칙
- 파일이 하나도 없으면 400
- OCR 실패 시 `status=failed`, `detail` 반환 가능
6.9 POST /api/contracts/{contract_event_id}/confirm
목적
- 사용자 보정값 저장
- 신고 필요 여부, 사건 유형, RTMS 확인 필요 여부를 확정
- 필요 시 `ready_for_filing`으로 올림
request body
{
"event_type": "new_contract",
"corrected_data": {
"road_address": "서울특별시 마포구 ...",
"registered_number": "서울-주임사-0001",
"deposit_amount": 50000000,
"monthly_rent": 1200000,
"lease_start_date": "2026-05-01",
"lease_end_date": "2028-04-30",
"tenant_name": "김세입"
},
"filing_required": true,
"filing_required_reason": "주임사 등록 주택 신규 계약",
"rtms_check_required": true
}
response body
{
"status": "confirmed",
"contract_event_id": 12,
"event_status": "ready_for_filing",
"filing_required": true
}
읽고/쓰는 테이블
- read: `contract_events`, `rental_properties`, `landlord_profiles`
- write: `contract_events.corrected_data_json`
- write: `contract_events.filing_required`
- write: `contract_events.filing_required_reason`
- write: `contract_events.rtms_check_required`
- write: `contract_events.event_status`
- write: `audit_logs`
실패 규칙
- 신고 필요인데 필수 입력이 비면 400
- 등록임대 흐름인데 `is_registered_landlord != 1`이면 경고 또는 400
6.10 POST /api/contracts/{contract_event_id}/filing-jobs
목적
- 제출 패키지 생성
- `filing_required = 1`인 계약만 가능
request body
{
"channel": "renthome",
"assigned_agent": "local-agent-1"
}
response body
{
"status": "created",
"filing_job_id": 20,
"job_status": "package_ready",
"package": {
"channel": "renthome",
"input_values": {
"tenant_name": "김세입",
"deposit_amount": 50000000,
"monthly_rent": 1200000
},
"attachments": [
"contract_pdf",
"registration_doc"
]
}
}
읽고/쓰는 테이블
- read: `contract_events`, `contract_files`, `rental_properties`, `landlord_profiles`
- write: `filing_jobs`
- write: `filing_job_files` (generated packet link 가능)
- write: `audit_logs`
실패 규칙
- `filing_required != 1`이면 400
- 필수 첨부 누락이면 400
- `channel` 허용값 외면 400
6.11 GET /api/filing-jobs/{filing_job_id}
목적
- 제출 작업 상세 조회
response body
{
"id": 20,
"channel": "renthome",
"job_status": "package_ready",
"requires_human_approval": true,
"package_json": {
"input_values": {}
},
"checklist_json": {
"required_files": []
},
"result": null
}
읽고/쓰는 테이블
- read: `filing_jobs`, `filing_job_files`, `filing_results`
실패 규칙
- 없으면 404
6.12 POST /api/filing-jobs/{filing_job_id}/agent-session
목적
- 로컬 실행 에이전트가 작업 시작을 선언
- 상태를 `awaiting_login` 또는 `filling`으로 올리는 시작점
request body
{
"agent_id": "macbook-hb-local",
"action": "start"
}
response body
{
"status": "accepted",
"filing_job_id": 20,
"job_status": "awaiting_login"
}
읽고/쓰는 테이블
- read: `filing_jobs`
- write: `filing_jobs.assigned_agent`
- write: `filing_jobs.started_at`
- write: `filing_jobs.job_status`
- write: `audit_logs`
실패 규칙
- 이미 종료 상태면 400
6.13 POST /api/filing-jobs/{filing_job_id}/status
목적
- 로컬 실행 에이전트가 진행 상태 갱신
- 로그인 대기, 입력 중, 최종 승인 대기, 실패 등을 서버에 기록
request body
{
"job_status": "awaiting_final_approval",
"error_message": null,
"payload": {
"current_step": "attachments_uploaded"
}
}
response body
{
"status": "updated",
"filing_job_id": 20,
"job_status": "awaiting_final_approval"
}
읽고/쓰는 테이블
- read: `filing_jobs`
- write: `filing_jobs.job_status`
- write: `filing_jobs.error_message`
- write: `audit_logs.payload_json`
실패 규칙
- 허용되지 않은 상태 전이면 400
6.14 POST /api/filing-jobs/{filing_job_id}/results
목적
- 제출 결과 업로드
- 접수번호, 필증, 영수증, 캡처, 오류 메시지 저장
- 성공 시 후속 일정 생성
content-type
- `multipart/form-data` 또는 JSON + file upload 분리 둘 다 허용 가능
- v1 권장: multipart
form/json 예시
{
"submission_status": "success",
"receipt_number": "2026-RT-000123",
"result_message": "정상 접수",
"collected_at": "2026-04-19T16:20:00"
}
response body
{
"status": "collected",
"filing_job_id": 20,
"contract_event_id": 12,
"job_status": "result_collected",
"event_status": "filed",
"followup_task_ids": [31, 32]
}
읽고/쓰는 테이블
- read: `filing_jobs`, `contract_events`
- write: `filing_results`
- write: `filing_job_files`
- write: `followup_tasks`
- write: `filing_jobs.job_status`, `submitted_at`, `completed_at`
- write: `contract_events.event_status`
- write: `audit_logs`
실패 규칙
- `submission_status=success`인데 접수번호가 없으면 400
- 파일 업로드 실패 시 500 또는 400
6.15 GET /api/contracts/{contract_event_id}/timeline
목적
- 계약 단위 타임라인 조회
- 운영 화면/상태판에서 바로 쓰는 읽기 API
response body
{
"contract_event_id": 12,
"event_status": "filed",
"filing_jobs": [
{
"filing_job_id": 20,
"channel": "renthome",
"job_status": "result_collected",
"receipt_number": "2026-RT-000123"
}
],
"followups": [
{
"task_kind": "insurance_check",
"task_status": "pending",
"due_at": "2026-05-01"
}
]
}
읽고/쓰는 테이블
- read: `contract_events`, `filing_jobs`, `filing_results`, `followup_tasks`
실패 규칙
- 없으면 404
6.16 GET /api/followups
목적
- 후속 일정 목록 조회
- 필터 기반 운영 리스트 API
query params
- `task_status` optional
- `task_kind` optional
- `due_from` optional
- `due_to` optional
- `contract_event_id` optional
response body
{
"items": [
{
"id": 31,
"contract_event_id": 12,
"task_kind": "insurance_check",
"task_status": "pending",
"due_at": "2026-05-01",
"note": "보증보험 상태 재확인"
}
]
}
읽고/쓰는 테이블
- read: `followup_tasks`
7. 상태 전이 규칙 상세
7.1 계약 사건
- `draft` → 계약 생성 직후
- `classified` → OCR/추출 후
- `ready_for_filing` → 사용자 보정 + 신고 필요 여부 확정 후
- `filed` → 결과 회수 성공 후
- `failed` → 작업 실패 후 복구 필요 상태
- `cancelled` → 운영자가 중단 처리
7.2 제출 작업
- `queued` → job 생성 직후
- `package_ready` → 패키지 생성 완료
- `awaiting_login` → 로컬 에이전트 시작, 사용자 로그인 대기
- `filling` → 자동 입력/첨부 진행 중
- `awaiting_final_approval` → 최종 제출 직전
- `submitted` → 제출 버튼 실행 후 결과 대기
- `result_collected` → 접수번호/필증 회수 완료
- `failed` → 제출 실패 또는 회수 실패
8. 실패 응답 규칙
8.1 공통
- validation error: 422
- not found: 404
- business rule violation: 400
- unexpected server error: 500
8.2 business rule 예시
- 등록 사업자가 아닌데 등록임대 계약신고 job 생성 시도
- 필수 첨부 없이 패키지 생성 시도
- 결과 업로드 시 success인데 접수번호 없음
- 이미 `result_collected` 상태인 job에 다시 start 요청
9. 기존 app/main.py 반영 원칙
- 기존 라우트 아래에 `/api/contracts...`, `/api/filing-jobs...`, `/api/followups` 추가
- 기존 `/api/intake`, `/api/smart-intake`, `/api/dashboard/summary`는 유지
- docs/custom HTML 라우트보다 API 라우트를 먼저 선언해 경로 충돌을 피함
- 응답 형식은 기존처럼 단순 dict 반환을 우선
10. 구현 우선순위
1. `POST /api/landlords`
2. `POST /api/properties`
3. `POST /api/contracts`
4. `POST /api/contracts/{id}/files`
5. `POST /api/contracts/{id}/confirm`
6. `POST /api/contracts/{id}/filing-jobs`
7. `POST /api/filing-jobs/{id}/results`
8. `GET /api/contracts/{id}/timeline`
9. `GET /api/followups`
11. 바로 다음 작업
- `local_agent_spec_v1.md` 작성
- 로컬 에이전트가 어떤 시점에 `/agent-session`, `/status`, `/results`를 호출하는지 고정
- `test_scenarios_v1.md`에서 신규 계약/변경 계약/재계약/실패 복구 케이스를 이 API 기준으로 정리