인증 및 scope
platform API는 조직의 key로 인증합니다. 두 가지 형태가 있습니다 — 같은 org, 같은 scopes, 다른 runtime:
| Form | Credential | Sent as |
|---|---|---|
| Server (backend) | kld_sk_live_… | Authorization: Bearer … 또는 X-Api-Key |
| Publishable (browser) | kld_pk_live_… | SDK가 단기 session으로 교환하며 raw bearer로 직접 전송하지 않음 |
Authorization: Bearer kld_sk_live_…
# or
X-Api-Key: kld_sk_live_…
Server key는 backend에서 각 request마다 보내는 bearer입니다.
Publishable key는 browser용입니다. SDK가 runtime에 단기
origin-bound session token으로 교환하므로 publishable key 자체가
page source에 지속적인 credential로 남지 않습니다. publishable key를 직접
bearer로 보내면 거부됩니다 — SDK를 통해 사용하세요. server key에는 CORS
grant가 없으므로 page가 server key로 만든 response를 읽을 수 없습니다 — 하지만 CORS는
request가 browser 밖으로 나가는 것 자체를 막지 못하므로 page
source에 server key를 넣는 순간 이미 유출된 것입니다. API가 거부할 때는 이미 늦습니다.
SDK가 mount 시 kld_sk_… key를 거부하는 이유도 정확히 이것입니다. server key는 항상
server-side에 보관하세요.
기존 legacy kld_live_… key는 두 위치 모두에서 direct bearer로 계속 작동합니다.
Scopes
key에는 capability scope가 있으며 각 route family는 하나를 요구합니다:
| Scope | Route family | Used by |
|---|---|---|
ai | /inference-api/b2b/v1/chat/*, /retrieval/* | chat embed |
design | /inference-api/b2b/v1/design/* | editor embed |
maps | designed basemaps | the tile embed |
vendor | (modifier, not a route) | the chat embed, on your own data |
기본적으로 Pro/Enterprise key는 ai, design, maps를 포함해 mint됩니다.
Free-plan key는 maps + tile product로 제한됩니다. 공식
plan × scope × product 표는
API 키 받기를 참조하세요.
Effective admission = stored key × current tier. Runtime session-exchange는
매 call마다 admission을 다시 계산하므로 Pro-tier key가 Free로 downgrade되면
그 시점부터 tile session만 mint합니다 — stored key가 여전히
ai와 design 제한을 가지고 있어도 같습니다. rejection subcode는
두 경우를 구분합니다:
insufficient_scope— key가 해당 capability를 한 번도 가진 적이 없음.tier_capability_not_allowed— key가 가지고 있었지만 current tier가 더 이상 허용하지 않음. 복원하려면 plan을 upgrade하세요.
stored key의 빈 allowed_products는 모든 product를 의미합니다
(none이 아님). tier는 이 superset을 plan이 허용하는 범위로 다시 좁힙니다.
vendor — 의도적으로 요청하고 필요한 곳에서만 사용
vendor는 route gate가 아닙니다. request의 허용/거부를 결정하는 대신,
AI chat이 답변할 때 조직이 upload한 vendor data를 참조할 수 있는지 결정합니다.
다른 모든 scope가 "이 key가 이 endpoint를 호출할 수 있는가"를 답한다면 vendor는
"이 key가 내부 데이터를 바탕으로 말할 수 있는가"를 답합니다.
따라서 기본적으로 포함되지 않습니다 — mint 시 명시적으로 요청하세요:
{ "name": "our-site-widget", "scopes": ["ai", "vendor"] }
어떤 key가 vendor를 가지는지가 전체 보안 결정입니다. chat embed는
browser widget입니다. publishable key를 단기 session으로 교환하므로
vendor-enabled key는 필연적으로 public page 안에 있게 됩니다. property list, 운영 시간,
공개 rate처럼 모든 방문자에게 보여도 되는 data라 면 괜찮습니다.
공개하고 싶지 않은 정보에는 적합하지 않습니다. exchange를 보호하는 origin
allowlist는 browser convention이지 confidentiality boundary가 아닙니다.
자체 Origin header를 설정할 수 있는 caller는 이것으로 막을 수 없습니다.
따라서:
- surface당 하나의 key. Marketing site, demo page, customer-facing
app이 같은 key를 공유해서는 안 됩니다. 자체 data로 답해야 하는 surface에만
vendor를 부여하세요. - rate sheet, cost basis 또는 미공개 정보를 publishable key 뒤에 두지 마세요. public page에 나오면 곤란한 답이라면 해당 data는 vendor-scoped embed에 속하지 않습니다.
- Session은 product에 맞게 좁혀집니다. 따라서 parent key가
vendor를 갖더라도tile또는viewersession은vendor를 가지지 않습니다.
철회하려면 key scopes를 편집하거나 revoke하세요 — 두 변경 모두 다음 request부터 적용되며 이미 실행 중인 session에도 적용됩니다. session expiry까지 지연되지 않습니다.
401 vs 403
의도적으로 구분됩니다:
- 401 Unauthorized — key 없음 / invalid / revoked / expired. key 값을 다시 확인하고
revoke되지 않았는지 확인하세요. publishable key가 raw bearer로 전송된 경우의
publishable_requires_session도 포함됩니다. - 403 Forbidden — key는 존재하지만 여기서는 허용되지 않음. 일반적인
subcode:
insufficient_scope(처음부터 없음),tier_capability_not_allowed(있었지만 tier가 더 이상 허용하지 않음),session_origin_mismatch,ip_not_allowed(server key),server_key_in_browser,product_not_allowed.
둘 다 fail closed입니다. scope가 없는 key는 모든 곳에서 거부됩니다.
Session token
SDK가 publishable key와 교환하는 session은 다음과 같습니다:
kld_sess_{env}_{jwt}
예: kld_sess_live_eyJhbGciOi…. runtime call에서는
Authorization: Bearer 또는 X-Api-Key로 전달합니다. Default TTL 900초(15
분); server max는 30분입니다. session은 mint된 product로 좁혀지고,
모든 request는 parent key의 live policy를 다시 읽습니다.
철회는 즉시 적용되지만 새로운 grant는 그렇지 않습니다. 두 방향은 의도적으로 비대칭이며 fail closed입니다:
| Change to the parent key | Effect on a session already in flight |
|---|---|
| Revoked or deleted | 다음 request에서 거부 |
| A scope removed | 다음 request에서 제거 |
| Plan downgraded | tier-gated capability는 다음 request에서 거부 |
| Rate limit or monthly cap set to a new value | 다음 request에서 적용 |
| A scope added, plan upgraded, or a limit lifted entirely | 보이지 않음 — 새 session mint 필요 |
session은 parent보다 권한을 좁힐 수만 있고 mint된 scope를 넘어 넓힐 수 없습니다. 따라서 새 grant에는 새 exchange가 필요합니다. session이 짧은 것(default 15분)은 그 간격을 작게 유지하기 위해서입니다.
전체 status table은 Errors를 참조하세요.