Skip to main content

npm

babysea on npm

GitHub

babysea on GitHub

Installation

npm install babysea

Quickstart

TypeScript
import { BabySea } from 'babysea';

const client = new BabySea({ apiKey: 'bye_...', region: 'us' });

// Submit a generation
const res = await client.generate('google/nano-banana', {
  generation_prompt: 'A cute baby seal on the beach',
});

// Poll for result (use webhooks in production — see below)
let generation = await client.getGeneration(res.data.generation_id);
while (['pending', 'processing'].includes(generation.data.generation_status)) {
  await new Promise(r => setTimeout(r, 2000));
  generation = await client.getGeneration(res.data.generation_id);
}

if (generation.data.generation_status === 'succeeded') {
  console.log('Output URLs:', generation.data.generation_output_file);
}

Configuration

TypeScript
const client = new BabySea({
  apiKey: 'bye_...',          // Required
  region: 'us',              // 'us' | 'eu' | 'jp' — defaults to 'us'
  timeout: 30_000,           // Request timeout in ms (default: 30000)
  maxRetries: 2,             // Max retries for retryable errors (default: 2)
});

// Override with a custom base URL (takes precedence over region)
const dev = new BabySea({
  apiKey: 'bye_...',
  baseUrl: 'https://custom.babysea.ai',
});

Runtime compatibility

RuntimeSupportedNotes
Node.js 18+Uses native fetch and crypto.subtle
DenoWeb standard APIs
BunWeb standard APIs
Cloudflare WorkersEdge runtime compatible
Modern BrowsersESM bundle, crypto.subtle required
Node.js < 18No native fetch — use a polyfill or upgrade

Response envelope

All successful responses:
{ "status": "success", "request_id": "...", "message": "...", "timestamp": "...", "data": { ... } }
Paginated responses add total, limit, and offset alongside data.

Package exports and types

TypeScript
import { BabySea } from 'babysea';
import { verifyWebhook } from 'babysea/webhooks'; // separate export for tree-shaking

import type {
  BabySeaOptions, BabySeaRegion,
  ApiResponse, PaginatedResponse, ApiErrorBody, RateLimitInfo,
  AccountData, BillingData, EstimateData, StatusData, UsageData, UsageEndpoint, UsageProvider,
  ImageGenerationParams, ImageGenerationData,
  VideoGenerationParams, VideoGenerationData,
  GenerationParams, GenerationData, Generation, GenerationDeleteData, GenerationCancelData,
  GenerationListData,
  HealthCacheData, HealthModel, HealthModelProvider, HealthModelsData,
  HealthProvider, HealthProvidersData, HealthStorageData,
  Model, ModelSchema, LibraryModelsData, ProviderProfile, LibraryProvidersData,
  ApiKeyScope, ApiKeyScopePreset,
  WebhookEventType, WebhookPayload,
} from 'babysea';

Methods

Every method maps directly to a REST endpoint. The route is shown above each example — use cURL or any HTTP client if you prefer.

Content generation

generate (image or video)

POST /v1/generate/image/{model_identifier} · POST /v1/generate/video/{model_identifier} Returns immediately with a generation_id. Track completion with getGeneration() or webhooks.
TypeScript
// Image
const res = await client.generate('google/nano-banana', {
  generation_prompt: 'A cute baby seal on the beach at sunset',
  generation_ratio: '16:9',
  generation_output_format: 'png',
});

// Video (generation_duration required)
const vid = await client.generate('google/veo-2', {
  generation_prompt: 'A baby seal plays in Arctic',
  generation_ratio: '16:9',
  generation_duration: 5,
});

// Image-to-video
const i2v = await client.generate('google/veo-2', {
  generation_prompt: 'The seal starts swimming',
  generation_duration: 8,
  generation_input_file: ['https://example.com/seal.jpg'],
});

// Resolution-priced video
const hd = await client.generate('bytedance/seedance-1-pro', {
  generation_prompt: 'A cinematic scene',
  generation_duration: 8,
  generation_resolution: '1080p',
  generation_input_file: ['https://example.com/photo.jpg'],
});

// Audio-capable model (true = audio rate, false = no-audio rate)
const audio = await client.generate('bytedance/seedance-1.5-pro', {
  generation_prompt: 'A baby seal plays in Arctic',
  generation_duration: 8,
  generation_resolution: '720p',
  generation_generate_audio: true,
});

