문서 목록으로 돌아가기
requirements / api_spec_v1.md

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 기준으로 정리