DKDevKey Docs

Architectural security interface

시크릿을 저장하고,
필요한 만큼만 전달하세요.

DevKey는 프로젝트별 시크릿을 암호화해 보관하고, 만료·권한·감사 로그가 있는 Access Key로 애플리케이션에 전달합니다. 이 문서는 관리자 콘솔과 API를 처음부터 운영까지 한 흐름으로 설명합니다.

Updated · 2026-09-08

최근 변경 사항

Secret 버전·복구

이전 값·설명으로 새 버전을 만들고, 휴지통에서 비활성 상태로 복구합니다. 배정된 유효 키의 연속 권한 오류가 선택한 1~100회에 도달하면 Secret을 자동 비활성화할 수 있습니다.

프로젝트 환경 분리

한 Project 안에서 default·development·staging·production의 Secret과 Access Key를 분리합니다. API 환경은 검증된 Access Key로 결정됩니다.

디스코드 알림·정기 교체

Workspace별 웹훅으로 만료·교체 예정 알림을 받고, 30·60·90일 교체 주기와 1·24·72시간의 이전 키 유예기간을 사용합니다. 새 키의 서비스 적용은 직접 진행합니다.

01 · Start here

처음 5분: 프로젝트에서 API 호출까지

Workspace와 프로젝트를 만든 뒤, 시크릿 하나에 읽기 권한이 있는 Access Key를 발급하고 첫 호출을 확인합니다.

  1. 관리자 콘솔에 로그인dev-key.com에서 이메일 인증을 마치고 로그인합니다.
  2. Project 만들기Projects에서 이름과 URL-safe slug(예: erp)를 정합니다. API 요청에는 이 slug를 사용합니다.
  3. Secret 저장Secrets → Create에서 Project·환경을 선택하고 DB_PASSWORD 같은 키와 값을 입력합니다. 값은 저장 전에 암호화되어 관리자 목록에도 평문으로 노출되지 않습니다.
  4. Access Key 발급Access Keys → Create에서 같은 Project·환경을 선택하고, 필요한 Secret에 Read/List 권한만 부여합니다. 만료일은 기한 없음, 달력 또는 빠른 기간 버튼으로 지정할 수 있습니다.
  5. 첫 번째 읽기 요청생성 직후 한 번만 표시되는 원문 키를 안전한 런타임 시크릿에 넣고 아래 API 예제로 확인합니다.
원문 키는 다시 볼 수 없습니다. Access Key 생성 성공 화면에서만 sk_dev_... 또는 sk_live_... 원문이 표시됩니다. 복사하지 못했다면 기존 키를 비활성화하고 새 키를 발급하세요.

02 · Mental model

DevKey의 구성 모델

권한 범위가 섞이지 않도록 리소스를 계층으로 나눕니다. 하나의 Access Key는 정확히 하나의 Project·환경에 속하고, 같은 Project·환경의 Secret Item들에만 권한을 받을 수 있습니다.

Workspace

팀의 최상위 보안 경계입니다. Project·Secret·Access Key·감사 로그는 Workspace 멤버십과 역할을 검사합니다. Owner, Admin, Developer, Viewer 역할이 있습니다.

Project · Environment

Project는 서비스 단위이며, 내부에 default·development·staging·production 환경을 둡니다. API URL에는 Project의 slug, 환경 선택에는 Access Key를 사용합니다.

Secret Item

키와 암호화된 값의 쌍입니다. 값 자체는 API 권한을 통과한 요청에서만 복호화됩니다.

Access Key

애플리케이션이 API를 호출하는 자격 증명입니다. 활성 상태·만료일·Project·환경 범위를 함께 검사합니다.

Permission

Access Key와 Secret Item 사이의 연결입니다. canRead, canList 등을 Secret 단위로 지정합니다.

Audit Log

Secret API의 성공·실패와 관리자의 변경 작업을 추적합니다. 작업자, 대상, IP, User-Agent, 오류 코드를 확인할 수 있습니다.

03 · Admin console

관리자 콘솔 사용법

웹 콘솔은 Better Auth 세션과 Workspace 멤버십으로 보호됩니다. 브라우저에서 값을 직접 다루는 시간을 줄이고, 작업 단위를 Project로 분리하세요.

Workspace Members에서 이미 DevKey에 가입한 사용자를 이메일로 추가할 수 있습니다. Owner는 전체 관리, Admin은 멤버와 리소스 관리, Developer는 프로젝트·시크릿·Access Key 변경, Viewer는 읽기 전용 권한을 가집니다. Owner 이전과 마지막 Owner 제거는 지원하지 않습니다.

Workspace와 Project 선택창은 이름을 입력해 검색할 수 있으며, Project는 slug로도 찾을 수 있습니다. 생성 화면에는 Developer 이상의 쓰기 권한이 있는 항목만 표시되고, 목록 필터에서는 읽을 수 있는 전체 항목을 선택하거나 필터를 해제할 수 있습니다. Secrets, Access Keys, Audit Logs 목록의 Project 열에는 조회 가능한 프로젝트의 현재 이름과 ID가 함께 표시됩니다. 삭제된 프로젝트의 감사 기록에는 ID만 남습니다. 멤버 화면도 현재 역할에 따라 허용된 역할과 작업만 표시합니다.

각 목록 하단의 Rows per page에서 표시 행 수를 바꾸면 실제 표와 페이지 범위가 함께 갱신됩니다. 정렬 방향과 현재 페이지도 선택한 값에 맞춰 유지되므로, 감사 로그처럼 많은 기록도 필요한 행 수만 확인할 수 있습니다.

삭제·폐기·재발급처럼 되돌리기 어려운 작업은 브라우저 기본 팝업 대신 DevKey 확인 창에서 다시 확인합니다. Passkey 별칭 입력과 오류 안내도 같은 스타일의 창으로 표시되며, 취소를 누르면 작업이 실행되지 않습니다.

운영 관리자 로그인은 세션과 TOTP MFA를 모두 요구합니다. 처음 이메일·비밀번호 또는 Passkey 로그인 후 인증 앱에서 스캔할 QR 코드와 복구 코드가 표시되며, QR 코드를 사용할 수 없는 경우 TOTP URI를 직접 등록할 수 있습니다. 복구 코드는 비밀번호 관리자에 저장하고 6자리 코드 등록을 완료해야 콘솔에 들어갈 수 있습니다. 로그인 후에는 Settings의 관리자 TOTP MFA 카드에서도 설정할 수 있습니다. API 호출에는 관리자 세션 쿠키가 아니라 Access Key를 사용합니다.

관리자 API는 로그인 후 24시간 이내의 최근 세션만 허용합니다. 새로고침 직후에는 세션 확인용 로딩 화면이 먼저 표시되고, 확인이 끝난 뒤에만 콘솔 또는 로그인 화면이 열립니다. 세션이 서버에서 사라지거나 최근 인증 기한을 넘기면 콘솔은 Secrets를 포함한 캐시 데이터를 지우고 로그인 화면으로 전환합니다. Active Sessions 목록에도 현재 세션이 확인되지 않으면 빈 목록을 로그인 상태로 표시하지 않고 다시 인증을 요구합니다.

