오류
Nodaro TypeScript SDK가 발생시키는 모든 오류의 HTTP 상태, 코드, 필드와 함께 크레딧, 요청 한도, 충돌, 실패한 작업을 처리하는 방법을 안내합니다.
Nodaro SDK가 API 응답에 대해 발생시키는 모든 오류는 NodaroError 또는 그 하위 클래스의 인스턴스입니다. 오류 상태로 실패한 요청은 그 상태에 맞는 하위 클래스를 발생시킵니다. 실행 후 대기하는 헬퍼는 작업이 실패하거나, 시간이 초과되거나, 중지되면 자체 하위 클래스를 발생시킵니다. 구체적인 클래스를 먼저 처리하고 NodaroError는 마지막에 처리하세요.
모든 오류 클래스
| 클래스 | 상태 | code | 추가 필드 | 발생 시점 |
|---|---|---|---|---|
NodaroError | 모두 | 서버의 코드 | 기본 클래스이며, 더 구체적인 클래스가 없는 모든 오류 | |
UnauthorizedError | 401 | unauthorized | 토큰이 없거나, 만료되었거나, 유효하지 않음 | |
ForbiddenError | 403 | forbidden | missingScope? | 권한이 거부되었거나, OAuth 토큰에 스코프가 없음 |
NotFoundError | 404 | not_found | 항목이 없거나, 내가 볼 수 없음 | |
RateLimitedError | 429 | rate_limited | 요청이 너무 많음 | |
InsufficientCreditsError | 402 | insufficient_credits | required?, available? | 계정이 실행 비용을 지불할 수 없음 |
StorageExceededError | 413 | storage_exceeded | limitBytes? | 계정의 저장 공간이 가득 참 |
WorkflowConflictError | 409 | workflow_conflict 또는 production_busy | currentUpdatedAt?, currentVersion?, currentRecord? | 다른 사람이 먼저 항목을 변경함 |
JobBlockedError | 422 | job_blocked | 배포 환경의 콘텐츠 정책이 요청을 거부함 | |
StudioOpError | 4xx | 서버의 코드 | opIndex | 스튜디오 일괄 처리의 오퍼레이션 하나가 거부됨 |
JobFailedError | 0 | job_failed | jobId, jobStatus | 기다리던 작업이 실패했거나 취소됨 |
JobTimeoutError | 0 | job_timeout | jobId, timeoutMs | 작업이 maxMs 안에 끝나지 않음 |
JobAbortedError | 0 | job_aborted | jobId? | 기다리는 동안 내 AbortSignal이 실행됨 |
JobHeldError | 0 | job_held | jobId | 작업이 사람의 검토를 위해 보류됨 |
StudioPreviewUnavailable | 0 | studio_preview_unavailable | 배포 환경이 스튜디오 일괄 처리를 미리 볼 수 없음 | |
StudioPreviewAppliedError | 0 | studio_preview_applied | applied | 미리 보기를 요청한 스튜디오 일괄 처리가 적용됨 |
모든 클래스에는 필드가 3개 있습니다. 읽을 수 있는 문장인 message, 비교할 수 있는 고정 문자열인 code, HTTP 상태인 status입니다. status가 0이면 오류가 HTTP 응답에서 나온 것이 아니라는 뜻입니다. 예를 들어 JobTimeoutError는 SDK 자체의 폴링 루프가 발생시킵니다.
일부 상태는 서버가 보낸 코드와 상관없이 하나의 클래스로 대응됩니다. 403은 code가 forbidden인 ForbiddenError가 되고, 404는 NotFoundError가 됩니다. 이런 경우 서버의 사유는 message에서 읽으세요. 409는 코드가 workflow_conflict나 production_busy일 때만 WorkflowConflictError가 됩니다. 400이나 503 같은 다른 상태의 오류와 다른 코드를 가진 409는 서버 자체의 code를 담은 NodaroError로 전달됩니다.
순서대로 오류 처리하기
import {
ForbiddenError,
InsufficientCreditsError,
NodaroError,
NotFoundError,
RateLimitedError,
StorageExceededError,
UnauthorizedError,
} from "@nodaro/sdk"
try {
await client.workflows.run(workflowId)
} catch (err) {
if (err instanceof UnauthorizedError) {
redirectToLogin()
} else if (err instanceof ForbiddenError) {
if (err.missingScope) requestAdditionalScopes([err.missingScope])
else showError("You do not have permission to do this.")
} else if (err instanceof InsufficientCreditsError) {
showCreditPaywall({ required: err.required, available: err.available })
} else if (err instanceof RateLimitedError) {
await retryWithBackoff()
} else if (err instanceof StorageExceededError) {
showError(`Storage limit of ${err.limitBytes} bytes reached.`)
} else if (err instanceof NotFoundError) {
showError("Not found.")
} else if (err instanceof NodaroError) {
console.error(`API error ${err.status} (${err.code}): ${err.message}`)
} else {
throw err // a network failure or a timeout, not an API answer
}
}클라이언트의 timeoutMs를 넘긴 요청과 네트워크 오류는 AbortError나 TypeError 같은 런타임 자체의 오류로 거부됩니다. 이 오류들은 NodaroError 인스턴스가 아닙니다.
인증과 권한
UnauthorizedError
HTTP 401입니다. 토큰이 없거나, 만료되었거나, 유효하지 않습니다. 새 토큰을 받거나 사용자를 다시 로그인하게 한 다음 다시 시도하세요.
ForbiddenError
HTTP 403입니다. 호출자가 이 작업을 할 수 없습니다. 엔드포인트에 필요한 스코프가 OAuth 토큰에 승인되지 않았으면, missingScope가 workflows:execute처럼 그 스코프를 알려 줍니다. 사용자에게 승인을 요청한 다음 새 토큰으로 다시 시도하세요. 스코프와 누락된 권한을 참조하세요.
그 밖에 에디션이 기능을 제공하지 않거나 역할의 권한이 부족한 경우도 있습니다. 모두 code가 forbidden으로 전달되므로, 어떤 사유인지 설명하려면 message를 표시하세요.
NotFoundError
HTTP 404입니다. 항목이 없거나, 이 호출자에게 보이지 않습니다. Nodaro는 두 경우에 똑같이 응답하므로, 내가 볼 수 없는 항목이 존재하는지 여부가 ID로 드러나지 않습니다. 스튜디오 프로덕션이나 Recast처럼 Nodaro Cloud에만 있는 리소스도 셀프 호스팅 설치에서는 404로 응답합니다.
크레딧, 저장 공간, 한도
InsufficientCreditsError
HTTP 402입니다. 계정이 실행 비용을 지불할 수 없어서 아무것도 시작되지 않았습니다. required는 실행에 필요한 크레딧이고, available은 계정에 있는 크레딧입니다. 둘 다 Nodaro Cloud가 설정하지만, 타입에는 선택 항목으로 표시되어 있습니다.
try {
await client.nodes.runAndWait("generate-video", params)
} catch (err) {
if (err instanceof InsufficientCreditsError) {
console.log(`Need ${err.required} credits, have ${err.available}`)
}
}실행 전에 client.credits.balance()로 잔액을 확인하세요. 크레딧을 참조하세요.
StorageExceededError
HTTP 413입니다. 계정이 저장 공간 한도에 도달했으며, limitBytes가 그 한도입니다. 업로드와 커뮤니티 복제처럼 내 저장 공간으로 복사하는 작업에서 발생합니다. 더 이상 필요 없는 미디어를 삭제한 다음 다시 시도하세요.
RateLimitedError
HTTP 429입니다. 요청을 너무 많이 보냈습니다. 기다린 다음, 대기 시간을 늘려 가며 다시 시도하세요. 예를 들어 2초, 4초, 8초 순으로 기다립니다. 몇 번 시도한 뒤에는 멈추세요. 요청 한도를 참조하세요.
작업 기다리기
client.nodes.runAndWait(), client.nodes.runMany(), 그리고 다른 리소스의 ...AndWait 헬퍼는 작업이 끝날 때까지 폴링합니다. 기다리는 동안 다음 오류가 발생할 수 있습니다.
JobFailedError
작업이 failed 또는 cancelled 상태로 끝났습니다. jobStatus가 어느 쪽인지 알려 주고, jobId가 작업을 가리키며, message에 작업 자체의 오류 메시지가 담겨 있습니다. client.jobs.get(err.jobId)로 작업 전체를 읽으세요. 그 error_hint가 안전 필터나 정책에 따른 차단을 설명합니다.
JobTimeoutError
작업이 기본값 15분인 maxMs 안에 종료 상태에 도달하지 못했습니다. 작업은 취소되지 않습니다. 대개 서버에서 계속 실행되어 완료되고 내 라이브러리에 저장됩니다. 나중에 client.jobs.get(err.jobId)로 가져오거나, 느린 모델에는 더 큰 maxMs를 전달하세요. 플랫폼이 복구 중인 작업은 상태에 recovering: true를 표시하며, 복구에는 수십 분이 걸릴 수 있습니다.
JobAbortedError
내 AbortSignal이 실행되었습니다. SDK는 즉시 폴링을 멈춥니다. 작업은 취소되지 않습니다. 서버에서 작업을 중지하고 예약된 크레딧을 환불받으려면 client.jobs.cancel(err.jobId)를 호출하세요.
JobHeldError
작업이 pending_review 상태에 도달했습니다. 이 배포 환경의 콘텐츠 정책이 사람이 검토하도록 결과를 보류했습니다. SDK는 이 상태를 처음 확인한 폴링에서 기다리기를 멈춥니다. 작업은 취소되지 않으며, 검토하는 동안 크레딧은 예약된 상태로 유지됩니다. 중복 요청도 보류되므로 요청을 다시 실행하지 마세요.
나중에 client.jobs.get(err.jobId)로 작업을 확인하세요. 검토자가 승인하면 completed, 검토자가 거부하면 failed, 내가 취소하면 cancelled가 됩니다. 거부된 작업에는 error_hint.kind가 policy-block으로 설정되어 있고, 그대로 표시할 수 있는 reason이 들어 있습니다. 이 오류는 작업 정책을 등록한 배포 환경에서만 발생합니다.
JobBlockedError
코드가 job_blocked인 HTTP 422입니다. 이 배포 환경의 콘텐츠 정책이 요청을 실행하기 전에 거부했습니다. 작업은 만들어지지 않았고 비용도 청구되지 않았습니다. message는 내 사용자를 위해 작성되었으므로 그대로 표시하세요. 같은 요청을 다시 시도하지 마세요. 이 오류는 작업 정책을 등록한 배포 환경에서만 발생합니다.
동시 변경
WorkflowConflictError
HTTP 409입니다. 조건부 변경을 했는데, 다른 사람이 먼저 항목을 변경했습니다. 다음 두 코드 중 하나로 전달됩니다.
workflow_conflict:expectedVersion이나expectedUpdatedAt을 넣은client.workflows.update()가 저장된 워크플로와 일치하지 않았습니다.production_busy: 서버가 내 변경을 적용하는 동안 스튜디오 프로덕션이 계속 바뀌어서, 서버가 재시도를 멈췄습니다.
해결 방법은 둘 다 같습니다. 항목을 다시 읽고, 새 사본에 변경 내용을 적용한 다음, 다시 보내세요. 서버가 포함해 보낸 경우에는 currentRecord에 현재 워크플로가 들어 있으므로, 다시 읽지 않고 병합할 수 있습니다.
import { WorkflowConflictError } from "@nodaro/sdk"
try {
await client.workflows.update(id, { settings, expectedVersion: loadedVersion })
} catch (err) {
if (err instanceof WorkflowConflictError && err.currentRecord) {
const merged = mergeSettings(err.currentRecord.settings, settings)
await client.workflows.update(id, { settings: merged, expectedVersion: err.currentVersion })
} else {
throw err
}
}장소와 오브젝트는 자체 충돌 코드인 concurrent_modification을 사용하며, 이 코드는 일반 NodaroError로 전달됩니다. 같은 방법으로 처리하세요. 항목을 다시 읽고, 병합하고, 다시 시도합니다.
스튜디오 일괄 처리
다음 세 클래스는 스튜디오 프로덕션에 속합니다.
StudioOpError: 오퍼레이션 묶음이 거부되었으며,opIndex는 원인이 된 오퍼레이션의 0부터 시작하는 위치입니다. 묶음의 어떤 내용도 기록되지 않았습니다. 그 오퍼레이션을 고친 다음 묶음 전체를 다시 보내세요.StudioPreviewUnavailable:dryRun: true로 미리 보기를 요청했지만, 이 배포 환경은 미리 보기를 제공할 수 없습니다. 묶음은 전송되지 않았습니다. 사용자에게 미리 보기를 사용할 수 없다고 알리고, 묻지 않은 채 묶음을 적용하지 마세요.StudioPreviewAppliedError: 미리 보기를 요청했는데 묶음이 그대로 적용되었습니다.applied.production과applied.version을 현재 상태로 취급하세요. 묶음을 다시 보내지 마세요.applied가undefined이면 무엇이든 결정하기 전에 프로덕션을 다시 읽으세요.
NodaroError에서 볼 수 있는 코드
다음 코드는 일반 NodaroError로 전달됩니다. 처리하려면 err.code와 비교하세요.
| 상태 | 코드 | 위치 | 의미 |
|---|---|---|---|
| 400 | validation_error | 여러 메서드 | 필드가 없거나 유효하지 않습니다. message가 그 필드를 알려 줍니다. |
| 400 | no_valid_inputs | client.reduce.run() | 모든 입력이 비어 있습니다. |
| 400 | invalid_edl | client.edit.applyEdl() | 편집 결정 목록이 검증에 실패했습니다. |
| 400 | limit_reached | client.developerApps.create() | 이미 최대 개수의 앱이 있습니다. |
| 400 | locked_field | client.apps.run() | 재정의 값이 Webhook URL 같은 대상을 바꾸려고 했습니다. |
| 409 | name_taken | 캐릭터, 조직 | 이름이나 슬러그가 이미 사용 중입니다. |
| 409 | concurrent_modification | 장소, 오브젝트 | 내가 읽은 뒤에 항목이 바뀌었습니다. |
| 410 | voice_cloning_retired | client.voices.createClone() | 보이스 복제는 더 이상 제공되지 않습니다. 대신 보이스를 디자인하세요. |
| 503 | provider_unavailable | client.llm.structuredJob() | 인스턴스가 이 모델을 실행할 수 없습니다. 다시 시도하지 마세요. |
| 503 | feature_disabled | client.copilot | 이 배포 환경에서 기능이 꺼져 있습니다. |
| 503 | nodaro_connection_required | client.edit.editPlan() | 셀프 호스팅 설치에서 이 기능을 쓰려면 Nodaro Cloud 연결이 필요합니다. |
각 참조 페이지에 해당 메서드의 코드가 나와 있습니다. API가 보낼 수 있는 모든 코드는 REST API 오류 페이지에 나와 있습니다.
안전하게 다시 시도하기
- 읽기는 자유롭게 다시 시도하세요.
get이나list에는 부작용이 없습니다. - 유료 요청은 무작정 반복하지 마세요. 시간이 초과된 요청도 이미 실행을 시작했을 수 있습니다. 생성을 다시 시도하기 전에 멱등성 키를 전달하세요.
client.nodes.run(type, params, { idempotencyKey })와runAndWait에서 받습니다. 같은 요청을 다시 시도할 때 같은 키를 다시 쓰면, 플랫폼은 두 번째 실행을 시작해 비용을 청구하는 대신 첫 번째 실행을 반환합니다. - 스튜디오와 Recast는 자체 재시도 토큰을 사용합니다. 스튜디오 메서드에서는
clientRequestId,client.recast.rescore()에서는requestId입니다. - 5xx 응답은 잠시 기다린 뒤 다시 시도하세요. 이 로직은
createClient의 사용자 지정fetch에 두면 좋습니다.
throwFromResponse(status, body)
throwFromResponse(status: number, body: {
error?: { code?: string; message?: string; [key: string]: unknown }
}): neverHTTP 상태와 Nodaro 오류 본문을 해당하는 오류 클래스로 대응시켜 발생시킵니다. SDK는 모든 응답에 이 함수를 사용하며, 클라이언트 없이 API를 호출하는 사용자 지정 전송 계층을 위해 내보내집니다.
속성
타입
import { throwFromResponse } from "@nodaro/sdk"
throwFromResponse(403, {
error: { code: "insufficient_scope", message: "Missing scope", missingScope: "workflows:execute" },
})
// throws a ForbiddenError whose missingScope is "workflows:execute"대부분의 경로는 { error: { code, message } } 형식으로 응답합니다. 일부 오래된 경로는 다른 형식으로 응답하며, SDK는 이 형식도 읽으므로 그 오류에도 실제 code와 message가 담깁니다.
| 본문 | code | message |
|---|---|---|
{ error: "<code>", message? }, 예: 캐릭터 학습 | 그 문자열 | message, 없으면 코드 |
{ error: "<code>", error_description }, OAuth | 그 문자열 | error_description |
{ message, issues: [...] }, HTTP 400의 유효성 검사 응답 | validation_error | message |
{ statusCode, error: "<reason phrase>", message }, 예: 서버에 없는 경로 | internal_error | message |
HTTP 500 이상에서는 SDK가 이 형식들에서 코드만 읽고 텍스트는 읽지 않습니다. 이런 오류의 message는 그 코드이거나 “Request failed”입니다. 기본 형식의 message는 모든 상태에서 읽습니다.
오류 클래스는 여전히 상태를 따릅니다. 404는 본문 내용과 상관없이 NotFoundError입니다. 읽을 수 있는 텍스트가 없는 본문은 “Not found” 같은 클래스의 기본 메시지를 유지합니다. InsufficientCreditsError는 required와 available을 기본 형식에서 읽거나, 서버가 그 옆에 보내는 required와 balance 필드에서 읽습니다.
자주 묻는 질문
TypeScript에서 Nodaro API 오류는 어떻게 처리하나요?
호출을 try/catch로 감싸고, 가장 구체적인 클래스부터 instanceof로 확인한 뒤 마지막에 NodaroError를 확인하세요. 모든 클래스는 @nodaro/sdk에서 내보냅니다.
계정의 크레딧이 부족하면 어떻게 되나요?
아무 작업도 시작되기 전에 HTTP 상태 402와 함께 InsufficientCreditsError가 발생합니다. required 필드와 available 필드가 실행에 필요한 크레딧과 계정에 있는 크레딧을 알려 줍니다.
JobTimeoutError가 발생하면 작업이 취소되나요?
아니요. 작업은 계속 실행되며 대개 완료됩니다. 나중에 client.jobs.get(jobId)로 가져오거나, 느린 모델에는 maxMs를 늘리세요.
RateLimitedError가 발생하면 다시 시도해야 하나요?
네, 잠시 기다린 뒤에 다시 시도하세요. 몇 초 기다리고, 429를 새로 받을 때마다 대기 시간을 두 배로 늘리고, 몇 번 시도한 뒤에는 멈추세요.
403 오류가 정확한 사유 코드를 알려 주지 않는 이유는 무엇인가요?
모든 403은 code가 forbidden인 ForbiddenError로 전달됩니다. 사유는 message에서 읽고, OAuth 스코프가 빠진 경우에는 missingScope를 확인하세요.
관련 문서
TypeScript SDK
작업과 실행
노드 실행
오류
요청 한도
최종 업데이트