Developer API

StoryNote API

Integrate AI image, video, and audio generation into your apps. One API key, all models.

Quick Start

1

Get your API key

Go to Settings in the app and create an API key. Keys start with sk_live_ and are shown only once.

2

Create a project

All generations belong to a project. Create one via the API to get a projectId.

3

Generate & poll

Send a generation request, receive a jobId, then poll or stream for results.

# Create a project
curl -X POST https://api.storynote.ai/v1/projects \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "My First Project"}'

# Generate an image
curl -X POST https://api.storynote.ai/v1/projects/{projectId}/image \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "directPrompt": "A fox reading a book in a cozy library",
    "modelId": "fal-ai/flux-2",
    "aspectRatio": "16:9"
  }'

# Poll for result
curl https://api.storynote.ai/v1/jobs/{jobId} \
  -H "Authorization: Bearer sk_live_your_key_here"

Authentication

All API requests require a Bearer token in the Authorization header. You can also pass it via the X-API-Key header.

# Bearer token (recommended)
curl https://api.storynote.ai/v1/models/available \
  -H "Authorization: Bearer sk_live_your_key_here"

# X-API-Key header (alternative)
curl https://api.storynote.ai/v1/models/available \
  -H "X-API-Key: sk_live_your_key_here"

API keys are created and managed in your Settings modal in the app. Keys begin with sk_live_ and are displayed only at creation. Store them securely.

Image Generation

POST/v1/projects/:projectId/image

Generate one or more images from a text prompt. Returns job IDs that can be polled for results.

Request Body

directPrompt*stringThe image prompt. Bypasses LLM rewriting for exact control.
modelIdstringModel to use. e.g. "fal-ai/flux-2", "nano-banana"
aspectRatiostring"1:1" | "16:9" | "9:16" | "4:3" | "3:4"
numImagesnumberNumber of images to generate (1–8). Default: 1
seednumberReproducibility seed. Each additional image increments by 1.

Response

// 202 Accepted
{
  "jobId": "663f1a2b...",
  "jobIds": ["663f1a2b..."]
}

Video Generation

POST/v1/projects/:projectId/video

Generate a video from a text prompt or an image frame. Supports image-to-video, text-to-video, and keyframe (first+last image) modes.

Request Body

directPromptstringMotion/scene description for the video.
firstFrameobject{ imageUrl: "https://..." } — source image for image-to-video.
lastFrameobject{ imageUrl: "https://..." } — end frame for keyframe mode.
modelIdstringVideo model. e.g. "fal-ai/kling-video/v2.6/pro/image-to-video"
durationSecnumberVideo duration in seconds. Default: 3
aspectRatiostring"16:9" | "9:16" | "1:1" | "4:3" | "3:4"
seednumberReproducibility seed.

Response

// 202 Accepted
{
  "jobId": "663f1a2b..."
}

Audio Generation

POST/v1/projects/:projectId/audio

Generate speech, sound effects, or music. The soundType field determines which parameters are required.

Request Body

soundType*string"speech" | "sfx" | "music"
textstringText to speak. Required for soundType: speech.
promptstringDescription of sound/music. Required for sfx and music.
voiceIdstringElevenLabs voice ID for speech generation.
modelIdstringAudio model override.
durationnumberDuration in seconds (sfx/music).

Response

// 202 Accepted
{
  "jobId": "663f1a2b..."
}

Jobs & Polling

GET/v1/jobs/:id

Get the current status of a generation job. Poll until status is 'completed' or 'failed'.

Response

{
  "id": "663f1a2b...",
  "status": "completed",
  "prompt": "A fox reading a book in a cozy library",
  "model": "fal-ai/flux-2",
  "imageUrl": "https://storage.storynote.ai/...",
  "videoUrl": null,
  "audioUrl": null,
  "seed": 42,
  "error": null,
  "updatedAt": "2025-05-01T12:00:00Z"
}
GET/v1/jobs/batch?jobIds=id1,id2,id3

Fetch multiple jobs in a single request. Up to 256 job IDs per request.

Response

{
  "jobs": {
    "663f1a2b...": { "id": "663f1a2b...", "status": "completed", ... },
    "663f1a2c...": { "id": "663f1a2c...", "status": "processing", ... }
  }
}
GET/v1/jobs/events/streamSSE

Real-time Server-Sent Events stream for all your job updates. More efficient than polling — connect once, receive updates for all jobs.