console.log(res.data.generation_id);
console.log(res.data.generation_provider_order); // string[] — ordered provider list
console.log(res.data.generation_initialized);    // true
Provider order — choose which providers to try and in what order. BabySea fails over automatically:
TypeScript
// Single provider
const res = await client.generate('minimax/video-01-director', {
  generation_prompt: 'Ocean waves',
  generation_duration: 5,
  generation_provider_order: 'replicate',
});

// Multi-provider failover
const res2 = await client.generate('google/nano-banana', {
  generation_prompt: 'Ocean waves',
  generation_provider_order: 'fal, replicate',
});
Use client.library.models() to see which provider orders each model accepts. Model-specific parameters — some models accept extra params beyond the standard schema:
TypeScript
// 1. Check what a model accepts
const catalog = await client.library.models();
const model = catalog.data.models.find(m => m.model_identifier === 'qwen/image');

// 2. Pass them in the generate call (all use the generation_ prefix)
const res = await client.generate('qwen/image', {
  generation_prompt: 'A landscape',
  generation_negative_prompt: 'blurry, low quality',
  generation_seed: 42,
  generation_guidance: 7,
});

getGeneration

GET /v1/content/{generation_id}
TypeScript
const res = await client.getGeneration('...');

console.log(res.data.generation_status);      // 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled'
console.log(res.data.generation_output_file); // null if purged or not yet ready
If output files were purged by retention cleanup, generation_output_file returns null alongside a generation_removed flag.

listGenerations

GET /v1/content/list — up to 100 per page, ordered by creation date descending.
TypeScript
const res = await client.listGenerations();             // first 50
const page2 = await client.listGenerations({ limit: 20, offset: 20 });

console.log(res.total);
for (const gen of res.data.generations) {
  console.log(gen.generation_id, gen.generation_status);
}

cancelGeneration

POST /v1/content/generation/cancel/{generation_id} — available while status is pending or processing.
TypeScript
const res = await client.cancelGeneration('...');

console.log(res.data.generation_status);    // 'canceled'
console.log(res.data.provider_cancel_sent); // true | false
console.log(res.data.credits_refunded);     // true | false

deleteGeneration

DELETE /v1/content/{generation_id} — permanently purges output files from storage. Generation metadata is retained for compliance.
TypeScript
const res = await client.deleteGeneration('...');

console.log(res.data.generation_id);
console.log(res.data.files_deleted); // number of storage files removed

Library

library.providers

GET /v1/library/providers
TypeScript
const res = await client.library.providers();

for (const p of res.data.providers) {
  console.log(p.provider_id, p.provider_name, p.provider_status);
}

library.models

GET /v1/library/models
TypeScript
const res = await client.library.models();

for (const m of res.data.models) {
  console.log(m.model_identifier, m.model_pricing);
  console.log(m.model_supported_provider);
  console.log(m.schema.generation_ratio, m.schema.generation_output_format);
}

Financials

estimate

GET /v1/estimate/{model_identifier} — free, consumes no GPU compute or credits.
TypeScript
// Image model
const res = await client.estimate('bfl/flux-schnell');
const res5 = await client.estimate('bfl/flux-schnell', 5);            // 5 generations
const resCount = await client.estimate('bfl/flux-schnell', { count: 5 });

// Video model
const vid = await client.estimate('google/veo-2', { duration: 8 });

// Resolution-priced video
const hd = await client.estimate('bytedance/seedance-1-pro', { duration: 8, resolution: '1080p' });

// Audio-priced video
const audioOn  = await client.estimate('bytedance/seedance-1.5-pro', { duration: 8, resolution: '720p', audio: true });
const audioOff = await client.estimate('bytedance/seedance-1.5-pro', { duration: 8, resolution: '720p', audio: false });

console.log(res.data.cost_per_generation);
console.log(res.data.cost_total_consumed);
console.log(res.data.credit_balance);
console.log(res.data.credit_balance_can_afford);
console.log(res.data.credit_balance_max_affordable);

billing

GET /v1/user/billing — requires account:read scope.
TypeScript
const res = await client.billing();

console.log(res.data.billing_plan);
console.log(res.data.billing_credit_balance);
console.log(res.data.billing_subscription_status);
console.log(res.data.billing_period_starts_at);
console.log(res.data.billing_period_ends_at);

API and account

status

GET /v1/status — verify your key and get key metadata. Requires account:read scope.
TypeScript
const res = await client.status();

console.log(res.data.account_id);
console.log(res.data.apikey_prefix);
console.log(res.data.apikey_name);
console.log(res.data.apikey_last_used_at);

