콘텐츠로 이동

API 구현

1. 개요

Messages·Chat Completions·Responses·Bedrock 수신과 상태·메트릭·키 관리용 관리 영역의 HTTP 구현입니다. 전체 계약은 API 레퍼런스를 참고하세요.

2. 구성 요소

구성 경로 책임
데이터·관리 mux internal/server/server.go 인증·집행·메트릭 조립
Anthropic internal/server/anthropicapi/ messages·count_tokens·models. 알 수 없는 모델 404·금지 모델 403에 키의 허용 모델 목록 표시
OpenAI internal/server/openaiapi/ chat/completions·models, 같은 허용 목록 오류 안내
Responses internal/server/responsesapi/ POST /v1/responses, 네이티브와 지원 무상태 텍스트·도구 어댑터, 공통 승인·라우팅
Bedrock internal/server/bedrockapi/ /model/{modelId}/invoke·invoke-with-response-stream의 AWS eventstream, count-tokens는 정책·저장소 거부 시 로컬 HTTP 200 추정치와 inputTokens(잘못된 자격 증명은 401 가능). Claude Code의 Bedrock 모드·기본 URL·인증 생략 설정에서 bearer를 공통 KeyAuth로 해석. AWS 형식 message·X-Amzn-ErrorType 오류, 지원 Bedrock 대상
네이티브 Converse internal/server/bedrockapi/, providers/bedrock/native_converse.go /model/{modelId}/converse, /converse-stream. 원본 JSON·AWS 이벤트, 공통 승인·감사, 적격 Runtime 대상만 지원(ADR-052)
정책 적용 비교 internal/server/evaluation.go mayu의 POST /admin/evaluations. 전체 관리자와 별도 가상 키, 비스트리밍 데이터 핸들러 실행, 응답 결과·정수 비용·불확실성 근거(ADR-052)
사용량 internal/server/usageapi/usage.go GET /v1/usage, 본인 키·팀의 유효 예산·할당량, 정수 microUSD, 무제한은 null. team_budget_day·key_budget_day는 calendar-day, 기존 월 필드는 calendar-month 유지. key_id 표시 없음
관리 키 internal/server/adminapi/keys.go 팀 권한과 감사가 적용되는 발급·조회·폐기
팀·사용자 internal/server/adminapi/teams.go GET /admin/teams는 관리 인증, PUT·DELETE /admin/teams/{name}은 전체 관리자. SetTeamLookup으로 다음 요청부터 집행. GET /admin/users는 owner의 읽기 전용 파생 뷰이며 사용자 테이블·사용자 지출 기능과 구분
본인 조회 internal/server/adminapi/whoami.go subject·teams·is_admin·auth_method의 비밀 없는 표현
키 콘솔 internal/server/adminui/ /admin/ui/는 내장·비인증·데이터 없는 셸, 데이터는 인증 API. login_origins가 있으면 브라우저 Code+PKCE SSO
인증 설정 server.go의 AuthConfigView GET /admin/auth/config의 비밀 없는 sso·issuer·client_id. OIDC 없으면 404, login_origins 없으면 sso=false
설정 조회·쓰기 internal/server/configapi/ GET /admin/config, PUT·DELETE /admin/providers/{name}·models/{name}. provider_store 없으면 405, 쓰기는 전체 관리자. base_url은 절대 http(s), aliases는 요청 내 중복·전체 모델 충돌 검사. export는 비밀 없는 Git용 설정
연결 시험 configapi/probe.go POST /admin/providers/test, 참조만 있는 초안의 HealthChecker. 전체 관리자, 메타데이터 주소 차단·선택적 allowed_hosts SSRF 보호. 클라이언트 메모리에만 상태, provider_store 없으면 405. 정제한 ok·latency_ms·detail 반환
모델 목록 configapi/catalog.go GET /admin/providers/catalog?type, 내장 자동완성 참고 목록. 모르는 유형은 빈 값이며 저장을 막지 않음
공급자 저장소 internal/providerstore/ 선택적 DB 기준 토폴로지. 비밀 열 없음, 영속 시드, 이식 가능한 DDL. aliases는 타깃별이 아닌 model_aliases에 모델별 저장
감사 검증 internal/server/auditapi/ GET /admin/audit/verify, 저장 대상별 완전 접두부 체인 검사, 16 MiB 상한
예산 알림 adminapi/alerts.go 전체 관리자 GET /admin/alerts/recent, 인스턴스별 최근 50회 webhook 링
공급자 상태 configapi/health.go 전체 관리자 GET /admin/providers/health, 주기적 HealthStore. health check 미설정 시 nil, 수동 시험과 별개
로그·본문 analyticsapi/logs.go, adminapi/bodies.go 전체 관리자 GET /admin/logs의 id-keyset 페이지·body_ref. GET·DELETE /admin/bodies/{ref}. 열람은 사용자별 5분 중복 제거 body_accessed, 삭제는 body_deleted, body_ref 대신 record_ref 감사. 삭제·정리·복호화 불가는 500 대신 410 tombstone
메트릭 internal/server/metricsapi.go 비인증 Prometheus /metrics
OpenAI 변환 internal/openai/convert.go 정규 요청·응답·청크 상호 변환
CP 사용량 internal/controlplane/usage.go POST /v1alpha1/usage: 검증 오류 400, 과대 413, 저장소 장애 503, 저장 후 ack·FIFO 재시도. GET은 team/user/model·group_by·정수 비용, 메모리 대체 시 degraded. export는 since/until 필수 CSV·JSON 스트리밍, 쓰기별 기한으로 30초 WriteTimeout 대응
CP 차트 internal/controlplane/usage_charts.go 인증된 GET /v1alpha1/usage/charts (ADR-051). since·배타적 until 필수, 양수 ≤366일. 정수 total_micro_usd, UTC days[{day,spent_micro_usd}], 상위 8 models[{key,spent_micro_usd}], other_micro_usd·model_count. 보관된 텔레메트리만 집계하며 사용자·팀 ID나 승인 크레딧은 없음. 잘못된 기간 400, 커서·저장소 실패 503이며 부분 차트 성공으로 바꾸지 않음. 성공 no-store
CP 중개 controlplane/broker.go POST /v1alpha1/credentials, dataplane·provider=bedrock 요청 1 MiB. 임시 키·토큰·만료와 no-store 응답. ARN 있을 때만 등록. 전용 상수 시간 토큰, JWT는 미검증 403. 3600초 STS, 정제한 최대 64자 ID를 세션명·SourceIdentity·태그에 사용. SourceIdentity 거부 시 태그로 1회 재시도. 빈 ID·다른 공급자 400, STS·불완전 결과는 고정 502
CP 정책 controlplane/policies.go GET /v1alpha1/policies: 하트비트·콘솔 OIDC, writable 포함. DSN 없으면 쓰기 405. PUT·DELETE는 전용 POLICY_WRITE_TOKEN 또는 콘솔 OIDC, 하트비트 쓰기는 403. 쓰기 토큰 없으면 모든 정적 bearer 쓰기 거부. actor/op/name/content-sha256 로그
CP 계정 연결 internal/controlplane/policies.go 같은 인증된 정책 목록에 선언의 identity_bindings: [{reported_ref, account_ref, kind}]를 선택적으로 추가(ADR-050). 바인딩이 없으면 생략하며 issuer·subject·IdP 그룹 원문은 반환하지 않음. Cache-Control: no-store. 정확한 재무 선택자를 위한 승인된 선언 메타데이터이며 사용자 레지스트리·신원 증명·회계 계정 변경이 아님
CP 내보내기/가져오기 controlplane/export.go, import.go GET /v1alpha1/config/export(비밀 없는 다중 GovernancePolicy YAML, 첨부 파일로 다운로드), POST /v1alpha1/config/import(Replace 방식 복원). dry_run=true로 문서별 diff와 result_generation을 미리 확인하고, 적용 시 expected_generation이 필수(불일치 시 409). 빈 번들은 allow_empty=true가 필요합니다. 번들 전체를 검증한 뒤 트랜잭션 한 번으로 반영하고, authnWrite 권한이 필요합니다(heartbeat 토큰은 403). store가 없으면 405입니다. YAML 전송 한도는 64 MiB이며, 두 sync 모드와 요청에 따른 authority 항목을 포함한 최악의 sync 응답 크기를 구성 요소별로 측정하고 한도 초과 시 즉시 중단합니다. 데이터 플레인 디코딩 한도인 8 MiB 이내여야 합니다(초과 시 400). 요청에 따른 크기 여유는 실제 데이터 플레인 클라이언트가 로컬 nonce로 생성하는 ASCII-safe/hex authority 요청 식별자를 전제로 합니다. JSON 이스케이프가 많이 필요한 식별자는 이를 보낸 데이터 플레인 자신의 디코딩 한도만 초과할 수 있습니다. pgstore 검증에서 authority 요청 식별자 문자 집합을 제한하는 작업은 후속 과제로 기록되어 있습니다. 정책만 다루며 원장·사용량·identity 설정은 대상이 아닙니다
CP 콘솔 internal/controlplane/ui/ /ui/ 운영 대시보드: 개요·정밀 사용량과 내보내기·최근 데이터 플레인 generation과 거절·검토 후 정책 CRUD(기존 서버 쓰기 권한). 데이터 없는 공개 셸, 분리된 읽기·쓰기 자격 증명은 JS 메모리만, self CSP와 API 불가·degraded 표시. 로그인 origin 환경 변수로 Code+PKCE
CP 인증 설정 controlplane/authconfig.go GET /ui/auth/config의 비밀 없는 sso·issuer·client_id. issuer·client_id 있을 때만 등록, 아니면 404
CLI 로그인 internal/server/authapi/ 데이터 영역의 선택적 config·발급·자기 폐기. 콘솔과 별도 OIDC client_id·검증기, 만료·owner는 서버 결정, 주체별 발급 제한. 폐기는 일반 KeyAuth

