주식 매도 API 설계를 앞서 요구사항은 다음과 같다.
AI를 활용하여 프롬프트를 작성하고 API를 해결해볼 것이다.
AI에게는 다음과 같은 상황을 고려하여 물어보았다.
주식을 매도를 할때는 동시성 문제가 발생을 할 수 있다고 생각을 하였다.
또한 매도를 할때 매도를 하려는 수량이 원래 가지고 있던 수량보다 많은 양을 택했을때도 문제가 생길 수 있다.
그리고 장외 시간인 경우 등 매도 자체가 불가능한 상황이 있을 수가 있다.
그래서 다음과 같이 API를 만들기 앞서 평소에 쓰던 프롬프트 설계를 사용하여 프롬프트를 작성후 지피티를 활용하여 작성을 하였다.
> 너는 “증권/금융 백엔드 시니어(API 설계 + 동시성 + 정합성 + 운영 전문가)”다.
이후 API가 다뤄야 할 도메인 상황을 설명했다.
> 사용자는 본인이 보유한 주식을 시장가로 매도한다.
> 주문은 즉시 체결되지 않을 수 있으며, 주문 접수와 체결은 분리될 수 있다.
> 중복 매도, 중복 잔고 감소 방지, 감사/추적 가능성, 실패 케이스를 충분히 고려해야 한다.
또한 결과물이 과제 요구사항에 맞게 나오도록 필수 항목을 지정했다.
- URL
- Method
- Request
- Response
- 상태코드
- 정책
특히 금융 API에서는 정상 응답보다 실패 상황과 정합성 처리가 중요하다고 판단해 다음 내용도 함께 요청했다.
- 보유 수량 부족
- 중복 요청
- 다중 탭 요청
- 네트워크 재시도
- 멱등성 처리
- 동시성 제어
- 부분 체결
- 거래정지 및 장외 시간
- 장애 상황 대응
처음에는 AI가 상태조회 API, 취소 API, 체결내역 조회 API까지 함께 제안했다.
하지만 과제의 핵심은 “주식 매도 API 하나를 설계하는 것”이었기 때문에, 이후 프롬프트를 수정해 시장가 매도 주문 접수 API 하나에 집중하도록 다시 요청했다.
최종적으로는 다음과 같은 방향으로 프롬프트를 보완했다.
> 주식 매도 API는 하나만 설계한다.
> 단, 실제 금융 환경을 고려해 주문 접수와 체결은 분리해서 설명한다.
> 필수 항목인 URL, Method, Request, Response, 상태코드, 정책이 반드시 포함되도록 한다.
이 과정을 통해 AI의 초안을 그대로 사용하지 않고, 과제 조건에 맞게 범위를 조정하고 필요한 내용을 선별했다.
/api/v1/accounts/{accountId}/orders/sell/market
POST
Authorization: Bearer {accessToken}
Idempotency-Key: 7b8e4c1a-5c9d-4a2b-91fd-11b7f0d2a901
Content-Type: application/json
{
"instrumentId": "005930", //종목 식별자
"quantity": 10, //매도 수량
"orderType": "MARKET", //주문 유형
"side": "SELL" //매도주문
}
| 검증 항목 | 검증 내용 |
|---|---|
| 로그인 여부 | JWT가 유효한 사용자인지 확인한다. |
| 계좌 소유 여부 | 요청한 accountId가 로그인 사용자의 계좌인지 확인한다. |
| 종목 검증 | 존재하는 종목인지, 현재 거래 가능한 종목인지 확인한다. |
| 주문 유형 검증 | orderType이 시장가 주문을 의미하는 MARKET인지 확인한다. |
| 매도 방향 검증 | side가 매도 주문을 의미하는 SELL인지 확인한다. |
| 수량 검증 | quantity가 1 이상인 정수인지 확인한다. |
| 보유 수량 검증 | 사용자가 해당 종목을 충분히 보유하고 있는지 확인한다. |
| 매도 가능 수량 검증 | 이미 다른 매도 주문에 예약된 수량을 제외하고도 매도 가능한 수량이 충분한지 확인한다. |
| 거래 시간 검증 | 현재 시간이 시장가 매도 주문을 접수할 수 있는 시간인지 확인한다. |
| 거래 제한 검증 | 거래정지, 서킷브레이커, 계좌 제한 등으로 주문이 제한되는 상황인지 확인한다. |
| 멱등성 검증 | 같은 Idempotency-Key로 이미 처리된 요청이 있는지 확인한다. |
201 Created
{
"orderId": "ord-20260519-000001",
"accountId": "acc-10001",
"instrumentId": "005930",
"side": "SELL",
"orderType": "MARKET",
"requestedQuantity": 10,
"filledQuantity": 0,
"remainingQuantity": 10,
"status": "ACCEPTED",
"message": "시장가 매도 주문이 접수되었습니다.",
"riskNotice": "시장가 주문은 시장 상황에 따라 예상 가격과 실제 체결 가격이 달라질 수 있으며, 부분 체결 또는 미체결될 수 있습니다.",
"createdAt": "2026-05-19T16:20:00+09:00",
"traceId": "trc-01HX9ZK8W3Q2"
}
실패 응답은 다음과 같이 반환한다.
{
"timestamp": "2026-05-19T16:20:00+09:00",
"errorCode": "HOLDING_001",
"message": "매도 가능 수량이 부족합니다.",
"reason": "보유 수량 10주 중 7주가 이미 다른 매도 주문에 예약되어 있어 현재 매도 가능 수량은 3주입니다.",
"clientAction": "주문 수량을 줄이거나 기존 미체결 매도 주문을 취소한 뒤 다시 시도하세요.",
"retryable": false,
"traceId": "trc-01HX9ZK8W3Q2"
}
| HTTP 상태코드 | 의미 | 사용 상황 |
|---|---|---|
| 201 Created | 주문 생성 성공 | 시장가 매도 주문이 정상 접수됨 |
| 200 OK | 기존 요청 결과 반환 | 같은 Idempotency-Key로 동일 요청이 재전송되어 기존 주문 결과를 반환 |
| HTTP 상태코드 | 에러코드 | 메시지 | 발생 상황 | 클라이언트 행동 |
|---|---|---|---|---|
| 400 | VALID_001 | 주문 수량은 1주 이상이어야 합니다. | 수량이 0 또는 음수 | 수량 수정 |
| 400 | VALID_002 | 주문 수량 형식이 올바르지 않습니다. | 수량이 소수 또는 문자열 | 정수로 수정 |
| 400 | VALID_003 | 주문 유형이 올바르지 않습니다. | orderType이 MARKET이 아님 | 요청값 수정 |
| 400 | VALID_004 | 매도 주문만 요청할 수 있습니다. | side가 SELL이 아님 | 요청값 수정 |
| 400 | VALID_005 | 종목 코드 형식이 올바르지 않습니다. | 종목 코드 형식 오류 | 종목 코드 수정 |
| 400 | IDEMPOTENCY_001 | Idempotency-Key가 필요합니다. | 멱등성 키 누락 | 키를 포함해 재요청 |
| 400 | IDEMPOTENCY_002 | Idempotency-Key 형식이 올바르지 않습니다. | UUID 형식이 아님 | UUID 형식으로 수정 |
| 401 | AUTH_001 | 로그인이 필요합니다. | JWT 없음 | 로그인 후 재요청 |
| 401 | AUTH_002 | 인증 토큰이 만료되었습니다. | JWT 만료 | 토큰 재발급 후 재요청 |
| 403 | AUTH_003 | 계좌 접근 권한이 없습니다. | 본인 계좌가 아님 | 올바른 계좌 선택 |
| 403 | ACCOUNT_001 | 거래가 제한된 계좌입니다. | 휴면, 압류, 사고 등록 계좌 | 고객센터 문의 |
| 404 | ACCOUNT_404 | 계좌를 찾을 수 없습니다. | 존재하지 않는 계좌 ID | 계좌 ID 확인 |
| 404 | STOCK_404 | 종목을 찾을 수 없습니다. | 존재하지 않는 종목 | 종목 재검색 |
| 409 | HOLDING_001 | 매도 가능 수량이 부족합니다. | 보유 수량 부족 | 수량 줄이기 |
| 409 | HOLDING_002 | 이미 다른 매도 주문에 예약된 수량이 있습니다. | 미체결 매도 주문 존재 | 기존 주문 확인 |
| 409 | ORDER_001 | 동일 종목의 매도 주문이 처리 중입니다. | 동일 계좌/종목 동시 매도 제한 | 기존 주문 상태 확인 |
| 409 | IDEMPOTENCY_003 | 같은 Idempotency-Key로 다른 요청이 전송되었습니다. | 키 재사용 + 본문 다름 | 새 키로 재요청 |
| 409 | CONFLICT_001 | 동시에 처리 중인 주문이 있어 요청을 완료할 수 없습니다. | 다중 탭/동시 요청 | 잠시 후 재시도 |
| 422 | MARKET_001 | 현재는 시장가 주문이 불가능한 시간입니다. | 장외 시간 | 장중 다시 요청 |
| 422 | MARKET_002 | 해당 종목은 거래정지 상태입니다. | 거래정지 종목 | 거래 재개 후 요청 |
| 422 | MARKET_003 | 서킷브레이커로 주문이 제한되었습니다. | 시장 급변으로 거래 제한 | 제한 해제 후 요청 |
| 422 | MARKET_004 | 시장가 주문을 지원하지 않는 종목입니다. | 정책상 시장가 불가 | 지정가 주문 사용 |
| 429 | RATE_LIMIT_001 | 요청이 너무 많습니다. | 짧은 시간 내 반복 요청 | 일정 시간 후 재시도 |
| 500 | SERVER_001 | 일시적인 서버 오류가 발생했습니다. | 예상하지 못한 서버 오류 | 같은 키로 상태 확인 또는 재시도 |
| 503 | INFRA_001 | 주문 처리 시스템을 일시적으로 사용할 수 없습니다. | DB, Redis, 메시지 큐 장애 | 잠시 후 재시도 |
| 504 | EXTERNAL_001 | 외부 주문 시스템 응답이 지연되고 있습니다. | 브로커/거래소 응답 지연 | 같은 키로 재시도 또는 상태 확인 |
API 성공은 체결 완료가 아니라 접수 완료를 의미한다.
실제 체결 가격은 접수 시점에 확정되지 않는다.
주문 접수 시 보유 수량을 바로 차감하지 않고 예약한다.
같은 주문 요청이 중복 처리되지 않도록 Idempotency-Key를 사용한다.
동일 계좌/종목 동시 매도 요청을 락과 트랜잭션으로 제어한다.
정규장 장중에만 시장가 매도를 허용한다.
일부만 체결될 수 있으며 잔여 수량은 계속 관리한다.
이미 체결된 수량은 취소할 수 없고 잔여 수량만 취소 가능하다.
같은 주문 재시도는 반드시 같은 멱등성 키를 사용한다.
과도한 주문 요청을 제한한다.
주문 요청과 상태 변경 이력을 추적 가능하게 남긴다.
외부 시스템 지연과 내부 장애를 고려해 상태를 관리한다.
AI가 잘해준 점은 생각보다 더욱 더 실패 상황을 자세히 설계해준 부분이다. 또한 내가 고려하지 못했던 거래정지 상태라던지 다영한 상황에 대해 고려하여 실패코드를 작성을 한 것 같다.
AI를 활용하면서 API를 작성하는 과정에서 처음에는 주문 매도를 위해 조회를 해야하는 다른 로직들 API까지 작성을 해주었다. 이 부분을 제외하고 매도 부분만 집중하고 싶어서 매도 부분 로직에만 집중을 하도록 프롬프트를 수정을 하였다.
또한 처음에는 단순히 요구 사항만 적고 요청을 했지만 추후에는 맨 위에 적은것과 같이 좀 더 구체화하여 프롬프트를 작성하였다.
초기 버전은 아래와 같다.
초기:
사용자는 자신이 보유한 주식을 시장가로 매도할 수 있습니다.
최대한 실제 금융 환경을 고려하여 API를 설계해주세요.
필수로 포함해야 하는 내용은 다음과 같습니다.
1. URL
2. Method
3. Request
4. Response
5. 실패 응답
- 인증 실패
- 계좌 권한 실패
- 보유 수량 부족
- 존재하지 않는 종목
- 거래 정지 종목
- 시장가 주문 불가능 시간
- 중복 요청
- 동시성 문제
- 외부 시스템 오류
6. 상태 코드
7. 이 API를 사용할 때 고려해야 할 정책
8. 동시성 이슈
이외에도 AI가 작성해준 실패응답에는 타임스탬프가 없어 추가를 하였다.