AI 작업 승인과 감사 로그
AI 에이전트의 고위험 변경을 승인하고, 모든 변경을 누가 했는지 추적하기
AI 에이전트의 고위험 작업을 승인하고, 관리 API로 한 모든 변경을 감사 로그로 추적합니다.
환불·가격 변경처럼 되돌리기 어려운 작업은 승인 대기로 두고 셀러가 승인해야 실행합니다. 결제 설정·자격 증명처럼 맡기지 않는 작업은 거부하고, 나머지는 바로 실행합니다. 판정은 서버가 하므로 에이전트 설정으로 우회되지 않습니다.
에이전트로 보는 요청
- Sayren 계정 › 액세스 토큰에서 AI 에이전트용으로 만든 토큰의 요청. 만든 뒤 바꿀 수 없고, 다른 도구로 옮겨 써도 에이전트 요청입니다.
- 원격 MCP에 Sayren 계정 로그인으로 연결한 요청.
@sayren/mcp가 보낸 요청. AI 에이전트용이 아닌 액세스 토큰을 넣어도 에이전트 요청입니다.
판정 규칙
에이전트 요청에만 적용합니다. 상점 플랫폼 사용자·일반 액세스 토큰·연동 앱의 요청은 스코프 안에서 바로 실행됩니다.
| 판정 | 작업 |
|---|---|
| 승인 필요 | 판매자 직권취소, 클레임 승인(환불), 현금영수증 재발급 |
| 승인 필요 | 상품 일부 수정의 가격·배송비 변경(값이 달라질 때만), 상품 전체 수정(항상), 옵션 구성 교체, 할인 설정·해제, 상품 삭제 |
| 승인 필요 | 웹훅 등록·수정, 상점 설정 변경(약관 버전·주소 제외), 구매자 탈퇴 처리 |
| 승인 필요 | 쿠폰 만들기·규칙 수정·지급·재개 |
| 승인 필요 | 주문 흐름 게시·버전 옮기기, 주문상품·주문 전체의 흐름 행동, 상점 사건, 상태 직접 이동(초안 작성·검사·시뮬레이션은 바로 됩니다) |
| 승인 필요 | 스토어프론트 발행·되돌리기·공개 전환·주소 변경·끄기, 내 도메인 추가·대표 지정·해제, 로컬 소스 올리기 중 package.json·vite.config.ts 변경 |
| 거부 | 결제(PG) 설정과 자격 증명, 웹훅 시크릿 교체, 소셜 로그인 키, 구매자 로그인 방식 변경, 약관 버전·주소를 바꾸는 상점 수정, 구성원 초대와 역할 변경, 주문 흐름 시크릿 저장·삭제, 구매자 위임, 승인 API(approve·reject)로 하는 승인·거절(MCP 대화에서 승인하기는 사람 확인을 거칩니다) |
나머지(상품 등록, 이름·상태 수정, 재고 변경, 발송 처리, 문의 답변 등)는 바로 실행됩니다. 오퍼레이션별 판정은
관리 API 레퍼런스의 x-agent-policy에 있습니다.
공개 전 상점
스토어프론트를 한 번도 공개하지 않았고 실결제 주문이 없는 상점에서는 쇼핑몰을 만드는 작업의 승인을 생략합니다. 대상은 상점 설정 변경, 적립금 정책 변경·적립금 지급, 상품 수정·전체 수정·옵션 구성 교체·날짜별 재고·할인 설정·해제·삭제, 스토어프론트 발행·되돌리기, 편집 소스 교체입니다. 공개 전환·주소 변경·도메인·웹훅·쿠폰·주문 같은 나머지 승인 필요 작업과 거부 작업은 그대로입니다. 스토어프론트를 한 번 공개하면 이후로는 다시 비공개로 바꿔도 승인이 필요합니다.
에이전트가 할 수 없는 작업
거부된 작업은 403 AGENT_OPERATION_DENIED이고 아무것도 바뀌지 않습니다. 승인으로도 풀리지 않으니 상점 플랫폼에서 직접 하십시오.
AI 작업 한도
에이전트의 쓰기가 실행되어 성공하면 AI 작업 1회로 셉니다. 승인 요청 생성·드라이런·거부된 작업·MCP 대화에서 사람이 내린 승인·거절은 세지 않고, 승인 뒤 실행은 실행할 때 셉니다.
요금제의 MCP 쓰기 월 한도를 다 쓰면 새 쓰기와 새 승인 요청이 429 PLAN_AI_ACTIONS_EXCEEDED입니다. 이미 승인한 작업과 발주 확인·발송 같은
거래 처리는 막지 않습니다. 남은 횟수는 GET /v1/store/subscription의 aiActions로 봅니다. 요금제와 한도
영향 미리보기
변경 요청에 x-sayren-dry-run: 1 헤더를 붙이면 실행하지 않고 판정과 영향만 돌려줍니다(meta.code = DRY_RUN). 누구나 쓸 수
있습니다. 아래는 PATCH /v1/products/prod_001에 { "salePrice": 25900 }을 보낸 응답의 data입니다.
{
"dryRun": true,
"decision": "APPROVAL_REQUIRED",
"reason": "상품 가격·배송비를 바꾼다",
"preview": {
"operationId": "ProductsController_patch",
"target": { "type": "products", "id": "prod_001" },
"changes": [{ "field": "salePrice", "before": 19900, "after": 25900 }],
"facts": { "productName": "베이직 코튼 티셔츠", "bodyFields": "salePrice" }
}
}decision은 ALLOW·APPROVAL_REQUIRED·DENIED입니다. changes는 달라지는 필드, facts는 작업별 영향 요약입니다(직권취소는
취소 수량·예상 상품 환불액, 클레임 승인은 종류·수량·예상 환불액). 예상 금액은 배송비 정산을 뺀 미리보기 값이고 확정 금액은 실행
응답입니다. MCP call_api_write는 dryRun: true로 같은 미리보기를 받아 변경 전에 확인을 받습니다.
상점 정보 수정(PATCH /v1/store)의 changes는 본문에 준 필드 중 값이 달라지는 것만 싣습니다. 배송비 정책·환불 정책은 항목마다
shippingPolicy.freeThreshold·refundPolicy.returnDeliveryFee처럼 나뉘고, 사업자등록번호는 저장될 숫자 형식으로 비교합니다.
드라이런도 실행과 같은 요청 검증을 거칩니다. 본문이 형식에 맞지 않으면 판정 대신 실행과 같은 400 VALIDATION_FAILED(fieldErrors 포함)를 돌려줍니다.
실행하면 실패할 것을 미리 알 수 있는 요청은 드라이런과 승인 요청 단계에서 실행과 같은 오류를 줍니다. 승인해도 실패할 요청은 승인 요청을 만들지 않습니다.
| 작업 | 미리 주는 오류 |
|---|---|
| 흐름 게시 | 검사에 차단 오류가 있으면 409 PUBLISH_BLOCKED(details.errors) |
| 구매자 적립금 지급 | expiresAt도 정책 유효 기간도 없으면 400 POINT_EXPIRY_REQUIRED |
| 구매자 적립금 차감 | 잔액보다 많으면 409 POINT_BALANCE_INSUFFICIENT |
| 적립 정책 수정 | 적립을 켜는데 유효 기간이 없으면 400 POINT_EXPIRY_REQUIRED |
미리보기 targetName은 대상 이름(상품명·쿠폰명·흐름 이름·구매자 이름)입니다. 흐름 게시는 facts에 흐름 이름·지정 상품 수·차단 오류·경고 수를, 적립금 지급·차감은 구매자·금액·잔액 전후·소멸 시각을, 적립 정책 수정은 항목별 전후(changes)를 싣습니다.
승인 대기 응답
승인이 필요한 에이전트 요청은 실행하지 않고 202(meta.code = APPROVAL_REQUIRED)와 승인 요청을 돌려줍니다(아래는 data).
{
"approvalId": "apr_3f9c2a1b7e40",
"status": "PENDING",
"operationId": "OrdersController_cancelBySeller",
"reason": "셀러 직권취소는 PG 환불로 이어져 되돌릴 수 없다",
"expiresAt": "2026-09-28T03:00:00.000Z",
"approvalUrl": "https://store.sayren.app/s/store_123/settings/approvals?approvalId=apr_3f9c2a1b7e40"
}- 승인 기한은 24시간이고, 지나면
EXPIRED로 실행되지 않습니다. - 같은 토큰·같은
Idempotency-Key로 다시 보내면 처음 승인 요청을 돌려줍니다. 메서드·경로·본문이 다르면409 IDEMPOTENCY_KEY_REUSED입니다. - 그 승인 요청이 끝났으면
200(meta.code=APPROVAL_ALREADY_DECIDED)입니다.SUCCEEDED·FAILED·UNKNOWN이면result를 보고,REJECTED·EXPIRED면 새 멱등키로 다시 요청하십시오. - 요청한 토큰은
GET /v1/approval-requests/{approvalId}(MCPget_approval_request)로 상태를 봅니다.
Store SDK는 두 응답을 ApprovalPendingError로 던집니다(data가 리소스가 아니라 승인 요청이므로).
import { ApprovalPendingError } from "@sayren/store-sdk";
try {
await api.products.patch("prod_001", { salePrice: 25900 });
} catch (error) {
// error.code: APPROVAL_REQUIRED(보류) · APPROVAL_ALREADY_DECIDED(같은 멱등키의 승인이 끝남)
if (error instanceof ApprovalPendingError) console.log(error.approvalId, error.approval.approvalUrl);
}승인 요청 처리하기
상점 플랫폼에서 설정 › 보안 › 승인 요청으로 이동합니다(소유자·관리자). 대기 중인 요청이 있으면 메뉴 옆에 건수가, 왼쪽 설정 아이콘에 점이 보입니다.
승인 대기 요청이 생기면 그 요청을 승인할 수 있는 소유자·관리자에게 메일로 알립니다. 메일의 **[승인 요청 보기]**가 이 화면을 엽니다.
- 같은 상점의 요청은 10분에 한 번 모아서 보냅니다. 그 사이에 생긴 요청은 다음 메일에 함께 실립니다.
- 스토어프론트 호스팅 끄기처럼 소유자만 승인하는 요청은 소유자에게만 갑니다. 스태프에게는 보내지 않습니다.
- 메일이 실패해도 요청은 그대로 대기합니다. 상점 플랫폼의 건수 표시로 확인하십시오.
- 목록(기본 상태 승인 대기)에서 요청을 선택합니다.
- 승인 요청 상세에서 사유·요청 본문·영향 미리보기를 확인합니다.
- 필요하면 결정 메모를 남기고 [승인] 또는 **[거절]**을 누릅니다.
- 승인하면 원 요청(메서드·경로·쿼리·본문·멱등키)을 한 번 실행합니다. 응답은
EXECUTING이고 결과가 나오면 화면이 바뀝니다. - 실행 권한은 요청 토큰과 승인자 권한의 교집합입니다. 그사이 토큰 폐기·로그아웃이면
401로 실패합니다. - 승인자도 원 요청 권한이 필요합니다. 스토어프론트 호스팅 끄기는 OWNER만 승인합니다(ADMIN은
403 INSUFFICIENT_ROLE). - 이미 처리된 요청은
409 APPROVAL_NOT_PENDING, 기한이 지났으면409 APPROVAL_EXPIRED입니다. 동시에 눌러도 한 번만 실행됩니다.
| 상태 | 뜻 |
|---|---|
PENDING | 승인 대기 |
EXECUTING | 승인 후 실행 중 |
SUCCEEDED | 실행 완료. result에 원 요청 응답 |
FAILED | 원 요청이 실패. result.error에 원인 |
UNKNOWN | 실행 결과 미확인(오래 걸렸거나 서버 재시작). 이미 실행됐을 수 있어 다시 실행하지 않음 |
REJECTED | 거절 |
EXPIRED | 기한 지남 |
UNKNOWN은 감사 로그에서 실행 기록을 찾으면 SUCCEEDED·FAILED로 바뀌고, 못 찾으면 그대로(result.statusCode = 0)이니
대상을 조회해 확인하십시오. API로는 상점 플랫폼에 로그인한 OWNER·ADMIN 토큰으로 api.approvalRequests.list·
approve(approvalId, { note })·reject를 부르고, 결과는 get으로 확인합니다.
MCP 대화에서 승인하기
원격 MCP에 연결했다면 대화 안에서 승인 요청을 처리할 수 있습니다. 결정은 사람이 확인 창에서 수락해야 실행됩니다.
- 에이전트가 승인 대기 요청을 만들면
decide_approval로 이 대화에서 승인할 수 있다고 안내합니다. 대기 목록은list_approvals로 봅니다. - 승인하거나 거절하라고 말하면 에이전트가
decide_approval을 부릅니다. - MCP 클라이언트가 확인 창을 띄웁니다. 작업·요청 경로·사유·변경 내용·기한을 확인하고 확인 칸을 선택한 뒤 수락합니다.
- 수락하면 상점 플랫폼에서 승인한 것과 같이 처리합니다. 승인이면 원 요청을 한 번 실행하고, 결과는
get_approval_request로 봅니다.
- 확인 창에서 거부하거나 창을 닫으면 아무것도 바뀌지 않습니다. 2분 안에 답하지 않아도 같습니다.
- 확인 창(MCP elicitation form)을 지원하지 않는 클라이언트와 로컬 MCP(
@sayren/mcp)는 승인 화면 링크(approvalUrl)만 알려 줍니다. 상점 플랫폼에서 처리하십시오. - 에이전트는 확인 창 없이 결정할 수 없습니다. 도구 인자로 확인을 대신할 수 없고, 액세스 토큰으로 승인 API를 직접 불러도
403 AGENT_OPERATION_DENIED입니다. - 승인 조건은 상점 플랫폼과 같습니다. 연결한 계정이 그 상점의 OWNER·ADMIN이어야 하고, 연결 토큰에 원 요청의 스코프와
store:rw가 있어야 합니다. 소유자만 승인하는 요청은 OWNER만 승인합니다. - 승인 요청의 승인자는 연결한 사람으로 남고, 감사 로그에 상세 기록
APPROVAL_DECIDED(metadata.via=MCP_ELICITATION)가 남습니다. - 확인 창은 MCP 클라이언트가 사람에게 보여 주는 화면입니다. 클라이언트 설정(예: Claude Code의
Elicitation훅)으로 확인 창에 자동으로 답하게 하면 사람 확인이 사라집니다. 이 서버에는 그렇게 설정하지 마십시오.
감사 로그
상점 플랫폼에서 설정 › 보안 › 감사 로그로 이동합니다(소유자·관리자, audit:r).
관리 API의 변경 요청(GET 외)마다 요청 기록(REQUEST)이 남습니다. 환불·직권취소 같은 작업은 변경 내용을 담은 상세 기록(DOMAIN)도
남고, 같은 요청의 기록은 requestId가 같습니다. API는 GET /v1/audit-logs입니다.
| 항목 | 내용 |
|---|---|
| 행위자 | USER(사람: 상점 플랫폼·CLI 로그인, 액세스 토큰)·AGENT·APP(연동 앱)·SYSTEM(자동 처리)과 이름. TOKEN은 옛 상점 API 토큰의 이전 기록에만 있습니다 |
| 동작 | 요청 기록은 오퍼레이션 ID(예: ProductsController_patch), 상세 기록은 작업 코드 |
| 대상 | 리소스 유형과 ID. 새로 만든 리소스는 응답의 ID |
| 결과 | 상태 코드, SUCCEEDED·FAILED·PENDING_APPROVAL·DENIED·DRY_RUN, 에러 코드 |
| 추적 값 | requestId, traceId, 멱등키, approvalId |
| 부가 정보 | 변경 내용, 승인 실행의 승인자(metadata.approvedBy), 앱·에이전트 호출의 위임 사용자(metadata.onBehalfOf) |
연동 앱에 로그인 토큰을 허락한 호출의 행위자는 앱(APP)이고, 승인 뒤 실행된 요청의 행위자는 요청한 에이전트입니다.
쿼리는 actorType(쉼표로 여러 개, 예: AGENT,APP), 정확히 일치하는 actorId·action·targetType·targetId, 기록 단위
source, 결과 outcome, requestId·approvalId, 기간 from(포함)·to(미포함, ISO 8601), 커서 cursor·limit(다음은
nextCursor)입니다. SDK는 api.auditLogs.list({ actorType: "AGENT", outcome: "PENDING_APPROVAL" })입니다.
- 요청 본문은 저장하지 않습니다(승인 요청의 본문은 승인 요청 조회로 봅니다).
- 인증에 실패한 요청(토큰 없음·만료, 스코프 부족)은 남지 않습니다. API 로그에서 봅니다.
- 요청 기록은 몇 초 늦게 보일 수 있고 1년 보관합니다. 환불·취소의 상세 기록은 지우지 않습니다.