Secret 만들기·수정·삭제

Secrets → Create에서 Project·환경, key, value를 입력합니다. value 입력란은 여러 줄 텍스트를 지원하므로 토큰·인증서·연결 문자열도 읽기 편하게 붙여 넣을 수 있습니다. key는 애플리케이션 환경 변수 이름처럼 대문자와 밑줄 조합을 권장합니다. 설명은 운영자가 목적과 소유 팀을 확인하는 데 사용합니다.

현재 관리자 Secret 생성·수정 API에는 value의 별도 애플리케이션 최대 길이를 설정하지 않았습니다. 다만 HTTP 요청과 Cloudflare 런타임 제한은 적용되므로, 매우 큰 파일은 Secret 대신 전용 파일 저장소를 사용하세요.

목록에서는 값 대신 메타데이터만 확인할 수 있습니다. 수정 화면에서 값을 비워 둔 채 저장하면 기존 값이 유지되고, 새 값을 넣으면 암호화된 값이 교체됩니다. 삭제 전에는 해당 Secret을 사용하는 Access Key 권한도 함께 점검하세요.

Secret 버전·삭제 복구·자동 비활성화

버전 이력: 생성·JSON 가져오기는 버전 1부터 시작합니다. 값 또는 설명 변경은 새 버전을 남기며 API PUT도 포함됩니다. Show의 버전 이력에서 저장 시각·설명을 확인하고 이전 버전의 복원을 누르면 그 값과 설명으로 새 버전이 추가됩니다. 평문·암호문은 관리자 이력 응답에 노출되지 않습니다. Active·실패 설정만 변경하면 새 버전은 생기지 않으며, 버전 복원은 Active나 실패 설정을 바꾸지 않습니다. 이력은 자동 만료되지 않으므로 암호화 키와 저장 용량을 관리하세요.

휴지통: 관리자·Secret API의 삭제는 즉시 비활성화하고 휴지통으로 이동합니다. Secrets의 보관 상태 → 휴지통 → 복구를 사용하세요. 값·버전·기존 권한은 보존되며 동일 Project/환경/key는 계속 예약됩니다. 복구 후에는 비활성 상태이고 실패 횟수는 0입니다. 권한을 확인한 뒤 Edit에서 Active를 직접 켜세요. 휴지통 항목은 일반 목록·Secret API에서 제외되며 수정·버전 복원·새 권한 배정은 불가합니다. Project·Workspace 삭제는 Secret과 모든 버전을 영구 삭제하므로 여기서 복구할 수 없습니다. 기능 추가 전에 영구 삭제된 데이터도 복구 대상이 아닙니다.

실패 횟수 선택: Create/Edit의 자동 비활성화: 연속 실패 횟수에 1~100 정수를 입력합니다. 비우면 사용하지 않습니다(기본값). 해당 Secret에 배정된, 서명 해시 검증을 통과한 활성·미만료 Access Key의 단건 Read/Write/Delete 권한 오류만 연속 집계합니다. 카운터는 Secret별로 여러 배정 키가 공유합니다. 정상 접근하면 초기화하고, JSON·dotenv 목록 성공은 실제 포함된 Secret만 초기화합니다. 목록에서 권한 때문에 빠진 항목은 실패로 세지 않습니다.

잘못된·누락된·만료된 키, 다른 Project, 미배정 권한, 없는 Secret, 429, 서버·복호화 오류, 외부 서비스 인증 실패는 집계하지 않습니다. 기준 횟수에 도달하면 Active가 꺼지고 SECRET_AUTO_DISABLED 감사 로그가 상태 변경과 한 트랜잭션으로 저장됩니다. 이후 단건 API는 404, 목록은 해당 항목을 제외합니다. 이미 진행 중인 요청은 완료될 수 있습니다. 횟수 설정을 변경하면 카운터만 초기화하고, 비활성 → 활성으로 직접 전환하면 카운터와 자동 비활성화 사유를 초기화합니다. 배정된 유효 키가 권한 없는 동작을 반복해도 비활성화될 수 있으므로 이 정책의 가용성 영향을 고려하세요. IP 기반 5분 내 5회 실패 이메일 알림과는 별도 기능입니다.

관리자 세션·최근 인증·Workspace 범위를 검사합니다. Viewer 이상은 이력 조회, Developer 이상은 삭제·복구·버전 복원이 가능합니다. Bearer Access Key로는 이 관리자 경로를 사용할 수 없습니다. Access Key 편집에서는 기존 휴지통 권한을 변경 없이 유지하거나 제거할 수 있고, 새 배정·확장은 거절됩니다. 권한 표의 휴지통 행은 읽기 전용으로 표시됩니다. 전체 권한 교체는 기존과 같이 최소 1개 배정이 필요합니다.

관리자 API동작
GET /admin/api/secrets?deleted=true휴지통 목록. 생략 시 일반 목록.
GET /admin/api/secrets/:id/versions?page=1&perPage=20{data,total}, 버전 내림차순. 페이지당 최대100. 각 항목: id, secretItemId, version, description, createdAt.
POST /admin/api/secrets/:id/restore빈 JSON 객체. 비활성 상태로 복구 후 Secret 메타데이터 반환.
POST /admin/api/secrets/:id/versions/:version/restore빈 JSON 객체. 새 버전 생성 후 Secret 메타데이터 반환. 휴지통 항목은 불가.

생성·PATCH의 failureThreshold는 null 또는 1~100 정수입니다. Secret 메타데이터에는 currentVersion, deletedAt, failureThreshold, failureCount, autoDisabledAt가 추가됩니다. 입력 오류400, 세션 없음401, 역할·범위 거부403, 복구할 항목·버전 없음404입니다. 복구는 admin.secret.restore, 버전 복원은 admin.secret.version.restore로 감사 기록을 남깁니다.

프로젝트 환경 분리

하나의 Project 안에서 default(기본), development(개발), staging(스테이징), production(운영)을 사용합니다. Project 상세 화면의 프로젝트 환경에서 Secrets 또는 Access Keys로 이동하세요. 목록의 환경 필터로 전환하고, 새로 만들기는 현재 필터의 Project·환경을 이어받습니다.

같은 Secret 이름도 환경별로 서로 다른 값·버전·휴지통·Active·실패 횟수를 가집니다. Access Key는 생성 시 선택한 한 환경에만 속하며, 동일 Project·환경의 Secret에만 권한을 배정할 수 있습니다. 다른 환경의 배정은 403입니다. Project나 환경 선택을 바꾸면 권한 표의 선택은 초기화됩니다. 생성 후 Project·환경 이동은 지원하지 않으며 대상 환경에 새 리소스를 만들어야 합니다. 키 재발급과 삭제 복구도 기존 환경을 유지합니다.