3. 주요 결정

Messages·Chat Completions·Bedrock Invoke/Converse의 컨텍스트 사전 검사 실패는 각 수신 프로토콜의 오류 형식 안에 prompt is too long: N tokens > M maximum 문구를 넣어 HTTP 400으로 반환합니다. Claude Code는 이 문구를 일반 모델 폴백 대신 압축 복구 대상으로 인식하고 초과량을 추출합니다. N은 업스트림 토크나이저의 실측값이 아닌 게이트웨이의 내용 기반 추정치(internal/tokenest)입니다. 디코딩한 JSON 텍스트를 문자 종류별 구간으로 나눠 보정된 비율을 적용하며(영문 산문·코드·숫자 위주 텍스트는 o200k_base 대비 3% 이내, 자연스러운 한국어는 o200k_base보다 약 9% 많게, 무작위에 가까운 음절과 hex는 적게 추정), 알려진 콘텐츠 위치의 인라인 이미지는 하나당 1,600토큰, 그 밖의 base64(문서·서명·불투명 상태)는 4바이트당 1토큰으로 셉니다. Claude 토크나이저는 자연스러운 한국어를 이 추정치의 약 1.7배로 셌습니다(2026-11-02 측정). 과소 추정된 요청은 업스트림으로 가며, 업스트림의 초과 오류는 복구 가능한 prompt-too-long 오류로 반환됩니다. 바이트/4는 영문 산문을 약 60% 과대 추정해 창의 약 62%를 넘는 산문을 거절했고(약 97k토큰으로 측정된 요청을 128k 창에서 거절) 숫자·hex· 무작위에 가까운 음절은 과소 추정했습니다. 같은 추정치를 로컬 count_tokens 응답에 사용하며(Bedrock count는 디코딩한 input.invokeModel.body를 추정), 동시 처리 본문 예산에 들어가지 못한 count 요청만 아래처럼 4바이트당 1토큰으로 응답합니다. 거버넌스 PreCheck 승인 추정치(TPM 예약·토큰 quota 사전 검사)와 명시적으로 선택한 Responses 모델의 라우팅 용량에도 같은 추정치를 사용합니다. 금전 예약, 개인정보 검사, 자동·엄격 타깃의 보수적인 용량 검사는 바꾸지 않습니다. 경계에 가까운 요청은 업스트림으로 가며, 인식된 업스트림 초과 오류는 같은 복구 가능 오류로 바뀝니다. 클라이언트 표시 윈도우와 게이트웨이 선언은 독립적입니다. 남은 입력이 한도에 들어가지 않으면 압축은 계속 실패할 수 있으며, 모델 한도를 더 크게 가정하면 안 됩니다.

