Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nureta — TypeScript SDK for the Nureta Generation API

TypeScript / JavaScript client for the Nureta ("tokenstore") Generation API — a Seedance-compatible platform for generating video, images, and scripts from text and reference images, with first-class support for explicit / NSFW content.

  • Website & dashboard: https://developer.nureta.ai/
  • Base URL: https://developer.nureta.ai
  • Auth: API key as a bearer token (Authorization: Bearer sk-...)
  • Billing: pay-per-generation against your balance (USDC top-ups on Base); failed jobs are auto-refunded
  • Async by design: you create a task, then poll it (or receive a webhook) until it's succeeded/failed

Zero runtime dependencies — built on the platform fetch. Works in Node 18+, Deno, Bun, Cloudflare Workers, and other modern runtimes. Ships ESM + CommonJS + type declarations.

The api.md file is the method reference; openapi.yml is the API's source of truth. This SDK mirrors the Python SDK.


Install

npm install nureta

Authenticate

import { Nureta } from 'nureta';

const client = new Nureta({ apiKey: 'sk-...' }); // or set NURETA_API_KEY

Create an API key in the API keys section of the dashboard. New keys default to 10 req/s (adjustable 1–100). Every call counts toward that key's rate limit; a 429 RateLimitError means back off and retry.


Quickstart — generate a video

import { Nureta } from 'nureta';

const client = new Nureta({ apiKey: 'sk-...' });

const { id } = await client.videos.create({
  model: 'seahorse-1080p',
  content: [{ type: 'text', text: 'a seahorse dancing through neon waves' }],
  ratio: '16:9',
  duration: 8, // single clip: 5 / 8 / 10 / 12 / 15s
});

// Poll until the task finishes (succeeded / failed / cancelled):
const task = await client.videos.waitForCompletion(id, { pollIntervalMs: 3000 });

if (task.status === 'succeeded') {
  console.log(task.content?.video_url);
} else {
  console.log('failed:', task.error?.message); // auto-refunded
}

Prefer to drive the poll loop yourself? retrieve and task.step are all you need:

for (;;) {
  const task = await client.videos.retrieve(id);
  if (task.status !== 'pending') break;
  // task.step?.code = queued | generating_script | rendering | stitching | ...
  await new Promise((r) => setTimeout(r, 3000));
}

Content items

content mixes exactly one text prompt with up to 9 image_url references and at most one audio_url:

const content = [
  { type: 'text', text: 'cinematic, moody lighting' },
  { type: 'image_url', image_url: { url: 'https://.../ref.jpg' } },
  { type: 'image_url', image_url: { url: 'https://.../start.jpg' }, role: 'first_frame' },
  { type: 'image_url', image_url: { url: 'https://.../end.jpg' }, role: 'last_frame' },
] as const;

A pinned first_frame/last_frame is mutually exclusive with reference images.

Don't host your reference yet? Presign an upload:

const signed = await client.uploads.create({ fileName: 'ref.jpg', contentType: 'image/jpeg' });
// PUT the bytes to signed.uploadUrl with the same Content-Type, then use signed.assetUrl.
await fetch(signed.uploadUrl, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/jpeg' },
  body: bytes,
});

Images

const { id } = await client.images.create({
  model: 'seahorse-image',
  content: [{ type: 'text', text: 'a clean studio product shot on marble' }],
  size: '4K', // 1K / 2K / 4K
});
const task = await client.images.waitForCompletion(id);
console.log(task.content?.image_url);

Scripts

Generate just a storyboard (title, characters, per-scene beats). Flat $0.01.

const { id } = await client.scripts.create({
  model: 'seahorse-script',
  content: [{ type: 'text', text: 'a lonely lighthouse keeper finds a message in a bottle' }],
  duration: 30, // 5–180s; scales scene count, not price
});
const task = await client.scripts.waitForCompletion(id);
console.log(task.content?.script?.title, task.content?.script?.scenes);

Configuration

const client = new Nureta({
  apiKey: 'sk-...',
  baseURL: 'https://developer.nureta.ai', // override for staging
  timeout: 60_000,                        // per-request, ms
  maxRetries: 2,                          // idempotent requests only (see below)
  defaultHeaders: { 'X-My-App': 'demo' },
  fetch: myFetch,                         // custom fetch (proxy, instrumentation)
});

Every method also takes per-call overrides:

await client.videos.retrieve(id, { timeout: 10_000, maxRetries: 5, signal: ac.signal });

Retries & timeouts

Requests retry with exponential backoff + jitter and honor a Retry-After header. Retries fire on 408 / 409 / 429 / 5xx and connection errors — but only for idempotent methods (GET polling and PUT edits). A billed create / render / cancel POST is never silently re-submitted, so you can't be double-charged. Set maxRetries: 0 to disable.