기존 데이터와 환경을 생략한 생성 요청은 default에 보존됩니다. Secret API 주소·응답 형식은 그대로이며, 검증된 Access Key가 환경을 결정합니다. 쿼리·헤더·본문으로 환경을 바꾸거나 다른 환경으로 대체 조회하지 않습니다. 해당 환경에 없거나 미배정인 Secret은 다른 곳에 동명 항목이 있어도 404입니다. sk_dev_·sk_live_는 DevKey 서비스 배포 환경 접두사이지 프로젝트 환경이 아닙니다.

이 기능은 애플리케이션의 데이터·API 권한 분리이며 별도 Cloudflare 배포나 암호화 키 분리가 아닙니다. 관리자 권한은 기존 Workspace 역할을 따릅니다. Developer는 소속 Workspace의 모든 환경을 변경할 수 있고 Viewer는 메타데이터를 조회할 수 있습니다. 별도의 환경별 관리자 역할은 제공하지 않습니다.

관리자 계약환경 동작
POST /admin/api/secrets, POST /admin/api/access-keys본문의 environment에 네 환경 중 하나 지정. 생략하면 default.
GET /admin/api/secrets?projectId=PROJECT_ID&environment=staging환경별 목록. 휴지통은 &deleted=true. 환경 생략 시 전체 환경.
GET /admin/api/access-keys?projectId=PROJECT_ID&environment=staging같은 환경의 Access Key 목록.
GET /admin/api/audit-logs?environment=staging환경별 감사 로그. 과거·미확인·Workspace 전체 기록은 환경이 null.

잘못된 환경·PATCH 환경 변경 시도는 400, 같은 Project/환경/key 중복 생성은 휴지통을 포함해 409입니다. JSON 가져오기는 Secret별 environment를 지정할 수 있으며 생략하면 default입니다. 로컬 더미 시드도 default만 대상으로 합니다. 기존 데이터의 값·ID·권한·버전은 변경하지 않습니다.

배포 시 구 Worker 요청을 중단하고 0007_mute_nightshade.sql과 새 Worker를 함께 적용하세요. 새 환경 데이터가 생긴 뒤 구 코드로 되돌리면 안 됩니다. 마이그레이션의 환경·권한 검사 트리거와 기존 버전 트리거는 향후 테이블 재생성에서도 유지해야 합니다.

JSON 일괄 등록

Settings → JSON 일괄 가져오기에서 Workspace·프로젝트·시크릿을 중첩 JSON 하나로 등록할 수 있습니다. 가져오기는 기존 데이터를 수정하지 않고 새 리소스를 생성하며, ID와 관계는 서버가 안전하게 생성합니다. 기존 accounts 키도 호환됩니다.

JSON import payload
{
  "workspaces": [
    {
      "name": "ERP",
      "projects": [
        {
          "name": "ERP API",
          "slug": "erp",
          "secrets": [
            {
              "key": "DB_PASSWORD",
              "value": "replace-with-a-real-secret",
              "description": "Production database password",
              "environment": "production",
              "isActive": true
            }
          ]
        }
      ]
    }
  ]
}

위 예제는 erp 프로젝트의 production 환경에 Secret을 생성합니다. Secret별 environment를 생략하면 default이며, Project 이름이나 slug가 환경을 결정하지 않습니다. 관리자 API를 직접 호출할 때는 로그인 세션 쿠키와 함께 POST /admin/api/import로 같은 JSON을 전송합니다. 성공 응답은 생성된 Workspace·프로젝트·시크릿 개수만 반환합니다.

JSON의 시크릿 값은 평문입니다. HTTPS 관리자 콘솔에서만 입력하고, 터미널 기록·서버 로그·이슈·채팅에 값을 남기지 마세요. 저장 전에 서버에서 AES-GCM으로 암호화되며, 등록 성공 후 콘솔 입력창은 내용을 지웁니다.

04 · Access keys

Access Key 발급과 수명 관리

Access Key는 sk_dev_... 또는 sk_live_... 형식입니다. 서버는 원문 대신 HMAC-SHA-256 해시만 저장하고, 요청 때 prefix로 후보를 찾은 뒤 전체 키를 검증합니다.

기간 선택

운영 환경의 Access Key는 만료일을 생략하면 기본 30일 후 만료됩니다. 필요하면 운영에서도 기한없음을 명시적으로 선택할 수 있고, 날짜를 지정하는 경우 최대 90일까지 가능합니다. 화면의 빠른 기간은 기한 없음·30일·60일·90일이며, 만료 시각은 저장된 UTC ISO 시각으로 비교됩니다.

생성 후 운영 체크리스트

  • 원문 키를 CI/CD 또는 Worker의 시크릿 저장소에 즉시 저장합니다.
  • 같은 Project 안에서도 개발·스테이징·운영 환경별 Secret과 Access Key를 분리합니다.
  • 필요한 Secret만 선택하고, 목록 조회가 필요 없으면 canList를 끕니다.
  • Workspace 소유자에게 디스코드 만료 알림 등록을 요청하고, 필요한 키에 교체 주기를 설정합니다.
  • 유출이 의심되면 Access Key 상세의 긴급 재발급 기능으로 이전 키를 즉시 폐기합니다. 정기 교체의 유예기간과 구분하세요.

디스코드 알림 · 정기 교체

키 만료·교체 예정 알림은 이메일 대신 Discord 웹훅으로 받습니다. 알림은 자동으로 보내지만 새 키 발급과 서비스 적용은 관리자가 진행합니다.

1. Workspace에 디스코드 웹훅 연결

  1. 채널의 웹훅 URL 준비Discord 텍스트 채널의 설정 → 연동 → 웹후크에서 URL을 복사합니다. 웹훅 주소 자체가 자격 증명이므로 공개 채팅·Git·로그에 남기지 마세요.
  2. Workspace 상세에서 등록Owner로 로그인한 뒤 Workspaces → 해당 Workspace 상세 → 디스코드 키 알림에 URL을 붙여 넣습니다. Admin·Developer·Viewer는 이 설정과 발송 이력을 조회·변경할 수 없습니다.
  3. 활성화하고 저장만료·정기 교체 알림 활성화를 선택하고 설정 저장을 누릅니다. 해당 Workspace의 모든 Project·환경에 적용됩니다. 저장은 연결 확인이나 테스트 발송이 아니며, 실제 발송 결과는 다음 운영 Cron 이후 확인합니다.

https://discord.com/api/webhooks/ID/TOKEN 또는 /api/v10/webhooks/ 주소만 허용합니다. 다른 호스트·포트·리다이렉트·쿼리·포럼/스레드는 지원하지 않습니다. 주소는 암호화해 저장하고 API에서 다시 보여주지 않습니다. 입력란을 비우면 기존 주소를 유지합니다. 메시지에는 Workspace·Project·환경·키 이름·예정일·관리 링크만 들어가며 원문·해시·Secret 값은 전송하지 않습니다. 멘션은 차단합니다.

