Installation
Quickstart
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
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
| Runtime | Supported | Notes |
|---|
| Node.js 18+ | ✅ | Uses native fetch and crypto.subtle |
| Deno | ✅ | Web standard APIs |
| Bun | ✅ | Web standard APIs |
| Cloudflare Workers | ✅ | Edge runtime compatible |
| Modern Browsers | ✅ | ESM bundle, crypto.subtle required |
| Node.js < 18 | ❌ | No 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
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.
// 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:
// 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:
// 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}
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.
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.
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.
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
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
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.
// 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.
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.
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.
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).
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
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
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
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:
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.
| Parameter | Type | Description |
|---|
rawBody | string | Raw request body — not parsed JSON |
signature | string | Value of the X-BabySea-Signature header |
secret | string | Your webhook secret |
toleranceSeconds | number | Optional — max age in seconds (default: 300) |
Next.js App Router
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.
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
| Event | webhook_data fields |
|---|
generation.started | account_id, model_identifier, generation_id, generation_status: 'processing', generation_provider_initialize, optional generation_prediction_id |
generation.completed | account_id, model_identifier, generation_id, generation_status: 'succeeded', generation_provider_used, generation_output_file, optional generation_prediction_id |
generation.failed | account_id, model_identifier, generation_id, generation_status: 'failed', generation_error, generation_error_code, optional generation_prediction_id, optional generation_provider_used |
generation.canceled | account_id, model_identifier, generation_id, generation_status: 'canceled', optional generation_prediction_id, optional credits_refunded |
credits.low_balance | account_id, current_balance, thresholds_crossed |
webhook.test | account_id, model_identifier, generation_id, generation_status: 'succeeded' |
Type narrowing with the WebhookPayload union:
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:
const payload = await verifyWebhook(rawBody, signature, secret, 600); // 10 minutes
Examples
Batch generation
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
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
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.
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
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)
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
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)`);