# 인증과 API 키

> 로그인(OAuth)과 계정 API 키 가운데 무엇을 쓸지, 키의 모양, 능력, 상품 범위 키, 한도와 폐기, 오류 코드.

## 요청 머리글

모든 호출은 `Authorization: Bearer <키>` 로 인증합니다. 공개 API 의 정본 호스트는 `api.gujo.ai` 입니다.

```bash
curl https://api.gujo.ai/api/products \
  -H "Authorization: Bearer $GUJO_API_KEY"
```

예전 클라이언트가 부르는 `gujo.ai/api/...` 도 `api.gujo.ai` 로 넘어가지만, 그 길은 `authorization`·`content-type`·`accept`·`accept-language`·`user-agent` 와 `X-*` 머리글만 넘깁니다. 새 코드는 언제나 `api.gujo.ai` 를 부릅니다.

## 로그인(OAuth)과 API 키

에이전트를 고객 MCP 에 붙일 때는 로그인이 먼저입니다. 키는 사람이 브라우저로 로그인할 수 없는 곳에서 씁니다.

| | 로그인(OAuth) | 계정 API 키 |
|---|---|---|
| 어디에 | 고객 MCP(`https://api.gujo.ai/api/mcp`)만 | 계정 API 전체와 고객 MCP |
| 언제 | Claude Code·Codex·Cursor·VS Code·Claude.ai 처럼 사람이 쓰는 MCP 클라이언트 | CI, 서버의 스크립트, 헤드리스 에이전트, `curl` 같은 직접 호출 |
| 받는 법 | 클라이언트에 서버 주소만 넣고, 브라우저에서 로그인해 권한에 동의합니다 | 개발자 설정에서 만들고, 한 번 보이는 값을 환경 변수에 넣습니다 |
| 권한 | 동의한 권한(기본 `mcp.read`·`products.read`) | 만들 때 고른 능력 |
| 수명 | 접근 토큰 1시간, 클라이언트가 스스로 갱신(30일 쓰지 않으면 만료) | 정한 만료일까지(없으면 폐기할 때까지) |
| 끊기 | 계정 보안 설정의 **연결된 앱** | 개발자 설정의 키 목록에서 폐기 |

권한 이름과 판정은 둘이 같습니다. 아래 능력 표가 로그인 권한에도 그대로 적용됩니다. 로그인으로 받은 토큰은 고객 MCP 에서만 받으므로, 그 토큰으로 다른 계정 API 를 부르면 401 `invalid_api_key` 입니다. 연결 순서와 토큰 수명은 [MCP 연결하기](mcp/index.md)에 있습니다.

직접 MCP 클라이언트를 만든다면 표준 OAuth 흐름(MCP 2025-11-25 authorization)을 따릅니다. 시작점은 보호 리소스 메타데이터 `https://api.gujo.ai/.well-known/oauth-protected-resource/api/mcp` 와 인가 서버 메타데이터 `https://account.gujo.ai/.well-known/oauth-authorization-server` 입니다. 인가 코드 + PKCE(S256)만 받고, 앱 등록은 클라이언트 메타데이터 문서(CIMD)나 동적 등록(`POST /oauth/register`)으로 합니다. 인가·토큰 요청에는 `resource=https://api.gujo.ai/api/mcp` 를 싣습니다.

## 키의 모양

- 키는 `gujo_` 로 시작합니다.
- 서버는 키의 SHA-256 해시만 저장합니다. 원문은 만들 때 한 번만 보이고, 그 뒤에는 Gujo 도 다시 보여 줄 수 없습니다.
- 개발자 설정 화면의 키 목록에는 이름, 키 앞 14글자(접두사), 능력이 보입니다. 어느 키인지 알아볼 때 접두사를 씁니다.

## 능력

키마다 쓸 수 있는 능력을 따로 적습니다. 문마다 필요한 능력은 [API 참조](api-reference.md)의 `ability` 칸에 있고, 비어 있으면 유효한 키만 있으면 됩니다.

| 능력 | 여는 문 | 처음부터 가진 키 |
|---|---|---|
| `products.read` | 라이브러리, 상품 목록·상세, 업데이트 확인, 설치 계획, 설정 진행 조회, 상품 라이선스 | 직접 만든 키, 기기 로그인 키 |
| `orders.read` | 내 주문, 청구서, 결제 수단 | 없음(만들 때 골라서 더함) |
| `subscriptions.read` | 내 구독 | 없음(만들 때 골라서 더함) |
| `mcp.read` | 고객 MCP(`POST /api/mcp`) | 직접 만든 키 |
| `setup-progress.write` | 상품 설정 진행 기록 | 없음(만들 때 골라서 더함) |
| `device.install` | 설치 기록, 앱 배포 피드와 다운로드 | 직접 만든 키, 기기 로그인 키 |
| `device.entitlements` | 보유 상품, 라이선스 확인, Pass 확인 | 기기 로그인 키 |
| `device.activate` | 앱 일괄 활성화 | 기기 로그인 키 |
| `device.usage` | 제품 사용량 업로드 | 기기 로그인 키 |
| `support.report` | 앱 문제 신고 | 기기 로그인 키 |