account

GET /v1/user/account — profile details for the identity bound to the key.
TypeScript
const res = await client.account();

console.log(res.data.account_id);
console.log(res.data.account_name);
console.log(res.data.account_email);

usage

GET /v1/usage — aggregated usage analytics for a 1–90 day window (default: 30).
TypeScript
const res = await client.usage();     // last 30 days
const res7 = await client.usage(7);  // last 7 days

console.log(res.data.usage_credit_balance);
console.log(res.data.usage_total_requests);
console.log(res.data.usage_total_generations);
console.log(res.data.usage_total_estimated_cost);

for (const ep of res.data.usage_endpoints) {
  console.log(ep.endpoint, ep.total_requests, ep.success_count, ep.error_codes);
}

for (const prov of res.data.usage_providers) {
  console.log(prov.provider, prov.total_submissions, prov.total_estimated_cost);
}

Health

health.providers

GET /v1/health/inference/providers
TypeScript
const res = await client.health.providers();

for (const p of res.data.providers) {
  console.log(p.provider, p.status, p.failure_rate);
  // status: 'healthy' | 'recovering' | 'degraded' | 'unknown'
}

health.models

GET /v1/health/inference/models
TypeScript
const res = await client.health.models();

for (const m of res.data.models) {
  console.log(m.model_identifier, m.providers);
}

health.storage / health.cache

GET /v1/health/storage · GET /v1/health/cache
TypeScript
const [storage, cache] = await Promise.all([
  client.health.storage(),
  client.health.cache(),
]);

console.log(storage.data.healthy, storage.data.latency_ms);
console.log(cache.data.healthy, cache.data.latency_ms);

Webhooks

The webhook helper is a separate export for tree-shaking:
TypeScript
import { verifyWebhook } from 'babysea/webhooks';
verifyWebhook(rawBody, signature, secret, toleranceSeconds?) parses the X-BabySea-Signature header, validates the HMAC, and rejects deliveries older than the tolerance window (default: 300s). Throws on failure.
ParameterTypeDescription
rawBodystringRaw request body — not parsed JSON
signaturestringValue of the X-BabySea-Signature header
secretstringYour webhook secret
toleranceSecondsnumberOptional — max age in seconds (default: 300)

Next.js App Router

TypeScript
import { verifyWebhook } from 'babysea/webhooks';

export async function POST(req: Request) {
  const rawBody = await req.text();
  const signature = req.headers.get('x-babysea-signature')!;

  try {
    const payload = await verifyWebhook(rawBody, signature, process.env.BABYSEA_WEBHOOK_SECRET!);

    if (payload.webhook_event === 'generation.completed') {
      const { generation_id, generation_output_file } = payload.webhook_data;
      // process result
    }

    return Response.json({ received: true });
  } catch {
    return Response.json({ error: 'Invalid signature' }, { status: 400 });
  }
}

Express / Node.js

Use express.text() instead of express.json() on your webhook route. The JSON parser re-serializes the body and breaks the HMAC signature.
TypeScript
import express from 'express';
import { verifyWebhook } from 'babysea/webhooks';

app.post('/webhooks/babysea', express.text({ type: 'application/json' }), async (req, res) => {
  try {
    const payload = await verifyWebhook(
      req.body, // raw string
      req.headers['x-babysea-signature'] as string,
      process.env.BABYSEA_WEBHOOK_SECRET!,
    );

    switch (payload.webhook_event) {
      case 'generation.completed':
        console.log('Output:', payload.webhook_data.generation_output_file);
        break;
      case 'generation.failed':
        console.log('Error:', payload.webhook_data.generation_error_code);
        break;
      case 'generation.started':
        console.log('Processing:', payload.webhook_data.generation_id);
        break;
      case 'generation.canceled':
        console.log('Canceled:', payload.webhook_data.generation_id);
        break;
      case 'credits.low_balance':
        console.log('Balance:', payload.webhook_data.current_balance);
        break;
    }

    res.status(200).json({ received: true });
  } catch {
    res.status(400).json({ error: 'Invalid signature' });
  }
});

Webhook events and payload types