2. 만료일과 교체 주기 구분

운영 Worker의 Cron이 매시간 활성 키의 만료일과 교체 예정일을 확인해 7·3·1일 이내 및 기한 도래 알림을 보냅니다. 놓친 구간은 가장 임박한 단계만 보냅니다. 비활성·교체 진행 중인 키는 제외하며, 만료된 키에는 정기 교체 알림을 보내지 않습니다. 기한 없음 키도 교체 주기를 설정할 수 있고, 교체 예정일이 지났다는 이유만으로 키를 차단하지 않습니다. 예를 들어 기한이 2일 남았을 때 처음 확인하면 7일 알림 없이 3일 이내 알림부터 보냅니다.

Access Key 상세의 정기 키 교체에서 Developer 이상이 정기 교체 알림 안 함·30·60·90일마다를 선택하고 교체 주기 저장을 누릅니다. 키 발급일부터 계산되며, 새 키 발급 시 주기가 다시 시작됩니다. 오래된 키에 주기를 설정하면 교체 예정일이 이미 지났을 수 있습니다.

시각결정 방식기한이 지나면
만료일 expiresAt발급·수정 시 지정. 운영에서 발급 시 생략하면 30일.Secret API 인증 거부(401).
교체 예정일 rotationDueAt키 발급일 + 30·60·90일. 알림 안 함은 null.교체 알림만 발송. 이 시각만으로 키를 차단하지 않음.
이전 키 사용 종료 retireAt교체 시작 + 1·24·72시간과 기존 만료일 중 빠른 시각.Cron 실행 여부와 관계없이 Secret API 인증 거부(401).
교체 주기는 만료일을 늘리지 않습니다. 만료가 30일 뒤인 키에 60일 교체 주기를 지정해도 30일 뒤 만료됩니다. 정기 교체로 발급한 새 키의 만료일도 이전 키에서 승계하지 않습니다. 콘솔의 교체 시작은 만료일을 별도로 전송하지 않으므로 운영 기본 30일이 적용됩니다. 새 키의 만료 설정도 확인하세요.

3. 새 키 적용 후 이전 키 폐기

  1. 새 키 발급 · 정기 교체 시작Access Key 상세에서 이전 키 유예기간 1·24·72시간(기본 24시간)을 선택하고 시작합니다. 새 키는 프로젝트·환경·권한·교체 주기를 승계하며 기존 휴지통 권한도 유지합니다.
  2. 원문 저장 후 서비스에 적용한 번만 표시되는 새 키를 안전한 저장소에 보관하고 사용하는 서비스에 직접 적용합니다. 이전 키는 유예기간 안에서 함께 쓸 수 있지만 원래 만료일을 넘기지는 않습니다.
  3. 새 키 적용 완료 · 이전 키 지금 폐기새 키로 필요한 Secret API 호출이 성공하는지 확인한 뒤 이전 키 상세에서 완료 버튼을 누릅니다. 직접 완료하지 않아도 사용 종료 시각부터 이전 키는 사용할 수 없습니다.

발급·권한 복사·폐기 예약·감사 기록은 한 트랜잭션이며 중복 교체를 막습니다. 비활성·만료·이미 교체 중인 키는 시작할 수 없습니다. 이전 키의 사용 종료 시각은 Cron이 지연돼도 API 인증에서 강제됩니다. 종료 예약을 취소·연장하거나 폐기 키를 재활성화할 수 없습니다. 이미 진행 중인 요청은 완료될 수 있습니다. 새 키를 삭제해도 이전 키의 종료 예약은 유지됩니다.

기존 유출 의심 · 즉시 폐기 후 재발급은 이전 키를 먼저 차단하는 긴급 기능으로 유지합니다. 운영 배포의 새 키는 만료일 생략 시 기본 30일이며, 교체 주기와 만료일은 별도입니다. 외부 서비스 Secret의 자동 재발급·배포는 지원하지 않습니다. 기존 인증/보안 이메일은 변경하지 않습니다.

4. 발송 상태와 재시도

Workspace 상세에서 Owner가 최근 발송 상태 최대 20건을 확인할 수 있습니다. 화면을 새로고침해 최신 결과를 확인하세요. 네트워크·5xx 오류는 1시간부터 지수 백오프로 최초 발송을 포함해 최대 5회 시도합니다. 429는 Retry-After를 반영해 발송을 일시 중지합니다.

화면 상태 / API 값의미와 대응
재시도 대기 / pending대기 또는 재시도 예정입니다. 다음 운영 Cron과 오류 코드를 확인합니다.
발송 중 / sending발송 처리 중입니다. 처리 중 장애는 잠금 만료 후 다음 실행에서 복구합니다.
발송 완료 / sentDiscord가 성공 응답을 반환했습니다. 채널에서 메시지를 확인합니다.
발송 실패 / failed재시도 한도 또는 재시도하지 않는 오류입니다. 오류 원인을 해결하고 필요한 키 교체는 직접 진행합니다.
설정/키 변경으로 건너뜀 / skipped설정·키 상태·예정일·알림 단계가 달라진 오래된 알림입니다. 현재 설정과 키를 기준으로 확인합니다.

일반 중복은 방지하지만 Discord 수신 직후 DB 기록 전에 장애가 나면 중복될 수 있습니다. 이미 처리된 동일 키·예정일·단계는 웹훅 재등록이나 설정 재활성화로 다시 보내지 않으며, 수동 재발송 기능은 없습니다. 한 실행은 종류별 최대 200건을 준비하고 40건을 처리하므로 대기량이 많으면 다음 실행으로 넘어갑니다. 웹훅 삭제 시 주소와 알림 설정은 제거하고 이력은 유지하지만, 키 또는 Workspace 자체를 삭제하면 관련 발송 이력도 삭제됩니다.

관리자 API 계약과 JSON 예제

아래 경로는 https://dev-key.com의 관리자 세션으로 호출합니다. 문서 호스트나 API 호스트에서는 제공하지 않습니다. JSON 예제는 요청 본문이며 Bearer Access Key나 웹훅 URL을 공개 예제에 넣지 마세요.

관리 API권한 / 계약
GET /admin/api/workspaces/:id/discordOwner. configured, enabled, updatedAt, deliveries. URL·암호문 제외.
PUT /admin/api/workspaces/:id/discordOwner. webhookUrl(생략 시 유지), enabled(boolean 필수). API에서 빈 문자열 URL은 400이므로 유지할 때는 필드를 생략합니다. 저장만 하며 즉시 메시지를 보내지 않습니다.
DELETE /admin/api/workspaces/:id/discordOwner. 주소 삭제·알림 중지. 발송 이력은 유지.
PATCH /admin/api/access-keys/:id/rotation-policyDeveloper+. rotationIntervalDays: null/30/60/90.
POST /admin/api/access-keys/:id/rotation/startDeveloper+. graceHours: 1/24/72(생략24), expiresAt: ISO/null/생략. 201 {rawKey,accessKey,previousAccessKey}, no-store.
POST /admin/api/access-keys/:id/rotation/completeDeveloper+. 이전 키를 지금 폐기하고 메타데이터 반환.
PATCH /admin/api/access-keys/ACCESS_KEY_ID/rotation-policy · 30일 주기
{ "rotationIntervalDays": 30 }
POST /admin/api/access-keys/ACCESS_KEY_ID/rotation/start · 24시간 유예
{ "graceHours": 24 }

