API key
API key의 종류와 scope, 웹 로그인으로 발급하고 폐기하기
API key는 /v1 세션 API를 부르는 Bearer 토큰이다. 요청마다 Authorization: Bearer <key>로 보낸다. key가 할 수 있는 일은 scope가 정한다. 에이전트·멤버·key 관리 같은 관리 API는 key로 부를 수 없고(403) 웹 로그인 cookie로만 부른다.
scope
| scope | 하는 일 |
|---|---|
sessions:read | 세션·turn·이벤트·사용량·receipt 읽기 |
sessions:write | 세션 만들기, 메시지 보내기, push 요청 |
sessions:control | interrupt, pause, resume, close, terminate, 프로필 변경 |
sessions:approve | 권한 요청과 질문에 답하기, push 요청의 승인·거부, PR 열기와 코멘트 |
sessions:recover | 복구 결정. write나 control에 포함되지 않는다 |
scope가 없는 요청은 본문을 읽기 전에 403 FORBIDDEN이다. sessions:approve는 개인 key와 에이전트 key 모두 발급할 때 넣을 수 있다. 다만 어떤 key도 그 key로 시작한 세션의 요청은 승인하지 못한다(403 self_approval). 다른 key나 사람이 승인한다.
종류
| 종류 | 누가 발급 | 어떻게 동작하나 |
|---|---|---|
개인 key (personal) | 모든 멤버, 자기 것만 | 만든 사람으로 동작한다. scope는 key의 scope와 그 사람의 지금 역할이 허용하는 scope의 교집합이다. 그 사람이 비활성화되면 401 |
에이전트 key (agent + agent_id) | owner·admin·그 에이전트 관리자 | 사람이 아니라 key 자체로 동작한다. 그 에이전트 release로 만든 세션만 만들고 본다. 발급한 사람이 떠나도 계속 동작한다. sessions:approve를 넣으면 그 에이전트의 세션에서 요청을 승인하고 질문에 답한다. 그 key로 시작한 세션은 빼고 |
에이전트를 고르지 않은 key, 곧 워크스페이스 전체에 닿는 key는 없다. agent_id 없이 kind: "agent"를 보내면 400 BAD_REQUEST다.
key에는 name(앞뒤 공백을 뺀 1~64자, 필수 아님)을 붙여 목록에서 알아볼 수 있다. 붙이지 않으면 null이다.
expires_at을 보내지 않으면 key는 폐기할 때까지 유효하다. 웹 콘솔의 새 에이전트 key 화면은 만료일을 90일 뒤로 채워 두고, 비우면 만료 없이 발급한다.
조직이 소유하고 사람에 묶이지 않는 조직 관리 key(Terraform·CI·SCIM 같은 자동화용)는 아직 없다. 웹 콘솔의 API key 화면에 owner·admin에게만 자리가 '준비 중'으로 보인다. 발급할 수 있게 되면 만료일은 필수다.
발급하기
웹 콘솔에서 발급하거나, 웹 로그인 cookie로 POST /v1/api-keys를 부른다. 아래는 만들기의 WEB 헤더를 쓴다. 비밀값(secret)은 이 응답에서 한 번만 나온다. 변수에 담고 출력하지 않는다.
issued=$(curl -sS "$API/v1/api-keys" "${WEB[@]}" \
-d '{"kind": "personal", "name": "laptop", "scopes": ["sessions:read", "sessions:write"]}')
MY_KEY=$(echo "$issued" | jq -r .secret)
KEY_ID=$(echo "$issued" | jq -r .key.id)
echo "$issued" | jq '{kind: .key.kind, name: .key.name, scopes: .key.scopes, receipt: (.receipt_id != null)}'
curl -sS -w '\n' "$API/v1" -H "Authorization: Bearer $MY_KEY"{
"kind": "personal",
"name": "laptop",
"scopes": [
"sessions:read",
"sessions:write"
],
"receipt": true
}
{"status":"ok","owner_id":"…"}발급한 사람의 scope를 넘는 scope를 요청하면 403 SCOPE_NOT_GRANTABLE이다.
목록과 폐기
목록에는 자기 개인 key와 자기가 관리하는 에이전트의 key만 나온다. 비밀값은 나오지 않는다.
last_used_at은 key가 마지막으로 인증에 성공한 시각이다. key마다 1분에 한 번까지만 기록하고, 다른 요청이 그 key로 세션에 쓰는 중이면 그 사용은 기록하지 않고 다음 사용에서 기록한다. 그래서 1분보다 늦을 수 있다. null은 마지막 사용 기록을 시작한 뒤로 기록된 사용이 없다는 뜻이고, 한 번도 쓰인 적이 없다는 뜻은 아니다. created_by_user는 발급한 사람의 id와 지금의 표시 이름이다. 그 사람이 워크스페이스를 떠나거나 계정이 비활성화돼도 이름은 그대로 나온다. 발급한 사람이 기록되지 않은 key(운영자 CLI로 만든 key)는 null이다.
curl -sS "$API/v1/api-keys" "${WEB[@]}" | jq -c --arg id "$KEY_ID" '.items[] | select(.id == $id) | {kind, name, scopes, revoked_at, used: (.last_used_at != null), issuer: .created_by_user.display_name}'
curl -sS -X DELETE "$API/v1/api-keys/$KEY_ID" "${WEB[@]}" | jq '{receipt: (.receipt_id != null)}'
curl -sS -o /dev/null -w '%{http_code}\n' "$API/v1" -H "Authorization: Bearer $MY_KEY"{"kind":"personal","name":"laptop","scopes":["sessions:read","sessions:write"],"revoked_at":null,"used":true,"issuer":"…"}
{
"receipt": true
}
401폐기한 key는 다음 요청부터 401이다. 이미 폐기한 key를 다시 폐기하면 아무것도 바꾸지 않고 receipt_id: null이다.
운영자 CLI
운영자는 서버에서 key를 바로 발급할 수 있다. 로컬 스택에서는 scripts/local.sh key다(개발용 서버 실행). 이 길은 위의 권한 판정을 거치지 않는다.
scripts/local.sh key <owner> --scopes sessions:read,sessions:write
scripts/local.sh key --workspace <workspace-id> --personal <user-id> --scopes sessions:read,sessions:write<owner>로 만든 key는 그 이름을 owner로 쓴다. --workspace는 --personal과 함께만 쓴다. 그 멤버의 개인 key가 되어 그 사람으로 동작한다. --personal 없이 --workspace만 주거나 <owner> 자리에 워크스페이스 id를 주면 거절한다. 워크스페이스 전체에 닿는 key는 없다. 에이전트 key는 웹 콘솔이나 POST /v1/api-keys로 발급한다.
SDK에서 key 쓰기
new Werft()는 WERFT_BASE_URL과 WERFT_API_KEY 환경 변수를 읽는다. 코드에서 정하려면 baseUrl과 apiKey를 넘긴다. 어떤 key로 무엇을 부르는지는 auth.me로, 서버가 답하는지는 root.get으로 확인한다. scope는 필요 없다.
import { Werft } from "@werft-ai/sdk";
const client = new Werft({
baseUrl: process.env.WERFT_BASE_URL,
apiKey: process.env.WERFT_API_KEY,
});
const me = await client.auth.me();
console.log(me.principal.kind, me.scopes);
const root = await client.root.get();
console.log(root.status);api_key [
'sessions:read',
'sessions:write',
'sessions:approve',
'sessions:control',
'sessions:recover'
]
okSDK는 key를 Authorization 헤더로만 보내고, 브라우저와 loopback이 아닌 http:// 주소에서는 클라이언트를 만들지 않는다. 옵션은 클라이언트 옵션에 있다.
웹 콘솔에서 발급한 개인·에이전트 key는 자기가 시작한 세션의 요청을 승인하지 못한다(403 self_approval). 개발용 서버의 운영자 key(scripts/local.sh key)만 예외다. 세션을 만드는 key와 승인하는 key를 나누는 법은 승인용 key 따로 쓰기에 있다. 에이전트 key에는 sessions:read, sessions:write, sessions:control, 승인자 key에는 sessions:read, sessions:approve가 필요하고, 세션의 공유 범위에 승인자가 들어 있어야 한다.