키 인증이 성공하면 count_tokens는 별칭을 정규화하고 라우팅·준비 상태 거부에도 200을 반환합니다. 없거나 잘못된 키는 핸들러 실행 전 401을 받습니다. 같은 프로토콜이면 원문 전달, 다르면 정규 변환을 적용합니다. 별칭은 RBAC·라우팅·감사·메트릭 전에 정규 이름으로 바꿉니다. Anthropic 원문 경로는 최상위 model만 바꾸고 중첩 cache_control과 HTML 이스케이프 상태를 보존합니다. 오류는 수신 형식이며 허용된 가용 모델 목록을 표시합니다. server.max_request_bytes를 넘는 본문은 Content-Length 선언 여부와 관계없이 수신 형식의 HTTP 413을 받습니다. Anthropic은 request_too_large, Chat Completions·Responses는 invalid_request_error, Bedrock은 ValidationException입니다(이전에는 {"error":"request body too large"}였고 길이를 선언하지 않으면 400이었습니다). count 엔드포인트는 계속 로컬 추정치로 200을 반환합니다. server.max_inflight_request_bytes를 설정하면 동시 처리 본문 예산에 들어가지 못한 생성 요청은 같은 형식의 503과 Retry-After: 2를 받습니다(Anthropic overloaded_error, Chat Completions·Responses는 코드 gateway_overloaded의 server_error, Bedrock ServiceUnavailableException). 들어가지 못한 count 요청은 본문을 버퍼링하지 않고 로컬에서 답합니다(HTTP 200, 4바이트당 1토큰).