교체 알림을 끄려면 rotationIntervalDays에 null을 보내며, 0은 허용하지 않습니다. 교체 시작의 expiresAt은 null이면 기한 없음, ISO 시각이면 지정 만료일, 생략하면 환경별 발급 기본값을 사용합니다. 정기 교체를 완료할 때는 이전 키 ID의 /rotation/complete를 호출합니다.

메타데이터에 rotationIntervalDays·rotationDueAt·retireAt·replacedByKeyId가 추가되며 설정 전에는 null입니다. 검증 오류 400, 세션 없음 401, 권한/범위 부족 403, 교체 상태 충돌 409입니다. 관리자 세션·최근 인증·MFA·호스트 분리를 그대로 적용하며 Bearer Key로 관리 기능을 호출할 수 없습니다.

배포 전 0008 마이그레이션을 적용해야 합니다. 기존 키의 만료·권한은 유지됩니다. 이전 Worker는 종료 예약을 검사하지 않으므로 유지보수 구간에 마이그레이션/Worker를 함께 적용하고 진행 중인 교체가 있을 때 구버전으로 롤백하지 마세요. 로컬 개발의 scheduled 호출은 외부 디스코드 메시지를 발송하지 않습니다. 운영 배포 후 Cron이 활성화되며, 무료 플랜의 CPU 한도와 발송 대기량을 확인하세요.

05 · Permissions

권한은 Access Key가 아니라 Secret에 부여합니다

Access Key 설정에서 “Access Key 자체”나 별도 메뉴를 권한 대상으로 고르는 방식이 아닙니다. 선택한 Project의 Secret Item을 행으로 보고, 각 행에 동작을 부여하는 구조입니다.

플래그현재 의미권장 사용
canRead해당 Secret의 값을 단건으로 읽을 수 있음필요한 단건 조회에만 켭니다.
canList프로젝트의 .env 목록 응답에 포함할 수 있음. 현재 구현은 Read도 함께 필요부팅 시 여러 값을 한 번에 주입할 때만 켭니다.
canWrite기존 Secret의 값을 교체할 수 있음애플리케이션의 런타임 시크릿 회전에만 켭니다.
canDelete기존 Secret을 비활성화하고 휴지통으로 이동복구는 관리자만 가능하므로 필요한 키에만 켭니다.
Project 범위는 서버에서 재검증됩니다. 화면에서 보이는 Secret 목록뿐 아니라 API가 제출된 모든 Secret ID가 Access Key의 Project에 속하는지 확인합니다. 다른 Project의 ID를 임의로 넣어도 403으로 거절됩니다.

06 · API

API로 Secret 읽기·쓰기·삭제

운영 API의 기본 주소는 https://dev.dev-key.com입니다. 모든 Secret API 요청은 Authorization: Bearer 헤더에 Access Key를 담아야 합니다. 원문 키를 URL, 로그, 소스 코드에 넣지 마세요.

API 요청 제한을 넘으면 429 Too Many Requests와 JSON 오류가 반환되며, Worker가 처리한 응답에는 Retry-After: 10 헤더가 포함됩니다. 헤더의 초만큼 기다린 뒤 재시도하세요.

단건 조회 · curl
curl --fail-with-body --silent --show-error   https://dev.dev-key.com/api/v1/projects/erp/secrets/DB_PASSWORD   -H "Authorization: Bearer $DEVKEY_ACCESS_KEY"

성공 응답은 다음처럼 project, key, 복호화된 value를 반환합니다.

200 · application/json
{
  "project": "erp",
  "key": "DB_PASSWORD",
  "value": "example-value"
}

프로젝트의 여러 값 읽기

목록 조회 · curl
curl --fail-with-body --silent --show-error   https://dev.dev-key.com/api/v1/projects/erp/secrets   -H "Authorization: Bearer $DEVKEY_ACCESS_KEY"

목록 응답에는 Access Key에서 canRead와 canList가 모두 켜진 활성·미삭제 Secret만 포함됩니다.

Secret 값 교체

canWrite 권한이 있는 Access Key는 이미 존재하는 Secret의 값만 교체할 수 있습니다. 새 Secret 생성과 설명·활성 상태 변경은 관리자 콘솔에서 수행합니다. PUT 응답에는 새 Secret 값이 포함되지 않습니다.

값 교체 · curl
curl --fail-with-body --silent --show-error   -X PUT https://dev.dev-key.com/api/v1/projects/erp/secrets/DB_PASSWORD   -H "Authorization: Bearer $DEVKEY_ACCESS_KEY"   -H "Content-Type: application/json"   --data '{"value":"rotated-secret"}'
200 · application/json
{
  "project": "erp",
  "key": "DB_PASSWORD"
}

Secret 삭제

canDelete 권한이 있는 Access Key는 해당 Project의 활성 Secret을 휴지통으로 이동할 수 있습니다. 성공 시 204 No Content를 반환합니다. 즉시 비활성화되며 값·버전·연결된 권한은 보존됩니다. 복구는 관리자 콘솔에서만 가능하며 비활성 상태로 복구됩니다.

삭제 · curl
curl --fail-with-body --silent --show-error   -X DELETE https://dev.dev-key.com/api/v1/projects/erp/secrets/DB_PASSWORD   -H "Authorization: Bearer $DEVKEY_ACCESS_KEY"

공개 API 엔드포인트

메서드경로인증응답
GET/health없음{ "ok": true }
GET/api/v1/projects/:slug/secrets/:keyBearer Access Key단건 Secret JSON
PUT/api/v1/projects/:slug/secrets/:keyBearer Access Key + canWriteProject와 key 메타데이터
DELETE/api/v1/projects/:slug/secrets/:keyBearer Access Key + canDelete204 No Content
GET/api/v1/projects/:slug/secretsBearer Access Key{ "secrets": { ... } }
GET/api/v1/projects/:slug/secrets.envBearer Access Key환경 변수 형식의 text/plain
GET/openapi.json없음OpenAPI 3 문서

대화형 API Reference는 dev.dev-key.com/docs에서 열 수 있습니다. 문서 호스트에서 /docs로도 같은 Swagger 화면을 사용할 수 있습니다.

07 · Environment injection

.env 형식으로 주입하기

애플리케이션 시작 시 여러 값을 읽어야 한다면 secrets.env를 사용합니다. 응답은 KEY=value 줄 목록이며, 값에 줄바꿈·따옴표가 있을 때는 서버의 dotenv 포맷터 규칙을 따릅니다.

