제품에 Format 임베드하기

이 문서는 영문 원고를 AI로 번역한 내용이라 표현이 어색할 수 있습니다.

자체 고객이 있는 제품이 있습니다. 당신의 UI에 버튼을 두고, 클릭한 고객을 위해 Sume가 만든 비디오나 이미지를 만들고 싶습니다. 이 페이지는 그 end-to-end 레시피입니다.

형태는 항상 같습니다.

고객은 Sume와 대화하지 않습니다. 서버가 Sume API 키 하나를 들고, 고객을 대신해 Format을 실행하며, 결과를 자체 레코드에 매핑합니다.

invoke 계약 자체는 Format 호출하기, 결과 형태는 구조화 출력을 참고하세요. 이 페이지는 그 주변 연동입니다.

1. 키 보관

Sume 계정 하나, 서버 측 키 하나, 고객은 많습니다. Sume에는 최종 사용자별 자격 증명이 없고, 브라우저에 안전한 키도 없습니다.

RuleWhy
키는 서버 환경에 두고, 클라이언트 JavaScript, 모바일 번들, NEXT_PUBLIC_* 변수에 두지 마세요.Sume 키는 당신의 크레딧을 씁니다. 키를 가진 누구나 소유한 Format을 cap까지 실행할 수 있습니다.
키를 프록시하지 마세요. 호출을 프록시하세요.브라우저 페이로드에 키를 붙여 전달하는 "패스스루" 엔드포인트는 한 홉 뒤의 같은 유출입니다. 엔드포인트는 고객 식별자를 받아 Sume 요청을 스스로 구성해야 합니다.
엔드포인트에 자체 인가 검사를 두세요.Sume는 당신을 인증하지, 고객을 인증하지 않습니다. 고객이 Format을 실행해도 되는지는 제품의 일입니다.
새 키를 만들고 이전 키를 폐기해 교체하세요.기존 키에 스코프를 추가할 수 없습니다 — 아래를 참고하세요.

API Keys에서 formats:readformats:write 스코프로 키를 만드세요.

Format API 트리거가 출시되기 전에 만든 키에는 그 스코프가 없고, 이후에도 추가할 수 없습니다. 이전 키는 모든 run에서 403 insufficient_scope로 실패합니다. 새 키를 만드세요. 서비스 계정 키로는 Format run을 아예 만들 수 없으며 — details.reasonservice_account_format_runs_unsupported로 실패합니다.

Idempotency-Key

고객은 더블클릭합니다. Job 큐는 재전달합니다. 키가 요청 시점이 아니라 만들고 있는 것에서 유도되지 않으면, 둘 다 유료 run 두 번이 됩니다.

DoDo not
자체 안정 식별자 — tenant id, order id, Format slug, 의도적으로 재실행할 때 올리는 버전 — 을 해시하세요.요청마다 uuidv4(). 헤더를 장식으로 만듭니다.
고객으로 네임스페이스를 두세요.order id만으로 만든 키 — id가 충돌하는 두 테넌트가 run을 공유합니다.
브라우저에 응답하기 전에 반환된 run_id를 레코드에 저장하세요.나중에 키를 다시 유도해 run을 찾는 데만 의존하세요. 가능하지만, 저장된 id는 재전송 한 번이 아니라 조회 한 번입니다.

재전송 의미, 정확히:

ReplayResult
같은 키, 같은 본문원래 run receipt와 idempotency_hit: true200. 두 번째 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를 담습니다.

WebhookPoll
You docommunication.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-signaturesume-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는 미디어를 담는 필드가 세 개입니다.

FieldUse it for
primary_output_url보여줄 하나입니다. Format이 단일 primary 파일을 만들지 않으면 null입니다.
artifacts[]run이 만든 모든 것: { id, type, url, content_type, size_bytes, width, height, duration_ms, checksum_sha256 }.
outputoutput_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는 각각에 다른 메시지가 필요합니다. "무언가 잘못됐습니다"로 합치면 답할 수 없는 지원 티켓이 가장 빨리 생깁니다.

제출 시 — 아무것도 실행되지 않았고, 청구도 없습니다

CodeStatusWhat it means for your integration
insufficient_scope403키에 formats:read / formats:write가 없거나 서비스 계정 키입니다. 요청이 아니라 키를 고치세요.
format_not_found404알 수 없는 handle·slug이거나, 이 키가 소유하지 않은 Format입니다. vanity 경로에서 1st-party Format이 반환하는 값이기도 합니다.
format_not_forkable409Format 카드가 아니라 내장 기능을 주소로 잡았습니다. Formats by Sume 또는 직접 만든 Format을 호출하세요.
format_api_trigger_disabled409그 Format의 API 트리거가 꺼져 있습니다.
format_inactive409Format이 비활성입니다.
format_run_in_progress409on_active_run: "reject"일 때만. 나중에 재시도하거나 "이미 실행 중"을 표시하세요.
idempotency_conflict409같은 키, 다른 본문. 키 유도가 불안정합니다 — 재시도하기 전에 그것을 고치세요.
invalid_request400공개 HTTPS URL이 아닌 webhook_url을 포함합니다.

여기의 4xx는 일시적 오류가 아니라 호출의 버그로 취급하세요. insufficient_scope를 영원히 재시도하는 것은 흔하고 비싼 실수입니다.

run 시 — run은 존재했고, 결과를 만들지 못했습니다

statusfailed이고, receipt는 erroroutput_error를 담습니다. 특히:

error.codeMeaning
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이 완료로 표기되어 넘어오지 않습니다.

종료이지만 실패가 아님

statusHandle it as
canceled누군가가 POST /v1/format-runs/{id}/cancel을 호출했습니다. 웹훅 없음 — 취소 응답과 status_url 폴링을 사용하세요.
skippedon_active_run: "skip"을 보냈고 이미 진행 중인 run이 있었습니다. skipped run은 웹훅을 전달하지 않습니다 — create 응답이 이미 skip_reason과 함께 알려 줍니다. POST를 기다리지 말고 받은 응답의 상태를 읽으세요. Format run은 기본적으로 동시 실행이 허용됩니다.

전달 시 — run은 괜찮고, 엔드포인트가 아니었습니다

전달 결과는 run을 바꾸지 않습니다. 열 번 거절당해도 전달은 실패하고 run은 여전히 completed입니다. result_url에서 가져오세요.

코드가 필요한 전달 케이스 하나: 1 MiB를 넘는 receipt는 payload: nullerror.codepayload_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 폴링이 여전히 연결되어 있습니다.
  • 위 모든 오류 코드가 지원팀이 조치할 수 있는 메시지에 매핑됩니다.

다음