모델 폴백: 한 단계 model_fallbacks와 기본 동일 계열 휴리스틱을 허용 검사 전에 적용합니다. 미설정 이름만 허용된 키는 다른 모델을 대신 받지 않고 거부됩니다. 이미 설정된 모델도 업스트림 404이면 기존 폴백 루프에서 교차 모델을 시도하되, 나중 추가한 타깃을 FilterModelAllowed로 다시 검사합니다. 응답 x-inferplane-model-fallback은 공급자 폴백 헤더와 독립적입니다. 미설정 이름 대체(업스트림 404 교차 모델 재시도는 헤더만 설정)는 Responses를 포함한 모든 생성 수신 경로가 이를 프로세스당 서로 다른 쌍마다 한 번 router: model fallback source=… requested=… served=…로 기록하고, inferplane_model_fallback_total{source,model}을 세며, 감사에 routing.reason: model_fallback과 model_fallback_from·model_fallback_source를 남깁니다. 응답하는 모델은 바뀌지 않습니다.

Messages는 출력 전 전송 오류·5xx/429·교차 모델 404만 재시도합니다. 타입이 있는 검증 오류와 다른 4xx는 종료하며 일반적인 Bedrock ValidationException은 모델 부재 근거가 아닙니다. 각 재시도 타깃은 전송 전에 자체 컨텍스트 선언을 다시 검사하며 최초 사전 검사 계약은 유지합니다. 엄격한 숫자 형식의 프롬프트 초과 SDK 검증 오류만 안전한 context_length_exceeded 코드와 인식 가능한 초과 문구를 보존하고 나머지 Bedrock 원문은 숨깁니다. Bedrock Converse(Claude 외 모델, SDK 경로와 네이티브 /converse 수신)는 서빙 엔진이 전달한 Input length (N) exceeds model's maximum context length (M). 문구(2026-10-27 openai.gpt-oss-120b-1:0에서 관측, Mantle 스트리밍 접두어 유무와 무관)와 코드가 정확히 context_length_exceeded인 OpenAI 오류(숫자 없음)도 인식합니다. 그러면 Messages·Chat Completions·Bedrock Invoke/Converse는 bedrock upstream error (ValidationException) 대신 각자의 오류 형식으로 복구 가능한 prompt is too long: N tokens > M maximum 오류(숫자는 업스트림이 알려 준 경우에만)를 반환하고, Responses는 고정 문구와 함께 error.code: context_length_exceeded(type invalid_request_error)를 반환합니다.