javascript
const events = new EventSource(
  "https://api.storynote.ai/v1/jobs/events/stream",
  { headers: { "Authorization": "Bearer sk_live_..." } }
);

events.addEventListener("job", (e) => {
  const job = JSON.parse(e.data);
  console.log(job.id, job.status, job.imageUrl);
});

Tip: Use the SSE stream instead of polling individual jobs. It's real-time, uses a single connection, and won't count against your rate limit per job.

Job Statuses

queued

Waiting in queue

processing

Generation in progress

completed

Result ready

failed

Error occurred

Models

GET/v1/models/available

Returns all models available to your account, organized by type (image, video, audio, tools) with credit costs and allowed parameters.

Response

{
  "image": [
    {
      "id": "fal-ai/flux-2",
      "name": "Flux 2",
      "credits": 2,
      "tier": "fast",
      "allowedAspectRatios": ["1:1", "16:9", "9:16", "4:3", "3:4"]
    },
    ...
  ],
  "video": [...],
  "audio": [...]
}

Projects

Every generation must belong to a project. Projects organize your generated assets and can be shared publicly.

POST/v1/projects

Create a new project.

Request Body

name*stringProject name.

Response

{
  "id": "665a3b1c...",
  "name": "My Project",
  "createdAt": "2025-05-01T12:00:00Z"
}
GET/v1/projects

List all your projects.

Response

[
  { "id": "665a3b1c...", "name": "My Project", "createdAt": "..." },
  { "id": "665a3b1d...", "name": "Campaign Assets", "createdAt": "..." }
]
GET/v1/projects/:id

Get a single project with its full graph data.

Response

{
  "id": "665a3b1c...",
  "name": "My Project",
  "nodes": [...],
  "edges": [...],
  "createdAt": "..."
}

Model Pricing

Each generation deducts credits based on the model used. Video models charge per second of output. Purchase credits from the pricing page.

TypeModelCost
ImageZ-Image Turbo1 cr
Flux 22 cr
Seedream v45 cr
Nano Banana6 cr
Recraft V46 cr
Nano Banana 212 cr
Nano Banana Pro35 cr
VideoVeo 3.1 Lite5 cr/s
Pixverse v67 cr/s
Kling v2.6 Pro10 cr/s
AudioElevenLabs Music1 cr
ElevenLabs SFX2 cr
ElevenLabs TTS3 cr
ToolsBackground Removal3 cr
Object Removal3 cr
Image Upscale5 cr

Rate Limits

Global300 req/min

All API endpoints

Generation20 req/min

Image, video, audio endpoints

Uploads50 req/min

File upload signing

SSE ConnectionsPer user limit

Concurrent event streams

When rate limited, the API returns 429 Too Many Requests. Back off and retry with exponential delay.

Errors

All errors return a consistent JSON shape with an error message and HTTP status code.

json
// Error response format
{
  "error": "Insufficient credits",
  "code": 402
}
CodeMeaning
400Bad request — missing or invalid parameters
401Unauthorized — invalid or missing API key
402Payment required — insufficient credits
404Resource not found
429Rate limit exceeded
500Internal server error

Full Examples

End-to-end examples that create a project, generate an image, and retrieve the result.

// Node.js — generate an image and get the result
const API = "https://api.storynote.ai/v1";
const KEY = "sk_live_your_key_here";

const headers = {
  "Authorization": `Bearer ${KEY}`,
  "Content-Type": "application/json",
};

// 1. Create a project
const project = await fetch(${API}/projects`, {
  method: "POST",
  headers,
  body: JSON.stringify({ name: "API Demo" }),
}).then(r => r.json());

// 2. Generate an image
const { jobId } = await fetch(${API}/projects/${project.id}/image`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    directPrompt: "A fox reading a book in a cozy library, warm lighting",
    modelId: "fal-ai/flux-2",
    aspectRatio: "16:9",
  }),
}).then(r => r.json());

// 3. Poll until complete
let job;
do {
  await new Promise(r => setTimeout(r, 2000));
  job = await fetch(${API}/jobs/${jobId}`, { headers }).then(r => r.json());
} while (job.status !== "completed" && job.status !== "failed");

if (job.status === "completed") {
  console.log("Image URL:", job.imageUrl);
} else {
  console.error("Failed:", job.error);
}

Ready to build?

Get your API key and start generating images, videos, and audio in minutes.

Get API Key