- 직접 만든 키의 기본 능력은 `products.read`·`mcp.read`·`device.install` 입니다.
- gujoctl 이나 Gujo Cloud Apps 의 기기 로그인으로 받은 키는 `products.read`·`device.entitlements`·`device.activate`·`device.install`·`device.usage`·`support.report` 여섯 가지를 받습니다. `orders.read`·`subscriptions.read`·`mcp.read` 가 필요하면 개발자 설정 화면에서 키를 따로 만듭니다.
- `support.staff`·`commerce.staff`·`design.staff` 는 Gujo 운영자용 능력입니다. 일반 계정의 키에 적어도 통하지 않습니다.

## 상품 범위 키

키는 대개 계정 전체에 걸립니다. Gujo Cloud Apps 가 앱을 활성화할 때 받는 키처럼 상품 하나에 묶인 키도 있습니다.

- 상품 하나에 묶인 키로 `GET /api/library` 를 부르면 그 상품 하나만 나옵니다.
- 다른 상품의 경로를 부르면 403 `product_api_key_scope_required` 입니다.

## 한도와 만료

- 살아 있는 키는 범위마다 3개까지입니다. 계정 전체 키와 상품마다 묶인 키는 따로 셉니다.
- 직접 만들 때 자리가 없으면 발급이 거절되고 화면에 한도 안내가 나옵니다. 키 하나를 폐기하면 자리가 생깁니다.
- 기기 로그인은 자리가 없으면 같은 클라이언트의 키 가운데 가장 오래 쓰지 않은 키를 폐기하고 새 키를 받습니다.
- 직접 만든 키는 만들 때 정한 만료일(없으면 폐기할 때까지)까지 쓸 수 있습니다. 기기 로그인 키는 90일 동안 쓰지 않으면 만료되고, 쓸 때마다 그 기한이 늘어납니다.

## 폐기

- 개발자 설정 화면의 키 목록에서 언제든 폐기합니다.
- gujoctl 키는 `gujoctl logout` 이 서버에서 폐기한 뒤 기기에서 지웁니다.
- 폐기했거나 만료된 키는 다음 요청부터 401 `invalid_api_key` 를 받습니다.

키가 새었다고 생각되면 먼저 폐기하고 새 키를 만듭니다.

## 오류

인증·권한 오류의 본문은 `{"message": "...", "error": "<코드>"}` 입니다. `/api/*` 는 로그인 화면으로 보내지 않고 언제나 JSON 으로 답합니다.

| 상태 | `error` | 뜻 |
|---|---|---|
| 401 | `missing_api_key` | `Authorization` 머리글이 없습니다 |
| 401 | `invalid_api_key` | 모르는 키이거나, 폐기·만료된 키입니다 |
| 403 | `missing_api_key_ability` | 키에 그 문의 능력이 없습니다 |
| 403 | `product_entitlement_required` | 그 상품의 이용권이 없습니다 |
| 403 | `product_api_key_scope_required` | 상품 하나에 묶인 키로 다른 상품을 불렀습니다 |
| 404 | — | 대상이 없습니다 |
| 422 | — | 입력 검증 실패. 본문은 `{message, errors}` 입니다 |
| 429 | — | 속도 제한을 넘었습니다. API 전체에 분당 60회가 걸리고, 일부 문은 따로 더 좁은 제한이 있습니다 |

능력 검사는 대상을 찾기 전에 돌아갑니다. 그래서 능력이 없는 키는 그 id 가 있는지 알 수 없습니다(404 대신 403).

## 키를 다루는 법

- 키는 머리글로만 보냅니다. 주소의 쿼리나 경로에 넣지 않습니다.
- 설정 파일과 저장소에는 키 값이 아니라 환경 변수 이름만 둡니다. [MCP 연결하기](mcp/index.md)의 키로 붙이는 설정도 모두 `GUJO_API_KEY` 를 가리킵니다.
- 설치 계획이 주는 서명 다운로드 주소에는 키를 보내지 않습니다. 키는 API 호스트에만 갑니다.
- 사람이 쓰는 MCP 클라이언트라면 키 대신 로그인으로 연결합니다. 키를 설정에 둘 일이 없어집니다.
