제품에 Format 임베드하기
이 문서는 영문 원고를 AI로 번역한 내용이라 표현이 어색할 수 있습니다.
자체 고객이 있는 제품이 있습니다. 당신의 UI에 버튼을 두고, 클릭한 고객을 위해 Sume가 만든 비디오나 이미지를 만들고 싶습니다. 이 페이지는 그 end-to-end 레시피입니다.
형태는 항상 같습니다.
고객은 Sume와 대화하지 않습니다. 서버가 Sume API 키 하나를 들고, 고객을 대신해 Format을 실행하며, 결과를 자체 레코드에 매핑합니다.
invoke 계약 자체는 Format 호출하기, 결과 형태는 구조화 출력을 참고하세요. 이 페이지는 그 주변 연동입니다.
1. 키 보관
Sume 계정 하나, 서버 측 키 하나, 고객은 많습니다. Sume에는 최종 사용자별 자격 증명이 없고, 브라우저에 안전한 키도 없습니다.
| Rule | Why |
|---|---|
키는 서버 환경에 두고, 클라이언트 JavaScript, 모바일 번들, NEXT_PUBLIC_* 변수에 두지 마세요. | Sume 키는 당신의 크레딧을 씁니다. 키를 가진 누구나 소유한 Format을 cap까지 실행할 수 있습니다. |
| 키를 프록시하지 마세요. 호출을 프록시하세요. | 브라우저 페이로드에 키를 붙여 전달하는 "패스스루" 엔드포인트는 한 홉 뒤의 같은 유출입니다. 엔드포인트는 고객 식별자를 받아 Sume 요청을 스스로 구성해야 합니다. |
| 엔드포인트에 자체 인가 검사를 두세요. | Sume는 당신을 인증하지, 고객을 인증하지 않습니다. 이 고객이 그 Format을 실행해도 되는지는 제품의 일입니다. |
| 새 키를 만들고 이전 키를 폐기해 교체하세요. | 기존 키에 스코프를 추가할 수 없습니다 — 아래를 참고하세요. |
API Keys에서 formats:read와
formats:write 스코프로 키를 만드세요.
Format API 트리거가 출시되기 전에 만든 키에는 그 스코프가 없고, 이후에도
추가할 수 없습니다. 이전 키는 모든 run에서 403 insufficient_scope로 실패합니다.
새 키를 만드세요. 서비스 계정 키로는 Format run을 아예 만들 수 없으며 —
details.reason이 service_account_format_runs_unsupported로 실패합니다.
Idempotency-Key
고객은 더블클릭합니다. Job 큐는 재전달합니다. 키가 요청 시점이 아니라 만들고 있는 것에서 유도되지 않으면, 둘 다 유료 run 두 번이 됩니다.
| Do | Do not |
|---|---|
| 자체 안정 식별자 — tenant id, order id, Format slug, 의도적으로 재실행할 때 올리는 버전 — 을 해시하세요. | 요청마다 uuidv4(). 헤더를 장식으로 만듭니다. |
| 고객으로 네임스페이스를 두세요. | order id만으로 만든 키 — id가 충돌하는 두 테넌트가 run을 공유합니다. |
브라우저에 응답하기 전에 반환된 run_id를 레코드에 저장하세요. | 나중에 키를 다시 유도해 run을 찾는 데만 의존하세요. 가능하지만, 저장된 id는 재전송 한 번이 아니라 조회 한 번입니다. |
재전송 의미, 정확히:
| Replay | Result |
|---|---|
| 같은 키, 같은 본문 | 원래 run receipt와 idempotency_hit: true인 200. 두 번째 run도, 두 번째 청구도 없습니다. |
같은 키, 다른 본문 — 다른 instruction 포함 | 409 idempotency_conflict. 아무것도 실행되지 않습니다. |
| 키 없음 | 매 호출이 새 유료 run을 시작합니다. |
3. run마다 spend cap 고르기
모든 Format에는 generation spend cap이 있습니다. run은 자신의 유효 cap을 넘길 수 없고, Format의 cap은 run이 따로 지정하지 않았을 때 물려받는 값입니다.
run 요청의 generation_spend_cap_usd는 플랫폼 최대치 $500까지 그 run만의 천장을
지정합니다 — Format 자체의 cap보다 큰 값도 적용되며, $500을 넘으면 400입니다.
그래서 cap은 자체 플랜 티어를 표현하기 자연스러운 자리입니다.
Format 자체의 cap은 PublicFormat.generation_spend_cap_usd_micros에서 읽으세요 —
항상 숫자이며, 한 번도 지정하지 않은 Format은 $400이 기본입니다. 해당 run의 유효
cap은 receipt의 usage.generation_spend_cap_usd_micros로 돌아옵니다.
cap은 generation spend를 묶습니다. 종료 receipt는 그 천장에 대해 run이 실제로
쓴 금액을 usage.billable_amount_usd_micros로 보고하며, 자체 UI에 run당 비용을
보여 주기에는 충분합니다 — 다만 Agent 자체의 LLM 턴은 제외되므로 run의 총비용도
청구서도 아닙니다. 고객 청구는 자체 기록에서 하고
GET /v1/usage로 대조하세요.
4. 결과 받기
Format run은 비동기입니다. 끝났음을 아는 방법은 두 가지이며, 동일한 receipt를 담습니다.
| Webhook | Poll | |
|---|---|---|
| You do | communication.webhook_url을 등록하고, 서명을 검증한 뒤 2xx를 반환하세요. | 종료될 때까지 status_url을 루프하세요. |
| Available | 환경별로 롤아웃 중이며, api.sume.com에는 아직 없습니다. | 어디에나, 오늘. |
| Costs you | 공개 HTTPS 엔드포인트 하나. | 진행 중인 run마다 타이머 하나. |
웹훅 수신기를 지금 만드세요 — 계약은 최종이며, 오늘 넘긴 URL은 환경에 전달이 켜지는 날 POST를 받기 시작합니다. 그때까지는 폴링 경로를 폴백으로 유지하세요. 전체 계약: Run 웹훅.
모든 전달을 검증하세요
Sume는 <timestamp>.<raw_body>에 대한 HMAC-SHA256으로 raw body에 서명하고
x-sume-webhook-signature에 sume-v1=<hex>를 보냅니다. 파싱하기 전에
검증하세요.
서명 시크릿은 대시보드의 웹훅 탭(/dashboard/webhooks)에서 확인하거나,
account:read 범위를 가진 API 키로 GET /v1/webhooks/signing-secret을 호출해
받으세요. 워크스페이스별로 파생된 값이므로, 서명이 유효하다는 것은 공용 시크릿을
가진 누군가가 아니라 여러분에게 서명되었다는 뜻입니다. 전달 워커가 서명할 때 쓰는
것과 같은 이름인 SUME_COM_WEBHOOK_SIGNING_SECRET으로, API 키와 같은 방식으로
저장하세요.
연동자를 잡는 네 가지:
- raw body에 대해 검증하세요. JSON을 파싱해 객체를 넘기는 프레임워크는 이미
서명된 바이트를 파괴했습니다. Express에서는 이 라우트에만
express.raw({ type: "application/json" })를 마운트하세요. - 빠르게
2xx를 반환한 뒤 작업하세요. 전달 시도 예산은 10초입니다. 응답 전에 비디오를 렌더하는 수신기는 작업하는 동안 재시도됩니다. request_id로 중복 제거하세요. 재시도가 그것을 반복합니다. 불안정한 엔드포인트에 대한 열 번의 시도가 데이터베이스에 열 행이 되면 안 됩니다.3xx는 전달이 아닙니다. 리다이렉트는 따르지 않습니다. 리다이렉터가 아니라 최종 URL을 등록하고, HTTP도 안 됩니다 — 비 HTTPS, localhost, 사설 대역 URL은 제출 시400 invalid_request로 거절되고 전달 시에도 다시 검사됩니다.
Job 웹훅은 다른 표면입니다
POST /v1/models/...를 직접 호출한다면, 그것들은 job_id가 있는 generation-job
웹훅(job.completed 등)을 내보냅니다. 웹훅에 설명되어
있습니다. 이벤트도, 페이로드도, 수명주기도 다릅니다.
서명 방식은 같으므로 검증기 하나로 둘 다 커버합니다 — 다만 event로 라우팅하고
본문에 run_id가 있다고 가정하지 마세요. 둘을 처리하는 단일 수신기는 먼저 이벤트
이름으로 switch하고, 인식하지 못한 것은 204로 취급해 새 이벤트 유형이 500과
재시도 폭풍이 되지 않게 하세요.
5. artifact를 UI에 매핑하기
종료 completed receipt는 미디어를 담는 필드가 세 개입니다.
| Field | Use it for |
|---|---|
primary_output_url | 보여줄 하나입니다. Format이 단일 primary 파일을 만들지 않으면 null입니다. |
artifacts[] | run이 만든 모든 것: { id, type, url, content_type, size_bytes, width, height, duration_ms, checksum_sha256 }. |
output | output_schema에 투영된 Format의 구조화 결과입니다. 안의 미디어는 같은 URL을 가리킵니다. 구조화 출력을 참고하세요. |
모든 URL은 내구성 있는 media.sume.com HTTPS URL입니다. 만료되지 않으며, 그래서
임베드가 실용적입니다 — 레코드에 URL을 저장하고 refresh 없이 영원히 렌더할 수
있습니다.
설계에 넣을 만한 결과 두 가지:
- 내구성 URL은 공개 URL입니다. 가진 사람은 누구나 fetch할 수 있습니다. 로그, 오류 리포트, 고객 브라우저 기록에 남습니다. 제품 모델이 고객 A가 고객 B의 출력을 보면 안 되는 것이라면, 자체 인증 라우트로 바이트를 프록시하거나 receipt 시점에 자체 스토리지로 복사해 거기서 서빙하세요.
- 복사할지, 링크할지 결정하세요. 링크는 무료이고 즉시입니다. 복사는 스토리지가 들지만 Sume를 떠나도 남습니다. 그 보장이 필요하면 웹훅에서, 레코드를 ready로 표시하기 전에 복사하세요.
artifacts[]는 run이 종료될 때까지 비어 있으며, output을 채우는 같은 job
원장에서 가져옵니다 — 둘은 항상 일치합니다.
6. 실패 taxonomy
run은 구분 가능한 네 곳에서 실패합니다. UI는 각각에 다른 메시지가 필요합니다. "무언가 잘못됐습니다"로 합치면 답할 수 없는 지원 티켓이 가장 빨리 생깁니다.
제출 시 — 아무것도 실행되지 않았고, 청구도 없습니다
| Code | Status | What it means for your integration |
|---|---|---|
insufficient_scope | 403 | 키에 formats:read / formats:write가 없거나 서비스 계정 키입니다. 요청이 아니라 키를 고치세요. |
format_not_found | 404 | 알 수 없는 handle·slug이거나, 이 키가 소유하지 않은 Format입니다. vanity 경로에서 1st-party Format이 반환하는 값이기도 합니다. |
format_not_forkable | 409 | Format 카드가 아니라 내장 기능을 주소로 잡았습니다. Formats by Sume 또는 직접 만든 Format을 호출하세요. |
format_api_trigger_disabled | 409 | 그 Format의 API 트리거가 꺼져 있습니다. |
format_inactive | 409 | Format이 비활성입니다. |
format_run_in_progress | 409 | on_active_run: "reject"일 때만. 나중에 재시도하거나 "이미 실행 중"을 표시하세요. |
idempotency_conflict | 409 | 같은 키, 다른 본문. 키 유도가 불안정합니다 — 재시도하기 전에 그것을 고치세요. |
invalid_request | 400 | 공개 HTTPS URL이 아닌 webhook_url을 포함합니다. |
여기의 4xx는 일시적 오류가 아니라 호출의 버그로 취급하세요. insufficient_scope를
영원히 재시도하는 것은 흔하고 비싼 실수입니다.
run 시 — run은 존재했고, 결과를 만들지 못했습니다
status는 failed이고, receipt는 error와 output_error를 담습니다. 특히:
error.code | Meaning |
|---|---|
unattended_blocked | 사람 없이 만족할 수 없는 게이트에 걸렸습니다 — 맞는 아바타 없음, 채팅이라면 물었을 누락된 입력. 메시지는 보여 주도록 쓰여 있습니다. |
format_run_failed | 일반 실패입니다. error.message를 읽으세요. cap을 넘겨 쓰려던 run도 여기로 오므로, 계속 실패하는 플랜 티어는 먼저 usage.generation_spend_cap_usd_micros와 대조하세요. |
run 실패는 새 멱등성 키로 재시도하세요 — 이전 키는 이미 실패한 run에 묶여 있고, 재사용하면 같은 실패 receipt가 돌아옵니다.
API run은 unattended입니다. 대화형 채팅용 Format은 사람의 승인을 기다리며
멈출 수 있고, API에서는 그 승인이 미리 허용되어 spend cap 안에서 계속합니다.
그래서 completed는 실제 결과입니다 — 절반만 끝난 run이 완료로 표기되어 넘어오지
않습니다.
종료이지만 실패가 아님
status | Handle it as |
|---|---|
canceled | 누군가가 POST /v1/format-runs/{id}/cancel을 호출했습니다. 웹훅 없음 — 취소 응답과 status_url 폴링을 사용하세요. |
skipped | on_active_run: "skip"을 보냈고 이미 진행 중인 run이 있었습니다. skipped run은 웹훅을 전달하지 않습니다 — create 응답이 이미 skip_reason과 함께 알려 줍니다. POST를 기다리지 말고 받은 응답의 상태를 읽으세요. Format run은 기본적으로 동시 실행이 허용됩니다. |
전달 시 — run은 괜찮고, 엔드포인트가 아니었습니다
전달 결과는 run을 바꾸지 않습니다. 열 번 거절당해도 전달은 실패하고 run은 여전히
completed입니다. result_url에서 가져오세요.
코드가 필요한 전달 케이스 하나: 1 MiB를 넘는 receipt는 payload: null과
error.code가 payload_too_large로 도착하며, 대신 가져올 result_url을 담습니다.
payload가 객체라고 가정하는 핸들러는 가장 크고 가치 있는 run에서 throw합니다.
출시 전 체크리스트
-
SUME_API_KEY는 서버 전용이며 모든 클라이언트 번들에 없습니다. - run 엔드포인트는 Sume를 호출하기 전에 자체 고객을 인가합니다.
-
Idempotency-Key는 요청마다 생성하지 않고 안정 식별자에서 유도합니다. -
generation_spend_cap_usd는 플랜 티어마다 설정됩니다. - 웹훅 수신기는 raw body에 대해
sume-v1을 검증하고 1초 안에2xx를 반환합니다. - 전달은
request_id로 중복 제거됩니다. -
payload: null(과도한 크기 receipt)은result_url로 폴백합니다. - 프로덕션에 전달이 아직 없으므로
status_url폴링이 여전히 연결되어 있습니다. - 위 모든 오류 코드가 지원팀이 조치할 수 있는 메시지에 매핑됩니다.