Access the raw response

const { data, response } = await client.models.list().withResponse();
console.log(response.headers.get('x-request-id'), data.models.length);

Errors

API errors throw an APIError subclass carrying status, code, body, and headers:

import { APIError, RateLimitError } from 'nureta';

try {
  await client.videos.create(params);
} catch (err) {
  if (err instanceof RateLimitError) {
    // back off
  } else if (err instanceof APIError) {
    console.error(err.status, err.code, err.message); // e.g. 401 AuthenticationError
  }
  throw err;
}
Class When
AuthenticationError (401) missing / invalid API key
BadRequestError (400), UnprocessableEntityError (422) invalid request field
PermissionDeniedError (403), NotFoundError (404), ConflictError (409)
RateLimitError (429) rate limit exceeded
InternalServerError (5xx) generation failed (auto-refunded)
APIConnectionError / APIConnectionTimeoutError network / timeout
APIUserAbortError you aborted via AbortSignal

NSFW / explicit content

Nureta renders explicit adult video. The underlying model has no built-in knowledge of sex acts, so explicit shots route through a separate anatomy-reference pipeline you opt into per shot with the explicit flag. It applies to multi-shot scenes — advanced mode (segments) or edit mode, video only, total duration > 15s.

  • explicit: truethis shot depicts sex. Describe the act concretely in the shot's script/beat, or it renders soft (no references attach).
  • explicit: false — build-up / story shot, rendered from text only.

Best model: seahorse-1080p for explicit video; seahorse-image at 4K for stills.

Advanced mode — bring your own storyboard

const { id } = await client.videos.create({
  model: 'seahorse-1080p',
  ratio: '9:16',
  content: [{ type: 'text', text: 'a moonlit rooftop encounter' }],
  segments: [ // must total 16–180s; per-segment 4–15s
    { script: 'she steps onto the rooftop, city lights behind her', duration_seconds: 8 },
    { script: 'he joins her at the railing; they kiss', duration_seconds: 10, explicit: false },
    { script: '<concrete description of the act>', duration_seconds: 12, explicit: true },
  ],
});

Edit mode — review the storyboard before the full render

Submit with mode: 'edit'; the task charges at submit and parks at status: 'planned' with a scene draft. Then tweak and render:

const { id } = await client.videos.create({
  model: 'seahorse-1080p',
  duration: 60,
  mode: 'edit',
  content: [{ type: 'text', text: 'a slow bedroom scene' }],
});

const draft = await client.videos.waitForCompletion(id); // stops at status "planned"

// Tweak a shot's script / duration / pinned frames (matched by 1-based index):
await client.videos.editPlan(id, {
  segments: [{ index: 2, script: 'she turns toward him', duration_seconds: 8 }],
});

// Or edit the short beats and let the server re-expand (returns 202):
await client.videos.editPlanStructured(id, {
  world: 'candlelit loft, warm tones',
  segments: [{ index: 3, beat: '<concrete act>', explicit: true }],
});
// Task status stays "planned" during re-expansion, so wait on the scene, not status:
await client.videos.waitForReexpand(id); // scene.reexpandStatus → "ready"

await client.videos.render(id);      // commit to the full render
// or client.videos.cancel(id);      // immediate full refund

Legal/safety: explicit generation is for adult (18+) content you are authorized to create. callback_urls must be HTTPS and must not target private/loopback ranges.


Models & pricing

for (const m of (await client.models.list()).models) {
  console.log(m.id, m.resolution, m.price_usd_per_sec);
}
// image / script model lists: client.images.listModels(), client.scripts.listModels()
Model Resolution USD/sec USD/5s
seahorse-480p 480p $0.1888 $0.9440
seahorse-720p 720p $0.2773 $1.3864
seahorse-1080p 1080p $0.5428 $2.7139
seahorse-image 1K/2K/4K $0.05 / image
seahorse-script $0.01 / script

Callbacks

Pass callback_url (and optional callback_secret) on creation and Nureta POSTs the terminal task object to you when it finishes — same JSON as retrieve. Delivery is at-least-once (dedupe by id); when a secret is set, verify the X-Tokenstore-Signature HMAC-SHA256 over timestamp + "." + rawBody. Webhook receiving is your server's job, not this SDK's.


Develop

npm install
npm run build       # tsup → dist/ (esm + cjs + d.ts)
npm run typecheck
npm test            # offline tests (node:test, injected fetch)

About

official typescript sdk for nureta APIs https://developer.nureta.ai/

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages