작업과 실행
TypeScript로 Nodaro 실행을 폴링하고, 나열하고, 평가하고, 취소하고, 삭제합니다. 워크플로 실행을 추적하고, 작업이 끝나면 서명된 Webhook 호출을 받습니다.
작업은 Nodaro에서 생성 한 번이며, 이미지 1장, 동영상 렌더링 1회, 보이스오버 1개가 각각 하나의 작업입니다. 실행은 워크플로 전체를 한 번 실행하는 것입니다. client.jobs는 작업을 읽고, 나열하고, 취소하고, 삭제하며, client.executions는 워크플로 실행을 읽고, 나열하고, 취소합니다. client.webhooks는 작업이 끝날 때 Nodaro가 호출하는 엔드포인트를 등록합니다. client.videoPro는 긴 동영상 생성 Pro 실행을 중지하거나 이어 갑니다. 이 메서드들은 작업 REST API와 실행 REST API와 같은 엔드포인트를 호출합니다.
메서드
| 메서드 | 기능 |
|---|---|
executions.get(id) | 모든 노드의 상태와 함께 워크플로 실행 읽기 |
executions.listForWorkflow(workflowId, params?) | 워크플로의 실행 목록 |
executions.cancel(id, params?) | 워크플로 실행 중지 |
jobs.get(id) | 입력과 출력을 포함해 작업 읽기 |
jobs.list(params?) | 내 작업을 최신순으로 나열 |
jobs.rate(id, rating) | 내 작업을 좋음 또는 나쁨으로 평가하거나 평가 지우기 |
jobs.getStatus(id) | 폴링용으로 작업의 상태 읽기 |
jobs.cancel(id) | 작업을 중지하고 예약된 크레딧 환불 |
jobs.delete(id) | 작업과 작업이 만든 미디어 삭제 |
webhooks.create(input) 외 | 작업이 끝날 때 Nodaro가 호출하는 엔드포인트를 등록, 변경, 테스트, 감사 |
verifyWebhookSignature(options) | 엔드포인트가 받은 호출의 서명 확인 |
videoPro.stop(jobId) | 동영상 생성 Pro 실행을 중지하고 완성된 부분 유지 |
videoPro.continueRun(jobId, opts?) | 동영상 생성 Pro 실행을 새 작업으로 이어 가기 |
상태
작업은 다음 상태를 거칩니다.
| 작업 상태 | 의미 |
|---|---|
pending, queued | 워커를 기다리는 중입니다. |
processing | 실행 중입니다. 모델이 알려 주는 경우 progress가 0에서 100까지 올라갑니다. |
pending_review | 이 배포 환경의 콘텐츠 정책에 따라 사람의 검토를 위해 보류되었습니다. 종료 상태가 아닙니다. |
completed | 완료되었습니다. output_data에 결과가 들어 있습니다. |
failed | 실패했습니다. error_message가 이유를 설명하며, error_hint가 실패를 분류할 수 있습니다. |
cancelled | 취소로 중지되었습니다. |
실행에는 자체 상태가 있습니다. pending, running, completed, failed, cancelled, stopping, timed_out, discarded입니다. 실행 안의 각 노드는 pending, running, completed, failed, skipped 중 하나입니다.
client.executions
실행은 워크플로를 한 번 실행하는 것입니다. AI 노드마다 작업 하나와 모든 노드의 상태를 묶습니다.
executions.get(id)
모든 노드의 상태 맵을 포함해 워크플로 실행을 읽습니다. ID가 워크플로 실행이 아니라 단일 노드 작업의 ID이면, 서버는 그 노드 하나에 대해 같은 형태로 응답합니다.
get(id: string): Promise<{ data: WorkflowExecution }>속성
타입
const { data } = await client.executions.get(executionId)
console.log(data.status, `${data.completedNodes}/${data.totalNodes}`, data.totalCreditsUsed)WorkflowExecution에는 id, kind, workflowId, status, outcome, triggerType(manual, webhook, schedule, app_run, single-node, telegram, telegram_account, api, mcp 중 하나), nodeStates, totalNodes, completedNodes, failedNodes, totalCreditsUsed, errorMessage, startedAt, completedAt, createdAt, updatedAt이 있습니다. kind는 워크플로 실행이면 execution, 단일 노드 작업이면 job입니다. MCP 클라이언트가 시작한 단일 노드 작업의 triggerType은 mcp입니다.
새로운 것이 없는 실행. 새로운 것을 찾지 못한 실행은 failed가 아니라 completed로 끝납니다. 예를 들어 피드에서 새 게시물을 찾지 못해 그 뒤의 작성 노드에 쓸 내용이 없는 경우입니다. 텍스트가 필요한 노드에 연결된 텍스트가 이번 실행에서 아무것도 만들지 않은 노드에서 왔다면, 그 노드는 건너뛰어지고 nodeStates[id].skipReason이 "empty_input"으로 설정됩니다. 그러면 완료된 실행의 outcome은 "nothing_new"가 됩니다. 그 밖의 완료된 실행은 outcome: "succeeded"입니다. outcome은 실행이 완료될 때까지 없습니다.
import { executionOutcome, countEmptyInputSkips } from "@nodaro/sdk"
const { data } = await client.executions.get(executionId)
if (data.outcome === "nothing_new") {
console.log(`Nothing new: ${countEmptyInputSkips(data.nodeStates)} node(s) had nothing to work on`)
}
executionOutcome(data.status, data.nodeStates) // the same answer, worked out in your code노드의 출력 읽기. nodeStates[nodeId].output은 노드가 completed일 때 있습니다. 3D 장면 노드처럼 노드가 failed이지만 실행이 쓸 수 있는 결과를 남긴 경우에도 있을 수 있습니다. 상태가 아니라 필드를 확인하고, output이 있다고 해서 성공으로 간주하지 마세요.
import { nodeStateMayCarryOutput } from "@nodaro/sdk"
const node = data.nodeStates["scene-1"]
if (node.status === "failed") {
console.error(node.error) // the failure stands
if (node.output?.plan) {
// the draft the run kept is still here, and it was billed
}
}
nodeStateMayCarryOutput(node.status) // true for "completed" and "failed"이 두 상태의 집합인 OUTPUT_BEARING_NODE_STATUSES도 내보냅니다.
executions.listForWorkflow(workflowId, params?)
워크플로 하나의 실행을 최신순으로, 한 페이지씩 나열합니다. 그 워크플로에서 시작한 단일 노드 실행도 목록에 포함됩니다.
listForWorkflow(workflowId: string, params?: ListExecutionsForWorkflowParams): Promise<{
data: WorkflowExecutionSummary[]
nextCursor?: string
}>속성
타입
const { data: runs, nextCursor } = await client.executions.listForWorkflow(workflowId, {
limit: 20,
status: "completed",
})각 WorkflowExecutionSummary에는 get()과 같은 kind와 outcome이 있습니다.
executions.cancel(id, params?)
워크플로 실행을 중지합니다. 중지하는 방법은 세 가지입니다.
- 즉시(기본값): 실행 중인 작업이 취소되고, 예약된 크레딧이 환불되며, 상태가
cancelled가 됩니다. mode: "after_current": 상태가stopping이 됩니다. 실행 중인 노드는 끝까지 실행되어 캔버스와 내 라이브러리에 저장된 다음, 실행이 중지됩니다.mode: "discard": 새 노드는 시작되지 않지만, 외부 모델 호출은 중간에 멈출 수 없으므로 실행 중인 작업은 취소되지 않습니다. 이 작업들은 끝까지 실행되어 내 라이브러리에 저장되지만, 결과가 캔버스로 돌아오지는 않습니다. 상태는discarded가 되며, 작업이 완료되었으므로 환불되지 않습니다.
cancel(id: string, params?: { mode?: "after_current" | "discard" }): Promise<{ success: true }>속성
타입
await client.executions.cancel(executionId, { mode: "after_current" })client.jobs
작업은 이미지 1장, 동영상 렌더링 1회, 보이스오버 1개처럼 생성 한 번입니다. 워크플로 실행은 AI 노드마다 작업을 하나씩 만들고, 단일 노드 실행은 매번 작업을 하나 만듭니다. 작업 필드는 API가 보내는 대로 snake_case를 사용합니다.
jobs.get(id)
입력과 출력을 포함해 작업을 읽습니다.
get(id: string): Promise<{ data: Job }>속성
타입
const { data: job } = await client.jobs.get(jobId)
if (job.status === "completed") console.log(job.output_data)Job에는 다음 필드가 있습니다.
| 필드 | 설명 |
|---|---|
id, status, progress | 작업 ID, 상태, 0~100의 진행률입니다. |
input_data, output_data | 요청과 결과입니다. 서버 전용 값은 둘 다에서 제거됩니다. |
error_message | 작업이 실패한 이유이며, 없으면 null입니다. |
error_hint | 일부 실패에 대한 구조화된 이유입니다. 아래를 참조하세요. |
credits | 작업에 예약된 크레딧이며, 없으면 null입니다. |
credit_status | 크레딧의 상태로, reserved, committed, refunded 중 하나입니다. 비용이 청구되지 않는 작업은 null입니다. |
credits_charged | 예약이 정산된 뒤 작업에 든 비용입니다. 예약된 금액인 credits보다 적을 수 있습니다. 환불 후에는 0, 작업이 실행 중이거나 작업이 자체적으로 아무것도 예약하지 않았으면 null입니다. 지출 합계를 내려면 이 필드를 합산하세요. |
job_type | 작업의 종류입니다. |
created_at, started_at, completed_at | 타임스탬프입니다. |
user_id | 소유자입니다. |
source, source_detail | 작업의 출처입니다. sdk, cli, mcp, app, web, api 등이며, sdk/2.17.0 같은 세부 정보가 함께 들어 있습니다. |
recovering | 모델이 결과를 전달한 뒤 워커가 멈춘 작업을 플랫폼이 복구하는 동안 true입니다. |
favorited | 작업의 소유자가 즐겨찾기했는지 여부입니다. client.gallery를 참조하세요. |
rating | "good", "bad" 또는 null입니다. jobs.rate()를 참조하세요. |
session_id | 작업이 만들어진 생성 세션이며, 없으면 null입니다. |
서버가 잠시 favorited와 rating을 읽을 수 없으면 이 필드를 생략합니다. 없는 필드는 false가 아니라 알 수 없음으로 취급하세요.
이미지 작업의 output_data에는 이미지의 픽셀 크기가 담깁니다. imageUrl의 width와 height, 그리고 imageUrls 옆의 images(항목마다 { url, width, height })입니다. 오래된 작업에는 이 필드가 없습니다. Job.output_data의 타입은 unknown이므로, 타입을 갖춰 읽으려면 NodeJobOutput으로 캐스팅하세요. runAndWait()를 참조하세요.
error_hint에는 두 가지 종류가 있습니다. 나머지를 읽기 전에 kind로 범위를 좁히세요.
safety-block: 모델 자체의 안전 필터가 출력을 거부했습니다.class는copyright,likeness,safety중 하나입니다.retried는 Nodaro가 이미 한 번 다시 시도했는지 알려 주며,suggestedProvider가 있으면 같은 요청을 다시 시도할 수 있는 모델을 가리킵니다. 모델이 프롬프트를 차단할 때를 참조하세요.policy-block: 이 배포 환경의 콘텐츠 정책이 요청이나 결과를 거부했습니다.reason은 사용자를 위해 작성되었으므로 그대로 표시하세요.
jobs.list(params?)
내 작업을 최신순으로, 한 페이지씩 나열합니다(GET /v1/jobs).
list(params?: ListJobsParams): Promise<{
data: Job[]
next: string | null
}>속성
타입
let cursor: string | undefined
do {
const page = await client.jobs.list({ type: "llm-structured", origin: "my-app", cursor })
for (const job of page.data) console.log(job.id, job.status)
cursor = page.next ?? undefined
} while (cursor)모든 필터는 서로 조합됩니다. 페이지에 담긴 행이 limit보다 적거나 하나도 없어도 next가 있을 수 있습니다. 페이지는 행 수가 아니라 항상 next를 기준으로 넘기세요.
// Your favorited images and videos that came out well
const { data: keepers } = await client.jobs.list({
type: ["generate-image", "generate-video"],
status: "completed",
favorited: true,
rating: "good",
})jobs.rate(id, rating)
내 작업 하나를 "good" 또는 "bad"로 평가하거나, null로 평가를 지웁니다(PUT /v1/jobs/:id/rating). 작업에는 평가가 하나만 있으며, 소유자만 평가할 수 있습니다. 다른 사람의 작업은 NotFoundError를 발생시킵니다.
rate(id: string, rating: "good" | "bad" | null): Promise<{ data: { jobId: string; rating: "good" | "bad" | null } }>속성
타입
await client.jobs.rate(jobId, "good")
await client.jobs.rate(jobId, null) // clear it평가는 Job.rating으로 다시 읽히고 jobs.list()의 필터로 쓰입니다. OAuth 앱에는 assets:write 스코프가 필요합니다. 평가가 없는 서버는 코드 not_available, 상태 503의 NodaroError를 발생시킵니다.
jobs.getStatus(id)
작업의 상태만 읽습니다. id, status, progress, output_data, error_message, error_hint, credit_status, 그리고 서버 시계 기준으로 작업이 끝난 시각인 completed_at(끝나기 전에는 null)입니다(GET /v1/jobs/:id/status). 요청 데이터와 비용 필드를 빼므로 get()보다 훨씬 가볍습니다. 폴링 루프에서 사용하세요.
getStatus(id: string): Promise<{ data: JobStatusResult }>속성
타입
async function waitForJob(jobId: string) {
for (;;) {
const { data } = await client.jobs.getStatus(jobId)
if (data.status === "completed") return data.output_data
if (data.status === "failed" || data.status === "cancelled") {
throw new Error(data.error_message ?? data.status)
}
await new Promise((resolve) => setTimeout(resolve, 2_000))
}
}client.nodes.runAndWait()가 진행률, 취소, 타입 지정 오류와 함께 이 루프를 대신 실행합니다.
jobs.cancel(id)
작업을 취소하고 예약된 크레딧을 환불합니다. 검토를 위해 보류된 작업도 취소할 수 있습니다.
cancel(id: string): Promise<{ success: true; cancelled: number }>속성
타입
const { cancelled } = await client.jobs.cancel(jobId)jobs.delete(id)
작업과 작업이 만든 비공개 미디어를 삭제합니다(DELETE /v1/jobs/:id). 작업의 소유자만 삭제할 수 있습니다. 실행 중인 작업은 그 상태 그대로 삭제되므로, 워커를 멈춰야 한다면 먼저 취소하세요.
delete(id: string): Promise<{ success: true }>속성
타입
await client.jobs.cancel(jobId)
await client.jobs.delete(jobId)client.webhooks
아웃바운드 Webhook은 작업이 끝날 때 내 서버에 알려 주므로 앱이 폴링할 필요가 없습니다. https 엔드포인트를 등록하면 Nodaro가 내 계정의 모든 job.completed와 job.failed에 대해 POST를 보냅니다. 각 호출은 엔드포인트 자체의 시크릿으로 서명됩니다. 실패한 호출은 약 43시간 동안 백오프로 다시 시도되며, 모든 시도는 엔드포인트의 전송 기록에 남습니다. 이벤트, 본문, 헤더, 재시도 일정은 Webhook을 참조하세요.
OAuth 앱 토큰에는 jobs:read 스코프가 필요합니다. 연결된 앱이 등록한 엔드포인트는 그 앱이 직접 제출한 작업만 받습니다.
create(input: CreateWebhookEndpointInput): Promise<CreatedWebhookEndpoint> // { data, secret }
list(): Promise<{ data: WebhookEndpoint[] }>
get(id: string): Promise<{ data: WebhookEndpoint }>
update(id: string, input: UpdateWebhookEndpointInput): Promise<{ data: WebhookEndpoint }>
delete(id: string): Promise<{ success: true }>
rotateSecret(id: string, input?: RotateWebhookSecretInput): Promise<RotatedWebhookSecret> // { secret, previousSecretExpiresAt }
deliveries(id: string, params?: ListWebhookDeliveriesParams): Promise<WebhookDeliveryPage>
getDelivery(id: string, deliveryId: string): Promise<{ data: WebhookDelivery }>
test(id: string): Promise<{ data: WebhookDelivery }>webhooks.create(input)
엔드포인트를 등록하고 secret(whsec_…)과 함께 반환합니다. 시크릿은 한 번만 표시되므로 서버 설정과 함께 보관하세요.
속성
타입
const { data: endpoint, secret } = await client.webhooks.create({
url: "https://example.com/nodaro-webhook",
events: ["job.completed", "job.failed"],
})
await client.webhooks.test(endpoint.id) // sends a ping주소 하나에는 엔드포인트가 하나만 있을 수 있습니다(409 url_taken). 계정 하나에는 엔드포인트가 최대 10개입니다(400 limit_reached).
webhooks.update(id, input)
url, events, description(null이면 지움), isActive 중 원하는 것을 바꿉니다. 50번 연속 전송이 최종 실패하면 Nodaro는 엔드포인트를 끄고 disabledReason을 "consecutive_failures"로 설정합니다. isActive: true는 엔드포인트를 다시 켜고 consecutiveFailures를 초기화합니다.
webhooks.rotateSecret(id, input?)
새 시크릿을 만들고 한 번 반환합니다. 새 시크릿은 지금부터 재시도를 포함한 모든 전송에 서명합니다. 이전 시크릿은 gracePeriodSeconds 동안 함께 계속 서명하므로, 공백 없이 수신기를 업데이트할 수 있습니다. 값은 0~604800이며 기본값은 하루입니다. 그동안 Nodaro-Signature에는 두 번째 v1=이 담기고, verifyWebhookSignature는 둘 중 어느 것이든 받습니다. 유출된 시크릿을 즉시 멈추려면 { gracePeriodSeconds: 0 }을 전달하세요.
const { secret, previousSecretExpiresAt } = await client.webhooks.rotateSecret(endpoint.id)이전 시크릿이 즉시 멈췄으면 previousSecretExpiresAt은 null입니다.
webhooks.deliveries(id, params?)와 getDelivery(id, deliveryId)
deliveries()는 전송 기록을 최신순으로 읽습니다. 기록은 30일 동안 보관됩니다. status("pending", "delivered", "failed")로 거르고, limit(1~100, 기본값 20)과 cursor로 페이지를 넘기세요. getDelivery()는 ID로 전송 하나를 읽습니다.
각 WebhookDelivery에는 event, jobId, status, attempts, responseStatus, responseBody(처음 2KB), durationMs, error, requestBody, nextAttemptAt, deliveredAt이 있습니다. requestBody는 보낸 본문 그대로이며, 전송이 성공하면 null이 됩니다.
webhooks.test(id)
ping을 대기열에 넣고 그 전송을 반환합니다. ping은 몇 초 안에 보내지며, 엔드포인트가 꺼져 있어도 한 번 시도됩니다. getDelivery(id, delivery.id)로 따라가세요.
verifyWebhookSignature(options)
엔드포인트가 받은 호출을 확인하고 파싱된 이벤트를 반환합니다. client의 메서드가 아니라 @nodaro/sdk의 최상위 내보내기입니다. Web Crypto만 쓰므로 Node 20+, Deno, Bun, Cloudflare Workers, Vercel Edge, 브라우저에서 실행됩니다.
verifyWebhookSignature(options: {
payload: string | Uint8Array | ArrayBuffer // the raw body, as received
header: string | readonly string[] | null | undefined // Nodaro-Signature
secret: string // whsec_…
toleranceSeconds?: number // default 300; 0 turns the check off
}): Promise<WebhookEvent>원본 본문을 받은 그대로 전달하세요. 파싱한 뒤 다시 직렬화한 JSON을 전달하면 안 됩니다. 이벤트는 data.job이 Job인 JobWebhookEvent이거나 PingWebhookEvent입니다. 확인에 실패하면 다음 코드 중 하나를 가진 WebhookSignatureError를 발생시킵니다. missing_header, malformed_header, signature_mismatch, timestamp_outside_tolerance, invalid_payload, missing_secret, crypto_unavailable입니다. 비교는 일정한 시간에 이루어집니다.
import { verifyWebhookSignature, WebhookSignatureError } from "@nodaro/sdk"
export async function POST(request: Request) {
try {
const event = await verifyWebhookSignature({
payload: await request.text(),
header: request.headers.get("nodaro-signature"),
secret: process.env.NODARO_WEBHOOK_SECRET!,
})
if (event.type === "job.completed") await saveResult(event.data.job)
return new Response(null, { status: 204 })
} catch (err) {
if (err instanceof WebhookSignatureError) return new Response(err.code, { status: 400 })
throw err
}
}10초 안에 2xx 상태로 응답하면 전송이 완료된 것으로 처리됩니다. 본문의 id는 전송 ID이며 재시도할 때마다 같으므로, 이미 처리한 이벤트를 건너뛰는 데 쓰세요.
client.videoPro
긴 동영상을 세그먼트 단위로 렌더링하는 노드인 동영상 생성 Pro의 실행을 제어합니다. Nodaro Cloud에서 동작합니다. 다른 노드 실행처럼 실행을 시작한 다음, 그 작업에 이 메서드들을 사용하세요.
videoPro.stop(jobId)
실행 중인 동영상 생성 Pro 작업을 정상적으로 중지합니다. 진행 중인 세그먼트는 버려지지만 비용은 청구되고, 남은 세그먼트는 건너뜁니다. 완성된 세그먼트는 이어 붙여 작업의 최종 동영상이 되고, 사용하지 않은 예약 크레딧은 환불됩니다. 아직 시작하지 않은 작업은 취소되고 전액 환불됩니다.
stop(jobId: string): Promise<StopVideoProResult>속성
타입
await client.videoPro.stop(jobId)
const { data } = await client.jobs.getStatus(jobId) // becomes completed, with the partial video작업을 계속 폴링하세요. 작업은 output_data.pro.stopped가 true로 설정된 상태로 완료됩니다.
videoPro.continueRun(jobId, opts?)
중지되었거나, 실패했거나, 완료된 실행을 새 작업으로 이어 갑니다. fromSegment 이전의 세그먼트는 다시 사용하고, 그 세그먼트부터는 모두 다시 생성합니다. 다시 생성한 세그먼트의 비용과 고정 Pro 요금만 지불합니다.
continueRun(jobId: string, opts?: { fromSegment?: number }): Promise<ContinueVideoProResult>속성
타입
const { jobId: newJobId, fromSegment } = await client.videoPro.continueRun(jobId, { fromSegment: 4 })결과에는 폴링할 새 jobId가 들어 있으며, continuedFromJobId, fromSegment, segmentCount, deduped가 들어 있을 수도 있습니다.
자주 묻는 질문
작업과 실행은 어떻게 다른가요?
작업은 이미지 1장이나 동영상 1개처럼 생성 한 번입니다. 실행은 워크플로 전체를 한 번 실행하는 것이며, AI 노드마다 작업 하나와 모든 노드의 상태를 묶습니다.
폴링 루프에서는 어떤 메서드를 호출해야 하나요?
client.jobs.getStatus(jobId)를 호출하세요. 상태, 진행률, 출력, 오류만 반환하므로 client.jobs.get보다 훨씬 가볍습니다.
작업을 취소하면 크레딧이 환불되나요?
네. client.jobs.cancel은 작업을 중지하고 예약된 크레딧을 환불합니다. 실행을 즉시 취소하면 실행 중인 작업에도 같은 일이 일어납니다.
내 앱이 폴링하는 대신 작업이 끝나면 Nodaro가 내 서버를 호출하게 할 수 있나요?
네. client.webhooks.create로 https 엔드포인트를 등록하세요. 그러면 Nodaro가 내 계정의 모든 job.completed와 job.failed에 대해 서명된 POST를 보냅니다. 각 호출은 verifyWebhookSignature로 확인하세요.
pending_review 상태는 무슨 뜻인가요?
이 배포 환경의 콘텐츠 정책이 사람이 검토하도록 결과를 보류하고 있다는 뜻입니다. 종료 상태가 아니며, 작업은 나중에 completed, failed, cancelled 중 하나가 됩니다. 계속 기다리고 요청을 다시 실행하지 마세요.
관련 문서
노드 실행
워크플로와 프로젝트
오류
작업
Webhook
최종 업데이트