Eventwebhook_data fields
generation.startedaccount_id, model_identifier, generation_id, generation_status: 'processing', generation_provider_initialize, optional generation_prediction_id
generation.completedaccount_id, model_identifier, generation_id, generation_status: 'succeeded', generation_provider_used, generation_output_file, optional generation_prediction_id
generation.failedaccount_id, model_identifier, generation_id, generation_status: 'failed', generation_error, generation_error_code, optional generation_prediction_id, optional generation_provider_used
generation.canceledaccount_id, model_identifier, generation_id, generation_status: 'canceled', optional generation_prediction_id, optional credits_refunded
credits.low_balanceaccount_id, current_balance, thresholds_crossed
webhook.testaccount_id, model_identifier, generation_id, generation_status: 'succeeded'
Type narrowing with the WebhookPayload union:
TypeScript
import type { WebhookPayload } from 'babysea';

function handlePayload(payload: WebhookPayload) {
  switch (payload.webhook_event) {
    case 'generation.completed':
      payload.webhook_data.generation_output_file;
      break;
    case 'generation.failed':
      payload.webhook_data.generation_error_code;
      break;
    case 'credits.low_balance':
      payload.webhook_data.thresholds_crossed;
      break;
  }
}
Custom tolerance window:
TypeScript
const payload = await verifyWebhook(rawBody, signature, secret, 600); // 10 minutes

Examples

Batch generation

TypeScript
import { BabySea, BabySeaError } from 'babysea';

const client = new BabySea({ apiKey: 'bye_...', region: 'us', maxRetries: 3 });

const prompts = [
  'A mountain landscape at dawn',
  'An underwater coral reef',
  'A futuristic city skyline',
];

const results = await Promise.allSettled(
  prompts.map(prompt =>
    client.generate('bfl/flux-schnell', {
      generation_prompt: prompt,
      generation_ratio: '16:9',
      generation_output_format: 'png',
    }),
  ),
);

for (const [i, result] of results.entries()) {
  if (result.status === 'fulfilled') {
    console.log(`✓ "${prompts[i]}" - ${result.value.data.generation_id}`);
  } else if (result.reason instanceof BabySeaError) {
    console.log(`✗ "${prompts[i]}" - ${result.reason.code}: ${result.reason.message}`);
  }
}

Cost check before generation

TypeScript
const estimate = await client.estimate('bfl/flux-schnell', 3);

if (!estimate.data.credit_balance_can_afford) {
  console.log(`Can afford at most ${estimate.data.credit_balance_max_affordable} generations.`);
  process.exit(1);
}

const result = await client.generate('bfl/flux-schnell', { generation_prompt: 'A cute baby seal' });

Credit balance check

TypeScript
const billing = await client.billing();

if (billing.data.billing_credit_balance < 1) {
  console.log('Low credits. Top up in your BabySea workspace before generating.');
  process.exit(1);
}

Polling

Polling is a fallback pattern. Use webhooks for production — they are more efficient, lower-latency, and avoid unnecessary API calls.
TypeScript
const gen = await client.generate('bfl/flux-schnell', { generation_prompt: 'A beautiful sunset' });

let generation;
do {
  await new Promise(r => setTimeout(r, 2000));
  generation = (await client.getGeneration(gen.data.generation_id)).data;
} while (['pending', 'processing'].includes(generation.generation_status));

if (generation.generation_status === 'succeeded') {
  console.log('Output:', generation.generation_output_file);
} else {
  console.log('Failed:', generation.generation_error);
}

Paginating all generations

TypeScript
const allGenerations = [];
let offset = 0;
const limit = 100;

while (true) {
  const res = await client.listGenerations({ limit, offset });
  allGenerations.push(...res.data.generations);
  if (allGenerations.length >= res.total) break;
  offset += limit;
}

console.log(`Fetched ${allGenerations.length} generations`);

Image-to-image (img2img)

TypeScript
const res = await client.generate('google/nano-banana', {
  generation_prompt: 'In the style of a watercolor painting',
  generation_input_file: ['https://example.com/input.jpg'],
});
Use client.library.models() to check which models support generation_input_file.

Health monitoring

TypeScript
const providers = await client.health.providers();
for (const p of providers.data.providers) {
  if (p.status !== 'healthy') {
    console.warn(`⚠ ${p.provider}: ${p.status} (${(p.failure_rate * 100).toFixed(1)}% failure rate)`);
  }
}

const [storage, cache] = await Promise.all([client.health.storage(), client.health.cache()]);
console.log(`Storage: ${storage.data.healthy ? '✓' : '✗'} (${storage.data.latency_ms}ms)`);
console.log(`Cache: ${cache.data.healthy ? '✓' : '✗'} (${cache.data.latency_ms}ms)`);