@flixly/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/index.cjs +254 -0
- package/dist/index.d.cts +238 -0
- package/dist/index.d.ts +238 -0
- package/dist/index.js +227 -0
- package/package.json +51 -0
- package/src/index.ts +467 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flixly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @flixly/sdk
|
|
2
|
+
|
|
3
|
+
Official JavaScript / TypeScript SDK for the [Flixly](https://www.flixly.ai) AI API. Generate images, video, audio, and chat completions from one client.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @flixly/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+ (uses global `fetch`).
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Flixly } from "@flixly/sdk";
|
|
17
|
+
|
|
18
|
+
const flixly = new Flixly({ apiKey: process.env.FLIXLY_API_KEY! });
|
|
19
|
+
|
|
20
|
+
// Fast: image models return synchronously.
|
|
21
|
+
const result = await flixly.generateAndWait({
|
|
22
|
+
model: "flux-dev",
|
|
23
|
+
prompt: "A cat wearing a top hat, oil painting style",
|
|
24
|
+
type: "TEXT_TO_IMAGE",
|
|
25
|
+
input: { aspect_ratio: "1:1", resolution: "1K" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(result.data.output_url); // cdn.flixly.ai URL
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Get an API key at [www.flixly.ai/dashboard/settings/api-keys](https://www.flixly.ai/dashboard/settings/api-keys).
|
|
32
|
+
|
|
33
|
+
## Webhooks (recommended for video / slow models)
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
await flixly.generate({
|
|
37
|
+
model: "veo-3-fast",
|
|
38
|
+
prompt: "Cinematic shot of mountains at dawn",
|
|
39
|
+
type: "TEXT_TO_VIDEO",
|
|
40
|
+
webhook_url: "https://example.com/flixly-webhook",
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Verify incoming webhook requests:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// In your webhook handler — Express, Hono, Next.js, etc.
|
|
48
|
+
import { Flixly } from "@flixly/sdk";
|
|
49
|
+
|
|
50
|
+
const valid = await Flixly.verifyWebhookSignature({
|
|
51
|
+
secret: process.env.FLIXLY_WEBHOOK_SECRET!,
|
|
52
|
+
timestamp: req.headers["x-flixly-timestamp"]!,
|
|
53
|
+
signature: req.headers["x-flixly-signature"]!,
|
|
54
|
+
body: rawBody, // pass the unmodified request body — JSON.parse + restringify will break verification
|
|
55
|
+
});
|
|
56
|
+
if (!valid) return res.status(401).end();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The webhook secret is shown once when you create the API key. Store it on your server like any other secret.
|
|
60
|
+
|
|
61
|
+
## Rate limits
|
|
62
|
+
|
|
63
|
+
Every response includes parsed rate-limit info — use it to pace yourself before a 429:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const res = await flixly.listModels();
|
|
67
|
+
console.log(res.rateLimit);
|
|
68
|
+
// { limit: 60, remaining: 42, resetAtSec: 1735056000 }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Chat (OpenAI-compatible)
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// Non-streaming
|
|
75
|
+
const res = await flixly.chat({
|
|
76
|
+
model: "gpt-5-4-mini",
|
|
77
|
+
messages: [
|
|
78
|
+
{ role: "user", content: "Explain async/await in one sentence." },
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
console.log(res.data.choices[0].message.content);
|
|
82
|
+
|
|
83
|
+
// Streaming
|
|
84
|
+
for await (const chunk of flixly.chatStream({
|
|
85
|
+
model: "gpt-5-4-mini",
|
|
86
|
+
messages: [{ role: "user", content: "Write a haiku about TypeScript" }],
|
|
87
|
+
})) {
|
|
88
|
+
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Errors
|
|
93
|
+
|
|
94
|
+
API errors throw `FlixlyError` with the response code, status, and details:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { Flixly, FlixlyError } from "@flixly/sdk";
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await flixly.generate({ model: "flux-dev", prompt: "" });
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (err instanceof FlixlyError) {
|
|
103
|
+
console.log(err.code, err.status, err.message);
|
|
104
|
+
// "invalid_request" 400 "prompt is required"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Configuration
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
new Flixly({
|
|
113
|
+
apiKey: "flx_live_...",
|
|
114
|
+
baseUrl: "https://www.flixly.ai", // default
|
|
115
|
+
timeoutMs: 120_000, // default 2 minutes
|
|
116
|
+
fetch: customFetch, // override (e.g. for proxies)
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Spending caps & API key management
|
|
121
|
+
|
|
122
|
+
Set a per-key monthly credit limit, configure default webhook URLs, and rotate webhook secrets at [/dashboard/settings/api-keys](https://www.flixly.ai/dashboard/settings/api-keys).
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BASE_URL: () => BASE_URL,
|
|
24
|
+
Flixly: () => Flixly,
|
|
25
|
+
FlixlyError: () => FlixlyError
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var BASE_URL = "https://www.flixly.ai";
|
|
29
|
+
var FlixlyError = class extends Error {
|
|
30
|
+
constructor(args) {
|
|
31
|
+
super(args.message);
|
|
32
|
+
this.name = "FlixlyError";
|
|
33
|
+
this.status = args.status;
|
|
34
|
+
this.code = args.code;
|
|
35
|
+
this.details = args.details;
|
|
36
|
+
this.rateLimit = args.rateLimit;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var Flixly = class {
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
if (!opts?.apiKey) throw new Error("Flixly: `apiKey` is required");
|
|
42
|
+
this.apiKey = opts.apiKey;
|
|
43
|
+
this.baseUrl = (opts.baseUrl || BASE_URL).replace(/\/$/, "");
|
|
44
|
+
this.fetchImpl = opts.fetch || globalThis.fetch;
|
|
45
|
+
this.timeoutMs = opts.timeoutMs ?? 12e4;
|
|
46
|
+
if (!this.fetchImpl) {
|
|
47
|
+
throw new Error("Flixly: no fetch implementation. Pass `fetch` in options on Node < 18.");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// ─── Generations ───────────────────────────────────────────────
|
|
51
|
+
/** Submit a generation. Returns immediately with `processing` for async models, or `completed` for fast sync models. */
|
|
52
|
+
async generate(req) {
|
|
53
|
+
return this.request("POST", "/api/v1/generate", req);
|
|
54
|
+
}
|
|
55
|
+
/** Fetch the current state of a generation. */
|
|
56
|
+
async getGeneration(id) {
|
|
57
|
+
return this.request("GET", `/api/v1/generations/${encodeURIComponent(id)}`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Submit a generation and poll until it completes or fails.
|
|
61
|
+
* Useful for short pipelines / one-shot scripts. Prefer
|
|
62
|
+
* `generate({ webhook_url })` in production.
|
|
63
|
+
*/
|
|
64
|
+
async generateAndWait(req, opts = {}) {
|
|
65
|
+
const start = Date.now();
|
|
66
|
+
const interval = opts.pollIntervalMs ?? 2e3;
|
|
67
|
+
const maxWait = opts.maxWaitMs ?? 10 * 6e4;
|
|
68
|
+
const initial = await this.generate(req);
|
|
69
|
+
if (initial.data.status === "completed" || initial.data.status === "failed") {
|
|
70
|
+
return initial;
|
|
71
|
+
}
|
|
72
|
+
let last = initial;
|
|
73
|
+
while (Date.now() - start < maxWait) {
|
|
74
|
+
await sleep(interval);
|
|
75
|
+
last = await this.getGeneration(initial.data.id);
|
|
76
|
+
if (last.data.status === "completed" || last.data.status === "failed") {
|
|
77
|
+
return last;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new FlixlyError({
|
|
81
|
+
status: 408,
|
|
82
|
+
code: "timeout",
|
|
83
|
+
message: `Generation ${initial.data.id} did not finish within ${Math.round(maxWait / 1e3)}s. Last status: ${last.data.status}.`,
|
|
84
|
+
rateLimit: last.rateLimit
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// ─── Models ────────────────────────────────────────────────────
|
|
88
|
+
/** List all available models. Includes per-model estimated_credits when authenticated. */
|
|
89
|
+
async listModels() {
|
|
90
|
+
return this.request("GET", "/api/v1/models");
|
|
91
|
+
}
|
|
92
|
+
// ─── Chat ──────────────────────────────────────────────────────
|
|
93
|
+
/** OpenAI-compatible chat completion (non-streaming). */
|
|
94
|
+
async chat(req) {
|
|
95
|
+
return this.request("POST", "/api/v1/chat/completions", { ...req, stream: false });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* OpenAI-compatible streaming chat completion. Returns an async
|
|
99
|
+
* iterable of `chat.completion.chunk` events as parsed JSON. The
|
|
100
|
+
* stream terminates on `[DONE]`.
|
|
101
|
+
*/
|
|
102
|
+
async *chatStream(req) {
|
|
103
|
+
const url = `${this.baseUrl}/api/v1/chat/completions`;
|
|
104
|
+
const ac = new AbortController();
|
|
105
|
+
const timer = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
106
|
+
let res;
|
|
107
|
+
try {
|
|
108
|
+
res = await this.fetchImpl(url, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: this.headers({ "Content-Type": "application/json" }),
|
|
111
|
+
body: JSON.stringify({ ...req, stream: true }),
|
|
112
|
+
signal: ac.signal
|
|
113
|
+
});
|
|
114
|
+
} finally {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
}
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
throw await responseToError(res);
|
|
119
|
+
}
|
|
120
|
+
if (!res.body) throw new Error("Flixly: streaming response has no body");
|
|
121
|
+
const reader = res.body.getReader();
|
|
122
|
+
const decoder = new TextDecoder();
|
|
123
|
+
let buf = "";
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done) return;
|
|
127
|
+
buf += decoder.decode(value, { stream: true });
|
|
128
|
+
const lines = buf.split("\n");
|
|
129
|
+
buf = lines.pop() || "";
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
if (!trimmed || !trimmed.startsWith("data:")) continue;
|
|
133
|
+
const payload = trimmed.slice(5).trim();
|
|
134
|
+
if (payload === "[DONE]") return;
|
|
135
|
+
try {
|
|
136
|
+
yield JSON.parse(payload);
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ─── Account ───────────────────────────────────────────────────
|
|
143
|
+
/** Credit balance, plan, parallel + rate limits, and aggregate usage. */
|
|
144
|
+
async getAccount() {
|
|
145
|
+
return this.request("GET", "/api/v1/account");
|
|
146
|
+
}
|
|
147
|
+
// ─── Webhook signature verification (static) ───────────────────
|
|
148
|
+
/**
|
|
149
|
+
* Verify a Flixly webhook delivery. Returns true if and only if the
|
|
150
|
+
* signature is valid AND the timestamp is within `tolerance` seconds.
|
|
151
|
+
*
|
|
152
|
+
* Call this on EVERY webhook POST you receive — without it, anyone
|
|
153
|
+
* who guesses the URL can forge a delivery.
|
|
154
|
+
*/
|
|
155
|
+
static async verifyWebhookSignature(args) {
|
|
156
|
+
if (!args.signature?.startsWith("sha256=")) return false;
|
|
157
|
+
const provided = args.signature.slice(7).toLowerCase();
|
|
158
|
+
const ts = Number(args.timestamp);
|
|
159
|
+
if (!Number.isFinite(ts)) return false;
|
|
160
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
161
|
+
const tolerance = args.tolerance ?? 300;
|
|
162
|
+
if (Math.abs(now - ts) > tolerance) return false;
|
|
163
|
+
const bodyStr = typeof args.body === "string" ? args.body : new TextDecoder().decode(args.body);
|
|
164
|
+
const enc = new TextEncoder();
|
|
165
|
+
const key = await crypto.subtle.importKey(
|
|
166
|
+
"raw",
|
|
167
|
+
enc.encode(args.secret),
|
|
168
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
169
|
+
false,
|
|
170
|
+
["sign"]
|
|
171
|
+
);
|
|
172
|
+
const sigBuf = await crypto.subtle.sign("HMAC", key, enc.encode(`${args.timestamp}.${bodyStr}`));
|
|
173
|
+
const expectedHex = bufferToHex(sigBuf);
|
|
174
|
+
return timingSafeEqual(expectedHex, provided);
|
|
175
|
+
}
|
|
176
|
+
// ─── Internals ─────────────────────────────────────────────────
|
|
177
|
+
headers(extra = {}) {
|
|
178
|
+
return {
|
|
179
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
180
|
+
"User-Agent": "flixly-js/0.1.0",
|
|
181
|
+
...extra
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async request(method, path, body) {
|
|
185
|
+
const url = `${this.baseUrl}${path}`;
|
|
186
|
+
const ac = new AbortController();
|
|
187
|
+
const timer = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
188
|
+
let res;
|
|
189
|
+
try {
|
|
190
|
+
res = await this.fetchImpl(url, {
|
|
191
|
+
method,
|
|
192
|
+
headers: this.headers(body ? { "Content-Type": "application/json" } : {}),
|
|
193
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
194
|
+
signal: ac.signal
|
|
195
|
+
});
|
|
196
|
+
} finally {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
}
|
|
199
|
+
const rateLimit = parseRateLimit(res.headers);
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
throw await responseToError(res, rateLimit);
|
|
202
|
+
}
|
|
203
|
+
const data = await res.json();
|
|
204
|
+
return { data, rateLimit };
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
function parseRateLimit(h) {
|
|
208
|
+
const limit = Number(h.get("X-RateLimit-Limit"));
|
|
209
|
+
const remaining = Number(h.get("X-RateLimit-Remaining"));
|
|
210
|
+
const resetAtSec = Number(h.get("X-RateLimit-Reset"));
|
|
211
|
+
if (!Number.isFinite(limit) || !Number.isFinite(remaining) || !Number.isFinite(resetAtSec)) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return { limit, remaining, resetAtSec };
|
|
215
|
+
}
|
|
216
|
+
async function responseToError(res, rateLimit) {
|
|
217
|
+
const rl = rateLimit ?? parseRateLimit(res.headers);
|
|
218
|
+
let code = "internal_error";
|
|
219
|
+
let message = `HTTP ${res.status}`;
|
|
220
|
+
let details;
|
|
221
|
+
try {
|
|
222
|
+
const body = await res.json();
|
|
223
|
+
if (body?.error) {
|
|
224
|
+
code = body.error.code || code;
|
|
225
|
+
message = body.error.message || message;
|
|
226
|
+
details = body.error.details;
|
|
227
|
+
}
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
return new FlixlyError({ status: res.status, code, message, details, rateLimit: rl });
|
|
231
|
+
}
|
|
232
|
+
function sleep(ms) {
|
|
233
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
234
|
+
}
|
|
235
|
+
function bufferToHex(buf) {
|
|
236
|
+
const bytes = new Uint8Array(buf);
|
|
237
|
+
let hex = "";
|
|
238
|
+
for (const b of bytes) hex += b.toString(16).padStart(2, "0");
|
|
239
|
+
return hex;
|
|
240
|
+
}
|
|
241
|
+
function timingSafeEqual(a, b) {
|
|
242
|
+
if (a.length !== b.length) return false;
|
|
243
|
+
let diff = 0;
|
|
244
|
+
for (let i = 0; i < a.length; i++) {
|
|
245
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
246
|
+
}
|
|
247
|
+
return diff === 0;
|
|
248
|
+
}
|
|
249
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
250
|
+
0 && (module.exports = {
|
|
251
|
+
BASE_URL,
|
|
252
|
+
Flixly,
|
|
253
|
+
FlixlyError
|
|
254
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @flixly/sdk — Official SDK for the Flixly AI API.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { Flixly } from "@flixly/sdk";
|
|
7
|
+
*
|
|
8
|
+
* const flixly = new Flixly({ apiKey: process.env.FLIXLY_API_KEY! });
|
|
9
|
+
*
|
|
10
|
+
* // Quick: kick off a generation and wait for it.
|
|
11
|
+
* const result = await flixly.generateAndWait({
|
|
12
|
+
* model: "flux-dev",
|
|
13
|
+
* prompt: "A cat wearing a top hat, oil painting style",
|
|
14
|
+
* type: "TEXT_TO_IMAGE",
|
|
15
|
+
* input: { aspect_ratio: "1:1", resolution: "1K" },
|
|
16
|
+
* });
|
|
17
|
+
* console.log(result.output_url);
|
|
18
|
+
*
|
|
19
|
+
* // Or: subscribe via webhook (preferred for video / slow models).
|
|
20
|
+
* await flixly.generate({
|
|
21
|
+
* model: "veo-3-fast",
|
|
22
|
+
* prompt: "Cinematic shot of mountains at dawn",
|
|
23
|
+
* webhook_url: "https://example.com/flixly-webhook",
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Verify incoming webhooks:
|
|
27
|
+
* const valid = await Flixly.verifyWebhookSignature({
|
|
28
|
+
* secret: process.env.FLIXLY_WEBHOOK_SECRET!,
|
|
29
|
+
* timestamp: req.headers["x-flixly-timestamp"]!,
|
|
30
|
+
* signature: req.headers["x-flixly-signature"]!,
|
|
31
|
+
* body: req.rawBody,
|
|
32
|
+
* });
|
|
33
|
+
* if (!valid) throw new Error("invalid signature");
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare const BASE_URL = "https://www.flixly.ai";
|
|
37
|
+
type TaskType = "TEXT_TO_IMAGE" | "IMAGE_TO_IMAGE" | "TEXT_TO_VIDEO" | "IMAGE_TO_VIDEO" | "VIDEO_TO_VIDEO" | "TEXT_TO_SPEECH" | "VOICE_CLONE" | "MUSIC_GENERATION" | "UPSCALE" | "REMOVE_BACKGROUND";
|
|
38
|
+
type GenerationStatus = "pending" | "processing" | "completed" | "failed";
|
|
39
|
+
interface GenerateInput {
|
|
40
|
+
aspect_ratio?: string;
|
|
41
|
+
resolution?: string;
|
|
42
|
+
duration?: string | number;
|
|
43
|
+
image_url?: string;
|
|
44
|
+
video_url?: string;
|
|
45
|
+
audio_url?: string;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
interface GenerateRequest {
|
|
49
|
+
/** Model id from `listModels()`. */
|
|
50
|
+
model: string;
|
|
51
|
+
/** Text prompt. */
|
|
52
|
+
prompt: string;
|
|
53
|
+
/** Task type. Inferred from the model when omitted. */
|
|
54
|
+
type?: TaskType;
|
|
55
|
+
/** Model-specific parameters (aspect ratio, resolution, image_url, etc.). */
|
|
56
|
+
input?: GenerateInput;
|
|
57
|
+
/**
|
|
58
|
+
* If set, we POST a signed event to this URL when the generation
|
|
59
|
+
* finishes. Overrides the key's default. Verify with
|
|
60
|
+
* `Flixly.verifyWebhookSignature()`.
|
|
61
|
+
*/
|
|
62
|
+
webhook_url?: string;
|
|
63
|
+
}
|
|
64
|
+
interface Generation {
|
|
65
|
+
id: string;
|
|
66
|
+
status: GenerationStatus;
|
|
67
|
+
type: string;
|
|
68
|
+
model: string | null;
|
|
69
|
+
output_url: string | null;
|
|
70
|
+
status_url: string;
|
|
71
|
+
webhook_url?: string | null;
|
|
72
|
+
credits_charged: number;
|
|
73
|
+
error?: string | null;
|
|
74
|
+
created_at: string;
|
|
75
|
+
completed_at: string | null;
|
|
76
|
+
message?: string;
|
|
77
|
+
}
|
|
78
|
+
interface ChatMessage {
|
|
79
|
+
role: "system" | "user" | "assistant";
|
|
80
|
+
content: string | Array<{
|
|
81
|
+
type: string;
|
|
82
|
+
text?: string;
|
|
83
|
+
image_url?: {
|
|
84
|
+
url: string;
|
|
85
|
+
};
|
|
86
|
+
}>;
|
|
87
|
+
}
|
|
88
|
+
interface ChatRequest {
|
|
89
|
+
model: string;
|
|
90
|
+
messages: ChatMessage[];
|
|
91
|
+
stream?: boolean;
|
|
92
|
+
max_tokens?: number;
|
|
93
|
+
temperature?: number;
|
|
94
|
+
}
|
|
95
|
+
interface ChatResponse {
|
|
96
|
+
id: string;
|
|
97
|
+
object: "chat.completion";
|
|
98
|
+
created: number;
|
|
99
|
+
model: string;
|
|
100
|
+
choices: Array<{
|
|
101
|
+
index: number;
|
|
102
|
+
message: {
|
|
103
|
+
role: string;
|
|
104
|
+
content: string;
|
|
105
|
+
};
|
|
106
|
+
finish_reason: string;
|
|
107
|
+
}>;
|
|
108
|
+
usage: {
|
|
109
|
+
prompt_tokens: number;
|
|
110
|
+
completion_tokens: number;
|
|
111
|
+
total_tokens: number;
|
|
112
|
+
};
|
|
113
|
+
credits_charged: number;
|
|
114
|
+
}
|
|
115
|
+
interface FlixlyModel {
|
|
116
|
+
id: string;
|
|
117
|
+
name: string;
|
|
118
|
+
type: string;
|
|
119
|
+
capabilities: string[];
|
|
120
|
+
estimated_credits?: number | null;
|
|
121
|
+
estimated_credits_per_1k_tokens?: number | null;
|
|
122
|
+
}
|
|
123
|
+
interface Account {
|
|
124
|
+
credits: {
|
|
125
|
+
subscription: number;
|
|
126
|
+
bonus: number;
|
|
127
|
+
total: number;
|
|
128
|
+
};
|
|
129
|
+
plan: {
|
|
130
|
+
name: string;
|
|
131
|
+
parallel_limit: number;
|
|
132
|
+
rate_limit: number;
|
|
133
|
+
renews_at: string | null;
|
|
134
|
+
};
|
|
135
|
+
usage: {
|
|
136
|
+
total_requests: number;
|
|
137
|
+
total_credits_used: number;
|
|
138
|
+
completed: number;
|
|
139
|
+
failed: number;
|
|
140
|
+
pending: number;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
interface RateLimit {
|
|
144
|
+
/** Requests allowed per minute on this key's plan. */
|
|
145
|
+
limit: number;
|
|
146
|
+
/** Requests left in the current 60s window at the time of the response. */
|
|
147
|
+
remaining: number;
|
|
148
|
+
/** Unix epoch seconds when the window resets. */
|
|
149
|
+
resetAtSec: number;
|
|
150
|
+
}
|
|
151
|
+
/** A successful API response, with rate-limit headers parsed out. */
|
|
152
|
+
interface FlixlyResponse<T> {
|
|
153
|
+
data: T;
|
|
154
|
+
rateLimit: RateLimit | null;
|
|
155
|
+
}
|
|
156
|
+
declare class FlixlyError extends Error {
|
|
157
|
+
readonly status: number;
|
|
158
|
+
readonly code: string;
|
|
159
|
+
readonly details?: Record<string, unknown>;
|
|
160
|
+
readonly rateLimit: RateLimit | null;
|
|
161
|
+
constructor(args: {
|
|
162
|
+
status: number;
|
|
163
|
+
code: string;
|
|
164
|
+
message: string;
|
|
165
|
+
details?: Record<string, unknown>;
|
|
166
|
+
rateLimit: RateLimit | null;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
interface FlixlyOptions {
|
|
170
|
+
apiKey: string;
|
|
171
|
+
/** Override the API base URL (defaults to https://www.flixly.ai). */
|
|
172
|
+
baseUrl?: string;
|
|
173
|
+
/** Custom fetch implementation. Defaults to global `fetch`. */
|
|
174
|
+
fetch?: typeof fetch;
|
|
175
|
+
/** Per-request timeout in ms. Defaults to 120_000 (2 minutes). */
|
|
176
|
+
timeoutMs?: number;
|
|
177
|
+
}
|
|
178
|
+
declare class Flixly {
|
|
179
|
+
private readonly apiKey;
|
|
180
|
+
private readonly baseUrl;
|
|
181
|
+
private readonly fetchImpl;
|
|
182
|
+
private readonly timeoutMs;
|
|
183
|
+
constructor(opts: FlixlyOptions);
|
|
184
|
+
/** Submit a generation. Returns immediately with `processing` for async models, or `completed` for fast sync models. */
|
|
185
|
+
generate(req: GenerateRequest): Promise<FlixlyResponse<Generation>>;
|
|
186
|
+
/** Fetch the current state of a generation. */
|
|
187
|
+
getGeneration(id: string): Promise<FlixlyResponse<Generation>>;
|
|
188
|
+
/**
|
|
189
|
+
* Submit a generation and poll until it completes or fails.
|
|
190
|
+
* Useful for short pipelines / one-shot scripts. Prefer
|
|
191
|
+
* `generate({ webhook_url })` in production.
|
|
192
|
+
*/
|
|
193
|
+
generateAndWait(req: GenerateRequest, opts?: {
|
|
194
|
+
pollIntervalMs?: number;
|
|
195
|
+
maxWaitMs?: number;
|
|
196
|
+
}): Promise<FlixlyResponse<Generation>>;
|
|
197
|
+
/** List all available models. Includes per-model estimated_credits when authenticated. */
|
|
198
|
+
listModels(): Promise<FlixlyResponse<{
|
|
199
|
+
models: FlixlyModel[];
|
|
200
|
+
total: number;
|
|
201
|
+
}>>;
|
|
202
|
+
/** OpenAI-compatible chat completion (non-streaming). */
|
|
203
|
+
chat(req: Omit<ChatRequest, "stream"> & {
|
|
204
|
+
stream?: false;
|
|
205
|
+
}): Promise<FlixlyResponse<ChatResponse>>;
|
|
206
|
+
/**
|
|
207
|
+
* OpenAI-compatible streaming chat completion. Returns an async
|
|
208
|
+
* iterable of `chat.completion.chunk` events as parsed JSON. The
|
|
209
|
+
* stream terminates on `[DONE]`.
|
|
210
|
+
*/
|
|
211
|
+
chatStream(req: Omit<ChatRequest, "stream">): AsyncIterable<any>;
|
|
212
|
+
/** Credit balance, plan, parallel + rate limits, and aggregate usage. */
|
|
213
|
+
getAccount(): Promise<FlixlyResponse<Account>>;
|
|
214
|
+
/**
|
|
215
|
+
* Verify a Flixly webhook delivery. Returns true if and only if the
|
|
216
|
+
* signature is valid AND the timestamp is within `tolerance` seconds.
|
|
217
|
+
*
|
|
218
|
+
* Call this on EVERY webhook POST you receive — without it, anyone
|
|
219
|
+
* who guesses the URL can forge a delivery.
|
|
220
|
+
*/
|
|
221
|
+
static verifyWebhookSignature(args: VerifyWebhookArgs): Promise<boolean>;
|
|
222
|
+
private headers;
|
|
223
|
+
private request;
|
|
224
|
+
}
|
|
225
|
+
interface VerifyWebhookArgs {
|
|
226
|
+
/** The HMAC secret you stored from your API key's webhook settings. */
|
|
227
|
+
secret: string;
|
|
228
|
+
/** The `X-Flixly-Timestamp` request header value (unix seconds, string). */
|
|
229
|
+
timestamp: string;
|
|
230
|
+
/** The `X-Flixly-Signature` request header value (`sha256=<hex>`). */
|
|
231
|
+
signature: string;
|
|
232
|
+
/** Raw request body bytes or string. MUST be unmodified — JSON.parse + re-stringify will break verification. */
|
|
233
|
+
body: string | Uint8Array;
|
|
234
|
+
/** Reject signatures older than this many seconds. Defaults to 300 (5 minutes) to prevent replays. */
|
|
235
|
+
tolerance?: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { type Account, BASE_URL, type ChatMessage, type ChatRequest, type ChatResponse, Flixly, FlixlyError, type FlixlyModel, type FlixlyOptions, type FlixlyResponse, type GenerateInput, type GenerateRequest, type Generation, type GenerationStatus, type RateLimit, type TaskType, type VerifyWebhookArgs };
|