.env 다운로드 · curl
curl --fail-with-body --silent --show-error   https://dev.dev-key.com/api/v1/projects/erp/secrets.env   -H "Authorization: Bearer $DEVKEY_ACCESS_KEY"   -o .env.runtime
파일을 로그에 남기지 마세요. .env.runtime는 프로세스 시작 직후 메모리로 읽고, 저장이 필요하면 배포 플랫폼의 시크릿 저장소를 사용합니다. Git에 커밋하지 않도록 .gitignore에 추가하세요.

08 · Security model

보안 경계와 감사

세션 + MFA

관리자 API는 Access Key가 아니라 Better Auth 세션을 사용하며, 운영에서는 TOTP 2단계 인증을 완료한 세션만 허용합니다. 세션 쿠키를 API 키처럼 공유하지 마세요.

암호화 저장

Secret 값은 Worker의 암호화 키로 AES-GCM 처리되어 D1에 저장됩니다. 목록 DTO에는 평문·암호문을 포함하지 않습니다.

최소 권한

Read·List·Write·Delete를 분리해 필요한 범위만 허용합니다. 특히 Write와 Delete는 자동화 키에 신중하게 부여하세요.

감사 추적

읽기·목록 조회·값 교체·삭제의 성공·실패, Project·Access Key, IP, User-Agent, 오류 코드가 감사 로그에 남습니다.

호스트 분리

dev-key.com은 관리자 UI, dev.dev-key.com은 API, docs.dev-key.com은 문서 전용입니다.

키 회전

유출 대응은 이전 키를 먼저 비활성화합니다. 정기 교체는 제한된 유예기간 동안 두 키를 함께 사용할 수 있으며 종료 예약은 취소·연장할 수 없습니다. 새 원문은 한 번만 표시됩니다.

WAF Rate Limit