Messages에서 OpenAI 호환 업스트림의 HTTP 400 오류는 Anthropic invalid_request_error 형식으로 변환합니다. 중첩된 error.code=context_length_exceeded는 고정 문구 prompt is too long으로 바꾸며, openaicompat은 정확히 일치하는 세 가지 문구, 즉 Anthropic 문구(prompt is too long: N tokens > M maximum), Converse 문구(Input length (N) exceeds model's maximum context length (M).), v0.8.5 소스로 확인한 기존 vLLM Chat Completions 초과 문구에 대해 검증된 양수 토큰 수와 한도를 보존합니다. 이 합계에는 요청한 출력 토큰이 포함될 수 있습니다. 알 수 없거나 잘못되었거나 너무 큰 오류 형식은 공급자 원문을 노출하거나 컨텍스트 오류로 추측하지 않고 upstream request rejected로 반환합니다. 파싱 한도는 64 KiB입니다. 비스트리밍 응답과 스트리밍 시작 오류에 적용하며 OpenAI 수신 오류 바이트는 그대로 유지합니다.

기존 선택적 예산 티어: routing의 budgetTiers는 affinity·context와 별개이고 budgetRef는 같은 문서의 숫자 한도를 가진 예산입니다. 기존 ADR-034는 CP 리스 원장으로 전역 사용률을 판단하고 윈도 안에서 단조롭게 티어를 유지합니다. 이미 라우팅된 모델에 SubstituteTier를 적용하며 원래 모델과 목적지 RBAC·경로가 모두 유효할 때만 치환합니다. 대안을 못 쓰면 원래 모델을 유지합니다. x-inferplane-substituted-model과 감사 model_substituted_from을 남깁니다. 정책 적용 단계에서도 SetRoutedAndPriced로 목적지 경로·가격을 요구하며 없으면 문서 전체를 거부합니다. 엄격 티어의 별도 거부 의미는 ADR-044를 따릅니다.

예산 윈도: period는 CalendarDay·CalendarMonth이고 생략하면 기존 의미인 CalendarMonth입니다. unlimited=true와 결합할 수 없습니다. 일·월 한도는 별도 규칙이며 각각 hardCap·failurePolicy·lease·adminContact를 가집니다.

4. 코드 위치

  • internal/server/anthropicapi/messages.go: Messages, 스트리밍 전달, 제한된 라벨.
  • internal/server/openaiapi/chat.go: Chat Completions.
  • internal/server/auth.go: 가상 키 해석.

5. 관련 문서

router·tier·governance·alert·bodystore·providers와 ADR-016/017/018/021/028/029/040/041/042를 참고하세요. 운영 절차와 CLI 로그인이 실무 흐름을 설명합니다.

observability 규칙 스키마

GovernancePolicy.spec.rules[].observability는 해당 주체의 정책 기반 OTLP 내보내기를 고릅니다. traces(exporter, 선택 samplePercent 1–100, 기본 100), audit(exporter), content(exporter) 중 하나 이상을 지정하며 FailOpen이어야 합니다. exporter 이름은 데이터 영역의 로컬 observability.exporters 상한을 가리킵니다. 데이터 영역은 선언되지 않은 exporter나 그 exporter가 허용하지 않는 신호를 지정한 규칙을 거부하고, 상한을 선언하지 않았으면 이런 규칙을 모두 거부합니다. 정책 기반 OTLP 내보내기를 참고하세요.

관측성 콘솔 API · mayu 전용

GET /admin/observability, PUT /admin/observability/limits, PUT /admin/observability/rules(internal/server/observabilityapi)는 server.WithFullAdminRoutes로 붙으므로 읽기를 포함한 모든 경로에 전체 관리자 권한이 필요합니다. 두 PUT은 dry_run과 expected_revision을 받습니다. dry run은 아무것도 바꾸지 않고, 적용은 미리보기 때 본 리비전을 지정해야 하며(expected_revision이 없으면 400 invalid_request, 오래된 값이면 409 revision_conflict), 저장·설치한 뒤 admin_observability_changed를 기록합니다. 관측성 경로 자체의 오류 본문은 {"error": message, "code": code}입니다. 401과 403은 공용 관리 미들웨어가 평소 본문으로 답합니다(401은 authentication_error 형식, 403은 {"error":"admin only"}). /admin/capabilities에 observability_export가 추가됩니다. 요청·응답 형식은 API 레퍼런스에 있습니다.

정책 라우팅 스키마와 증거 · ADR-043

sensitiveData는 onDetected·onUninspectable(InternalOnly·Block), 내부 전용일 때 명시적 internalModels와 FailClosed가 필요합니다. 와일드카드는 금지입니다. context는 FailOpen, fromModels, simpleModel, complexModel, 양수 maxSimpleInputTokens, 선택적 비공백 complexKeywords, 기본 Shadow·명시적 Enforce이며 다른 routing 종류와 함께 둘 수 없습니다. 일치하는 개인정보 규칙은 교집합·Block 우선입니다. 전체 계약을 참고하세요.

공급자 DTO의 data_boundary는 internal·external·unknown, 생략은 unknown입니다. 모델 DTO는 음이 아닌 컨텍스트(0=불명)와 tools·vision·reasoning·structured_output을 받습니다. 조회·쓰기·내보내기가 보존하고 모르는 enum은 거부합니다.

모든 생성은 공통 안전 체인과 수신 형식 403을 사용합니다. 계산은 개인정보·준비 상태·오래된 정책·본문 크기 실패에서 업스트림 호출 없이 로컬 200입니다. 라우팅 사유·선택 모델과 앞선 예산 치환 헤더를 구분하고 감사 requested·selected·proposed·actual의 의미도 구분합니다. Responses는 ADR-044에서 추가했습니다. 새 규칙 전 바이너리·CRD 업그레이드, 최초 CP 보호에는 require_sync가 필요합니다.

ADR-044는 Mask, normalModel·maxNormalInputTokens·stability, enforceTargets를 추가합니다. 엄격 티어는 100%를 지원하며 참조 소프트 예산은 두 번째 승인 리스가 아닌 계량 임계값입니다. 세션 힌트가 권한을 부여하지 않으며 매 고정·시도를 재검사합니다. 필드·Codex 설정을 참고하세요.

Responses는 Bedrock Converse·InvokeModel·Mantle, Anthropic·Chat 어댑터의 이식 가능한 텍스트·도구 프로파일을 받습니다. none effort는 백엔드 추론 제어 요구가 아닙니다. strict 도구와 불투명 추론 항목은 해당 네이티브 요구를 유지합니다. 저장 상태 참조(previous_response_id, conversation, item_reference 입력 항목)는 모든 대상에서 라우팅 전에 400 stored_reference_unsupported로 거부합니다. upstream은 gateway의 공유 자격 증명으로 이를 해석하고 gateway에는 사용자별 소유권 근거가 없기 때문입니다. client는 전체 입력을 다시 보냅니다.

모델 조회 메타데이터 의미
capabilities 설정된 기능, 도구가 검증된 경로의 tools 포함
responses_mode 전체 해석된 체인을 보수적으로 판단한 native·bridge·unsupported
codex_model 공개 이름에서 유도한 네이티브 클라이언트 카탈로그 바인딩, 비공개 배포 ID 아님
context_window·max_model_len 선언된 상한, 미선언 시 생략

인증된 키 권한으로 조회 결과를 제한하고 추론 시에도 기능·개인정보·예산·호환성을 재검사합니다.

로컬 금전 사용량 · ADR-054

GET /v1/usage의 로컬 예산 객체는 미확정 시도별 의무인 reserved_usd_micros와 정산을 동결했음을 나타내는 accounting_uncertain을 선택적으로 추가합니다. spent_usd_micros는 관측 비용을 유지합니다. 잔액은 예약을 반영하며 동결 시 0입니다. 마지막 사용량을 받지 못하면 실제 비용을 0으로 꾸미지 않고 의무를 유지합니다. 이 카운터는 프로세스 재시작 시 초기화됩니다.

영속 예산 하트비트 · ADR-045

DURABLE_BUDGETS=true이면 POST /v1alpha1/sync가 authority.protocol=escrow-v1을 협상합니다. 비-JWT 머신 bearer만 허용하고 콘솔 OIDC는 권한을 발급할 수 없습니다. 전체 정책, DB 소유 윈도, 권한, 보고·계량 ack를 반환합니다. 기존 요청은 409이고 영속 클라이언트는 구형·무효 응답을 거부합니다. ActiveTier의 팀·사용자 범위를 유지하며 readyz는 실제 DB를 검사합니다. 전송 계약과 설정을 참고하세요.

공유 게이트웨이 · ADR-046

key_store.type과 governance_store.type을 postgres로 설정하면 키 공유·원자적 자원 승인을 활성화합니다. 하트비트의 권한 네임스페이스를 검증하고 로컬 권한은 요청하지 않습니다. 사용자 요청률·일/월 tokenQuota는 이 프로파일이 필요합니다. 사용량은 enforcement_mode=shared와 주체별 shared_limits를 반환합니다. 키 CLI는 --config를 받고 가져오기는 원래 신원·폐기 이력을 보존합니다. 공유 거버넌스를 참고하세요.

Responses 업스트림 오류 코드

네이티브 Responses HTTP 실패는 업스트림 상태와 정확히 일치하는 허용 코드 context_length_exceeded, invalid_encrypted_content만 보존합니다. error.code 및 기존 감사 outcome.error에 기록하고 원문 메시지·param·알 수 없는 코드는 숨깁니다. 오류 해석은 64 KiB로 제한하며 복구 코드만을 이유로 재시도하지 않습니다. SSE 전송 시작 전과 비스트리밍 HTTP 실패에 적용합니다. 네이티브 SSE의 종료 실패도 원문을 제거한 response.failed 이벤트로 두 코드를 보존하며 첫 이벤트가 실패인 경우에도 동일합니다. 다른 중단은 기존의 제한된 게이트웨이 오류 이벤트를 유지합니다. 유효한 네이티브 status="incomplete" 출력과 사용량은 재시도 없이 전달합니다. max_output_tokens는 정상적인 미완료 종료 이유입니다. Responses 승인 HTTP 429의 error.type은 청구 쿼터 타입 insufficient_quota 대신 rate_limit_exceeded입니다.

코드가 포함된 HTTP 실패에서 두 허용 복구 코드의 error.type은 invalid_request_error입니다. ADR-053은 SSE 헤더 전송 전 발생한 게이트웨이 스트림 실패에 upstream_timeout, stream_incomplete, response_incompatible, usage_missing 같은 제한된 코드를 추가합니다. 기존 게이트웨이 오류의 error.type="upstream_error"는 유지하며, 이전에는 같은 실패 경로에서 error.code를 생략했습니다. 클라이언트는 게이트웨이 실패를 잘못된 요청으로 취급하지 않으면서 새 코드로 더 구체적인 원인을 표시할 수 있습니다. 실패 코드만을 이유로 요청을 다시 실행하지 않습니다.

Responses의 컨텍스트 사전 검사 거부도 HTTP 400과 error.code="context_length_exceeded"를 반환합니다. 기존 검사 기준과 인증 순서는 유지합니다. 무상태 브리지에서 모든 호환 대상이 선언된 컨텍스트 창 때문에만 요청을 거부하면(라우터 사유 context_window_exceeded) 같은 400을 반환합니다. 이전에는 403 no policy-permitted compatible Responses target을 반환해 Codex가 같은 요청을 다시 보냈습니다. 그 밖의 경로 없음 사유는 403을 유지합니다.

분석 근거 (ADR-053)

internal/server/analyticsapi/는 full-admin summary·timeseries·logs·health와 기존 Mode-B rebuild 경로를 제공합니다. internal/analytics/evidence.go는 SQLite와 PostgreSQL 투영이 공유하는 분류기입니다. Summary Totals, 팀/모델 행, 일별 지점에 operations를 추가하고, 이벤트에는 has_cost, outcome_state, cost_state, partial, 제한된 error_code, projection_version을 추가합니다. 경로·메서드·권한 등급은 바뀌지 않습니다.

operations.completed는 Cost=nil도 포함한 관측 request_completed 기록 수입니다. 기존 requests·토큰·금액은 Cost가 있는 기록만 포함합니다. 결과 네 가지와 비용 상태 다섯 가지는 각 분류 안에서 배타적이며 전송 성공과 비용 근거는 독립적입니다. API의 정수 산정액은 청구서·작업 품질 지표·승인 크레딧이 아닙니다.

필드 계약과 추가 예제, 운영 분모, 원본 복구를 확인하세요. Mode-B 마이그레이션·backfill은 트랜잭션으로 수행하지만 기존 checkpoint 이후 건너뛴 원본을 재생하지는 않습니다. 과거 투영 건수 0도 완전한 이력을 증명하지 않습니다. 어느 프로파일에서도 분석 변경은 금전 권한을 변경하지 않습니다.

감사 콘솔 (ADR-057)

분석 인덱스의 콘솔 투영을 읽는 읽기 전용 경로 다섯 개입니다. 모두 full-admin 권한이 필요합니다. 토큰이 없으면 401, 인증된 비관리자는 403을 받습니다. 분석 인덱스가 켜져 있고 그 저장소가 콘솔 조회를 구현할 때만 마운트되며(SQLite Mode A와 PostgreSQL Mode B 모두 구현), 그렇지 않으면 경로가 없고 GET /admin/capabilities는 audit_console: false를 보고합니다. otel.trace_link_template을 설정하면 같은 엔드포인트가 trace_link_template도 반환합니다. GET만 허용하며(405 method_not_allowed) 응답은 no-store입니다.

GET /admin/audit/events?kind=policy_denied,upstream_error&limit=100
GET /admin/audit/events/{id}
GET /admin/audit/sessions?since=2026-10-29T00:00:00Z
GET /admin/audit/sessions/{ref}?after=<event-id>
GET /admin/audit/summary
경로 반환
/admin/audit/events {"events":[...],"next":"<id>"}, 최신순. 다음 페이지는 next를 before로 전달합니다. 거버넌스 거부 행은 기록의 governance 이름인 gov_scope, gov_limit, gov_source도 가지며, 다른 행과 그 필드 이전에 기록된 거부에는 없습니다.
/admin/audit/events/{id} {"event":{...},"record":{...},"hash":"sha256:...","waterfall":{...}}. record는 저장된 감사 행입니다. hash는 그 행의 SHA-256으로 기록의 체인 위치를 보여 줄 뿐 체인 검증이 아닙니다(검증은 /admin/audit/verify). waterfall은 latency와 attempts에서 만든 타임라인입니다(행 종류 gateway, attempt, settle, denied. 기록에 지연 시간이 없으면 행이 없습니다).
/admin/audit/sessions {"sessions":[...],"next":"<id>"}. session_ref별 한 행(없는 기록은 세션이 아님)과 요청·거부·PII·자동 모드 차단·서브에이전트 수, 비용, 처음·마지막 시각.
/admin/audit/sessions/{ref} {"events":[...],"next":"<id>"}, 오래된 순. s1: 세션 다이제스트 하나의 이벤트이며 after와 limit만 적용합니다.
/admin/audit/summary 종류별 건수, 상위 10개 카테고리, PII 건수(masked, internal, blocked, shadow), 상위 50개 거부(code, policy, scope, limit, source, team, key_id, count, last_ts. scope·limit·source는 기록이 거버넌스 한도를 알릴 때만 값이 있음), 자동 모드 건수(block, allow, unknown, signatures), 12개 균등 시간 구간, 인덱스가 가진 가장 이른 시각 coverage_start_ms.

목록 경로와 요약이 공유하는 쿼리 파라미터입니다. 모든 값을 검증하며 잘못된 값은 마지막 열의 닫힌 400 코드를 반환합니다.

파라미터 규칙 코드
since, until RFC 3339 시각. 기본 구간은 최근 24시간이며 until만 주면 since는 24시간 앞입니다. since는 until보다 앞서야 하고 구간은 366일을 넘을 수 없습니다. 원시 URL에서 + UTC 오프셋은 %2B로 보내야 하며 아니면 공백으로 해석됩니다. invalid_range
limit 1~200 정수(/sessions/{ref}는 500). 기본 100. invalid_limit
before, after 26자 이벤트 ID(ULID). invalid_cursor
kind ok, policy_denied, privacy_blocked, privacy_masked, privacy_internal, upstream_error, auto_mode_blocked의 쉼표 구분 부분집합. invalid_kind
session s1: 다음 소문자 16진수 32자리. invalid_filter
trace_id 소문자 16진수 32자리. invalid_filter
code, class, category [a-z0-9_] 1~64자. invalid_filter
team, key_id, model, provider 최대 256바이트. model은 요청 모델 또는 실제 모델과 일치합니다. invalid_filter
pii=true PII 근거가 있는 행만. 없음
auto=true 자동 모드 판정이 block 또는 unknown인 행만. allow는 요약에서 세지만 목록에는 나오지 않습니다. 없음

잘못된 {id}·{ref}는 400 invalid_id, 없는 이벤트는 404 not_found입니다. 저장소 실패는 상세 없이 500 query_failed입니다. 오류 본문은 {"error":"<code>"}이며 입력을 되돌려 주지 않습니다.