운영 API에 적용하는 /api/v1/* 규칙은 현재 Free 플랜 기준 Cloudflare 접속 지점과 클라이언트 IP별 10초 요청 수를 제한하고, 초과 요청을 429로 응답합니다.

Retry-After

Worker rate-limit binding이 제한을 초과한 API 요청에 429와 Retry-After: 10을 반환합니다. 클라이언트는 헤더의 초 단위만큼 기다린 뒤 재시도하세요.

실패 알림

한 IP에서 5분 안에 401/403이 5회 발생하면 해당 Workspace의 Owner에게 Resend 이메일을 보냅니다. 감사 로그에는 실패와 오류 코드가 남고, 키 원문은 저장하지 않습니다. 디스코드 만료·교체 알림이나 Secret 자동 비활성화와는 별개입니다.

운영 전 확인

  • 브라우저 개발자 도구·서버 로그·CI 출력에 Access Key가 찍히지 않는지 확인합니다.
  • 만료·비활성화 후 같은 요청이 401이 되는지 스테이징에서 테스트합니다.
  • 접근하지 않아야 할 Secret은 권한을 끄고, 필요한 경우 Project 자체를 분리합니다.
  • 감사 로그에서 비정상 IP·User-Agent·실패 코드가 반복되는지 확인합니다.

Audit history

관리자 변경 이력과 감사 로그 검색

콘솔의 Audit Logs에서 관리자 작업과 기존 Secret API 접근 기록을 함께 확인합니다. 최신순으로 표시되며, 100건 이전의 기록도 페이지를 이동해 조회할 수 있습니다. 검색창에서는 동작, 작업자 ID, 대상 ID, Secret 이름, IP, 오류 코드를 검색합니다. 필터 메뉴에서 Workspace, Project, 동작, 작업자 ID, 시작·종료 시각을 선택하고 Success에서 성공·실패를 구분하세요. 화면에서 입력한 시각은 브라우저의 현지 시각을 기준으로 서버에 전달합니다.

Workspace·Project·Secret·멤버·Access Key·권한의 생성, 수정, 삭제는 admin.* 동작으로 기록됩니다. 실제 변경과 기록을 하나의 D1 트랜잭션에 저장하므로 기록 저장에 실패하면 해당 변경도 취소됩니다. 긴급 재발급은 기존 키 폐기, 새 키 생성, 권한 설정 등 반영된 단계를 각각 남기고, JSON 일괄 등록은 생성된 Workspace마다 기록을 남깁니다. 거부된 관리자 요청은 변경 성공 이력에 포함하지 않습니다. 기존 Secret API의 성공·실패 기록은 계속 유지됩니다.

디스코드 설정 저장·삭제는 admin.workspace.discord.update·admin.workspace.discord.delete, 교체 주기·시작·완료는 admin.access_key.rotation.policy·admin.access_key.rotation.start·admin.access_key.rotation.complete로 검색합니다. 정기 교체 시작은 새 키 생성·권한 복사·이전 키 종료 예약과 감사 기록을 한 번에 저장합니다. Cron이 종료된 키의 비활성 상태를 반영하면 ACCESS_KEY_RETIRED가 남습니다. Discord 발송 결과는 감사 로그가 아니라 Workspace 상세의 최근 발송 상태에서 확인하세요.

기록에는 작업자 ID, 대상 종류·ID, 동작, 시각, IP, User-Agent만 사용하며 Secret 값·암호문·Access Key 원문·해시는 포함하지 않습니다. 대상 삭제 후에도 대상 ID는 남습니다. Project 삭제 후에는 Workspace 멤버가 이력을 볼 수 있고, Workspace까지 삭제되어 범위 참조가 사라지면 각 관리자 기록의 작업자 본인만 조회할 수 있습니다. 현재 Workspace에서 제거된 멤버는 해당 Workspace 기록에 접근할 수 없습니다.

관리자 조회 API

웹 호스트의 GET /admin/api/audit-logs는 관리자 세션이 필요하며 { "data": [...], "total": 123 } 형식으로 반환합니다. 기본값은 page=1&perPage=25&sort=createdAt&order=DESC이고 페이지당 최대 100건입니다. workspaceId, projectId, environment, actorUserId, action, success=true|false, q를 조합할 수 있습니다. Workspace 전체 설정이나 과거·미확인 기록의 환경은 null이므로 특정 환경 필터에서는 제외됩니다. from·to는 시간대가 포함된 ISO 시각이며 양쪽 경계를 포함합니다. 잘못된 범위나 정렬 필드는 400, 접근할 수 없는 Workspace·Project 필터는 403을 반환합니다.

정렬 필드는 createdAt, id, accountId, projectId, actorUserId, action, resourceType, resourceId, secretKey, success, errorCode이며 order=ASC|DESC를 사용합니다. 배포 시 새 감사 로그 마이그레이션을 먼저 적용하세요. 이전 기록의 대상 종류·ID는 비어 있을 수 있습니다.

09 · Local & deploy

로컬 개발과 배포

DevKey는 Vite로 React 정적 자산을 빌드하고 Hono Worker가 API와 자산을 함께 제공합니다. D1 마이그레이션과 비밀값은 배포 환경별로 분리됩니다.

로컬 시작

PowerShell / macOS / Linux
npm install
Copy-Item .dev.vars.example .dev.vars  # PowerShell
# cp .dev.vars.example .dev.vars       # macOS/Linux
npm run db:migrate:local
npm run build
npm run dev

.dev.vars에는 로컬 암호화 키·Access Key pepper·Better Auth 설정이 필요합니다. 예제 파일을 복사한 뒤 값은 직접 생성하고, 파일 내용은 커밋하거나 공유하지 마세요.

개발용 바로 로그인

http://127.0.0.1:8787/ 또는 http://localhost:8787/에서 개발용 바로 로그인을 누르면 이메일·비밀번호 입력 없이 관리자 화면으로 들어갑니다. 최초 클릭 시 로컬 D1에 전용 계정 developer@dev-key.invalid를 만들고 이후에는 같은 계정을 재사용합니다. 기존 사용자로 로그인하거나 다른 사용자의 Workspace 권한을 부여하지 않습니다. 처음에는 Workspace를 만들어 테스트를 시작하세요.

정식 Better Auth 세션과 HttpOnly 쿠키를 사용하며 로그아웃·권한 검사는 일반 로그인과 같습니다. 쿠키는 브라우저 세션 동안, 서버 세션은 최대 24시간 유지됩니다. 비밀번호 없는 개발 계정에는 테스트용 데이터만 사용하세요.

APP_ENV=development와 DEV_LOGIN_ENABLED=true일 때만 동작합니다. 요청 주소와 PUBLIC_APP_ORIGIN·PUBLIC_API_ORIGIN·BETTER_AUTH_URL은 정확한 localhost, 127.0.0.1, [::1] 중 하나여야 하며, 로그인은 동일 출처 JSON POST만 허용합니다. 운영·프리뷰·LAN 주소에서는 사용할 수 없습니다. REQUIRE_ADMIN_MFA=true이면 비활성화되며 개발 계정의 MFA나 변경된 계정 정보를 덮어쓰지 않습니다. 끄려면 .dev.vars에 DEV_LOGIN_ENABLED=false를 설정하고 서버를 재시작하세요.

GET /api/auth/dev-login-status는 계정 생성 없이 { "enabled": true | false }를 반환합니다. POST /api/auth/dev-login에 빈 JSON 객체를 보내면 세션 쿠키와 { "ok": true }를 반환하며 JSON에 토큰은 포함하지 않습니다. 공개 Secret API가 아닌 로컬 개발 전용 경로이고 이메일은 발송하지 않습니다. UI 수정 후에는 npm run build로 자산을 다시 빌드하세요.

개발용 더미 데이터

로컬 서버를 실행한 상태에서 별도 터미널로 아래 명령을 실행하고, 개발용 바로 로그인 계정의 관리자 화면을 새로고침하세요. 로그인할 때 자동으로 데이터를 추가하지는 않습니다.

Local demo seed
npm run db:seed:local
# 다른 로컬 포트를 사용하는 경우
npm run db:seed:local -- http://localhost:8787

[Demo] Commerce·[Demo] Platform Workspace 2개에 Project 6개, Secret 60개(활성 54·비활성 6), Access Key 12개(활성 읽기 전용 6·비활성 읽기/쓰기 6), 권한 108개를 준비합니다. 키 만료는 생성 시점부터 런타임 30일·폐기된 CI 7일로 설정합니다. 최초 실행은 실제 관리자 변경 감사 기록 104개를 남겨 100개 이후 페이지도 확인할 수 있으며, User-Agent는 DevKey-local-demo-seed/1입니다.

가짜 값과 .invalid 주소만 사용하며, 기존 관리자 API를 거쳐 Secret 암호화·키 해시·권한 범위·감사 기록 규칙을 그대로 적용합니다. 원본 키·Secret 값·세션 쿠키를 출력하거나 파일에 저장하지 않습니다. Secret API를 직접 호출하려면 UI에서 별도의 테스트 키를 만들고 최초 생성 응답에서 복사하세요. 시드 프로세스의 임시 로그인 세션만 종료하며 브라우저 로그인은 유지합니다.

개발 로그인 기능이 활성화된 정확한 로컬 주소에서 전용 계정과 Workspace 소유권을 확인한 뒤 실행합니다. 외부·LAN 주소와 리다이렉트는 거부하며 운영 명령은 실행하지 않습니다. 동시에 여러 시드를 실행하지 마세요. 재실행은 Workspace 이름, 해당 Workspace의 Project slug, 해당 Project의 default 환경 안에서 Secret key와 Access Key 이름으로 기존 항목을 찾아 건너뛰고 누락된 항목만 추가합니다. 수정한 값·상태·권한·만료일을 덮어쓰지 않고, 이름이 중복되면 중단합니다.

전체 시드는 단일 트랜잭션이 아니므로 실패하면 일부 데이터가 남을 수 있습니다. 원인을 해결한 뒤 재실행하면 누락 항목을 채웁니다. 생성과 비활성화 사이에 중단된 항목은 활성 상태로 남을 수 있고, 재실행 시 기존 상태를 자동 초기화하지 않습니다. 기존 데이터 삭제·초기화는 수행하지 않습니다.

검증과 운영 배포

검증
npm test
npm run build

운영 배포 스크립트는 빌드 → 원격 D1 마이그레이션 → 운영 비밀값 동기화 → wrangler deploy --env production 순서로 실행됩니다. 이 과정에서 Worker rate-limit binding과 MFA 스키마가 배포됩니다. WAF zone 규칙은 별도로 적용합니다.

최근 추가된 프로젝트 환경 분리는 0007_mute_nightshade.sql, 디스코드 알림·정기 교체는 0008_parched_serpent_society.sql을 포함한 마이그레이션이 필요합니다. 구 Worker 요청을 중단하는 유지보수 구간에 스키마와 Worker를 함께 적용하세요. 새 환경 데이터나 교체 종료 예약이 생긴 뒤 이전 Worker로 되돌리면 안 됩니다. 알림·교체 운영 주의사항도 함께 확인하세요.

운영 명령은 승인 후 실행합니다. npm run deploy, npm run db:migrate:prod, npm run secrets:prod는 원격 상태를 변경합니다. 먼저 변경 범위·마이그레이션 SQL·환경 변수를 리뷰하고, 승인된 운영 세션에서만 실행하세요.

운영 API Rate Limiting

Cloudflare WAF 규칙은 Worker 배포와 별도로 zone의 http_ratelimit Ruleset에 적용합니다. 현재 Free 플랜에서는 Host 조건을 사용할 수 없어 /api/v1/* 경로 기준으로 제한하며, Worker의 호스트 분리가 실제 Secret API 노출 범위를 계속 보장합니다. 저장소의 스크립트는 동일한 규칙 참조값을 재사용해 중복 생성하지 않습니다. Cloudflare API 토큰은 커밋하거나 로그에 남기지 마세요.

Rate limit 검토·적용
npm run cloudflare:waf:rate-limit
npm run cloudflare:waf:rate-limit -- --apply

호스트별 책임

호스트역할허용 표면
dev-key.com
www.dev-key.com
관리자 웹React UI, Better Auth 경로
dev.dev-key.comAPI/api/*, /health, OpenAPI, Swagger
docs.dev-key.com문서문서 홈, Swagger, OpenAPI JSON
localhost개발 편의웹·API 표면을 함께 제공

10 · Troubleshooting

문제 해결

증상가능한 원인확인할 것
401 Missing or invalid bearer tokenAuthorization 헤더가 없거나 형식이 다름Authorization: Bearer $DEVKEY_ACCESS_KEY와 키의 공백·개행을 확인합니다.
401 Access key has expired만료일이 현재 시각보다 과거콘솔에서 만료일을 확인하고 새 키를 발급합니다.
401 Access key is inactive or missing키가 비활성화됐거나 prefix를 찾지 못함키 전체를 다시 복사하지 말고, 새 키를 발급해 배포합니다.
401 Access key rotation grace period has ended이전 키의 정기 교체 유예기간이 종료됨서비스에 교체된 새 키를 적용합니다. 이전 키는 연장·재활성화할 수 없으며, Cron이 비활성 상태를 반영한 후에는 inactive 오류로 보일 수 있습니다.
429 Too many API requestsWorker 또는 Cloudflare WAF API 요청 제한 초과Retry-After가 있으면 해당 초만큼 기다린 뒤 재시도합니다. 정상 트래픽이 계속 제한되면 Worker binding과 WAF 임계값을 함께 점검합니다.
403 Access key cannot read this secret해당 Secret에 Read 권한이 없음Access Key 상세 → 권한에서 Secret 행의 Read를 켭니다.
403 Access key cannot write this secret해당 Secret에 Write 권한이 없음값 교체 요청에는 Secret 행의 Write가 필요합니다.
403 Access key cannot delete this secret해당 Secret에 Delete 권한이 없음삭제 요청에는 Secret 행의 Delete가 필요합니다.
API 목록에 Secret이 안 보임Read/List 권한, 환경, Active 또는 휴지통 상태가 맞지 않음같은 Project·환경의 활성·미삭제 Secret에 Read와 List가 모두 필요합니다. 콘솔에서는 환경·보관 상태 필터도 확인합니다.
404 Project/Secret not foundslug·key 오탈자, 다른 환경, 미배정 권한, 비활성 또는 삭제된 SecretAPI 호스트와 Project slug, Secret key의 대소문자 및 Access Key 환경을 확인합니다. 자동 비활성화라면 감사 로그와 실패 설정을 점검하고, 휴지통 복구 후에는 권한을 확인해 Active를 직접 켭니다.
409 정기 교체 상태 충돌비활성·만료·이미 교체 중인 키에서 교체 시작, 또는 종료 예약 후 정책 변경·재활성화 시도현재 키의 Active·만료일·이전 키 사용 종료 시각을 확인합니다. 완료 요청에는 새 키가 아니라 이전 키 ID를 사용하고, 폐기된 키는 새로 발급합니다.
관리자 API 401Better Auth 세션이 없거나 다른 호스트에서 호출관리자 UI 호스트에서 로그인하고 쿠키 전송 여부를 확인합니다.
Admin session required세션이 없거나 운영 MFA가 미완료관리자 웹 호스트에서 다시 로그인하고, Passkey 로그인 후 표시되는 TOTP 설정 또는 Settings → 관리자 TOTP MFA에서 등록을 완료합니다.
Session is not fresh관리자 세션의 최근 인증 기한(24시간)이 지남콘솔이 로그인 화면으로 전환되면 이메일·비밀번호 또는 Passkey로 다시 로그인합니다. 기존 리소스 캐시는 자동으로 제거됩니다.
반복 401/403 알림이 오지 않음Resend 설정 또는 Workspace Owner 이메일 없음RESEND_API_KEY와 발신자 설정을 확인합니다. 실패 자체는 감사 로그에 남습니다.
디스코드 설정이 안 보임 / 403Workspace Owner가 아님소유자에게 웹훅 설정·발송 이력 확인을 요청합니다. 키 교체는 Developer 이상이 수행합니다.
디스코드 만료·교체 알림이 오지 않음웹훅 미등록·중지, 알림 대상 없음, 로컬 개발 또는 운영 Cron 미실행웹훅 등록·활성화, 키 만료일·교체 예정일·Active·교체 진행 여부를 확인합니다. 저장 직후 테스트 발송은 없고 로컬에서는 발송하지 않습니다. 운영 Cron 실행 후 화면을 새로고침해 발송 상태를 확인합니다.
웹훅 저장 400 / DISCORD_HTTP_400·401·403·404허용되지 않는 URL 형식 또는 Discord가 거부한 웹훅·요청텍스트 채널의 원본 웹훅 URL을 확인하고 쿼리·스레드 주소를 사용하지 마세요. 삭제·재생성한 웹훅은 Owner가 새 주소를 등록합니다. 재시도하지 않는 실패는 원인을 해결해도 동일 알림을 자동 재발송하지 않습니다.
DISCORD_HTTP_429 / DISCORD_HTTP_5xx / DISCORD_NETWORK_ERRORDiscord 요청 제한, 서버 또는 네트워크 오류발송 상태와 시도 횟수를 확인합니다. Retry-After와 백오프 이후 운영 Cron에서 최대 5회까지 시도하며, 실패가 확정되면 필요한 교체를 직접 진행합니다.
DELIVERY_UNCONFIRMED / WEBHOOK_DECRYPT_FAILED최종 시도의 전송 결과 기록이 없거나 저장 주소 복호화에 실패미확정 발송은 채널에서 수신 여부를 확인합니다. 복호화 실패는 운영 암호화 설정을 점검하고 필요한 경우 Owner가 웹훅을 재등록합니다. 주소·비밀값을 로그에 출력하지 마세요.

11 · Glossary

용어

Workspace 팀과 권한을 묶는 최상위 경계. Project 서비스를 묶는 단위. Environment 한 Project 안에서 Secret·Access Key를 격리하는 default·development·staging·production 범위. Secret Item 버전과 휴지통을 가진 암호화 key-value 리소스. Access Key 한 Project·환경의 API 호출용 Bearer 자격 증명. Permission 키와 Secret 사이의 동작 범위. Audit Log 접근 성공·실패와 관리자 변경 추적 기록.

Discord Webhook Workspace 키 알림을 보내는 비밀 URL. Rotation Policy 발급일부터 계산하는 교체 알림 주기. Grace Period 정기 교체 때 이전 키 사용을 잠시 허용하는 기간이며, 만료일이나 종료 예약을 연장하는 기능은 아닙니다.

문서와 구현의 기준이 다르게 보이면 OpenAPI JSON과 현재 배포된 콘솔의 동작을 우선 확인하세요.