Authentication
All API requests are authenticated using an API key passed in the X-API-Key header. API access is available on Pro and Lifetime plans.
Header
X-API-Key: ft_live_your_key_hereGetting a key: Go to Profile → API and click "Create Key".
Key format: Keys start with ft_live_ followed by 32 hex characters.
Security: Keys are shown only once at creation. Store them securely. Never expose keys in client-side code.
Endpoints
Base URL: https://pub.finetuning.ai
/v1/meGet your account info and remaining credits.
Response
{
"data": {
"id": "usr_abc123",
"email": "you@example.com",
"name": "Your Name",
"tier": "pro",
"limits": {
"monthlyGenerations": 2000,
"generationsUsed": 47,
"monthlyRemaining": 1953,
"packCredits": 0,
"totalRemaining": 1953,
"queueDepth": 5
}
}
}/v1/generationsCreate a new music generation. Deducts one credit and submits to the generation queue.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| tags | string | required | Music description (max 500 chars). E.g. "lofi chill piano night" |
| lyrics | string | optional | Optional lyrics (max 2000 chars) |
| duration | number | optional | Duration in seconds (10-180, default 60) |
| bpm | number | optional | Tempo (60-200, default 120) |
| language | string | optional | Language code: en, ja, de, fr, es, zh, ko, pt, it, ru |
| key | string | optional | Musical key: C, C#, D, ... B |
| scale | string | optional | Scale: major or minor |
| timesignature | string | optional | Time signature: 2-7 (default 4) |
| seed | number | optional | Reproducibility seed (random if omitted) |
Response
{
"data": {
"id": "gen_xyz789",
"status": "processing",
"prompt": "lofi chill piano night",
"parameters": {
"bpm": 85,
"duration": 120,
"timesignature": "4",
"language": "en",
"keyscale": "C minor",
"seed": 1234567
},
"creditsRemaining": 1952,
"createdAt": "2025-01-15T10:30:00Z"
}
}/v1/generationsList your generations with pagination and optional status filtering.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| limit | number | optional | Results per page (1-100, default 20) |
| offset | number | optional | Pagination offset (default 0) |
| status | string | optional | Filter by status: pending, processing, completed, failed |
Response
{
"data": {
"generations": [
{
"id": "gen_xyz789",
"title": "Lofi Chill Piano",
"prompt": "lofi chill piano night",
"status": "completed",
"audioUrl": "https://media.finetuning.ai/...",
"duration": 120,
"isPublic": false,
"playCount": 5,
"likeCount": 0,
"parameters": { "bpm": 85, "duration": 120, ... },
"createdAt": "2025-01-15T10:30:00Z",
"completedAt": "2025-01-15T10:30:08Z"
}
],
"hasMore": true,
"nextOffset": 20
}
}/v1/generations/:idGet detailed info about a single generation including audio URL, parameters, and statistics.
Response
{
"data": {
"id": "gen_xyz789",
"title": "Lofi Chill Piano",
"prompt": "lofi chill piano night",
"status": "completed",
"audioUrl": "https://media.finetuning.ai/...",
"duration": 120,
"fileSize": 1920000,
"isPublic": false,
"playCount": 5,
"downloadCount": 2,
"likeCount": 0,
"parameters": { ... },
"generationTime": 7.2,
"createdAt": "2025-01-15T10:30:00Z",
"completedAt": "2025-01-15T10:30:08Z"
}
}Rate Limits
60
Requests per minute per user
5
Active API keys per account
Rate limit info is included in response headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
Retry-After: 42 (only on 429 responses)
If you exceed the limit, you'll receive a 429 Too Many Requests response with a Retry-After header.
Error Codes
All errors follow the format: {"error": {"code": "...", "message": "..."}}
| Code | Status | Description |
|---|---|---|
| MISSING_API_KEY | 401 | X-API-Key header was not provided |
| INVALID_API_KEY | 401 | API key is malformed, revoked, or does not exist |
| PRO_PLAN_REQUIRED | 403 | Your subscription does not include API access |
| RATE_LIMITED | 429 | Too many requests — wait and retry |
| VALIDATION_ERROR | 400 | Request body is missing or has invalid fields |
| MONTHLY_LIMIT_REACHED | 402 | No generations remaining this month |
| QUEUE_FULL | 429 | Too many generations in progress |
| GENERATION_FAILED | 500 | Generation queue submission failed |
| NOT_FOUND | 404 | Resource not found |
| INTERNAL_ERROR | 500 | Unexpected server error |
Code Examples
curl
# Generate a track
curl -X POST https://pub.finetuning.ai/v1/generations \
-H "X-API-Key: ft_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"tags": "epic orchestral cinematic trailer",
"duration": 180,
"bpm": 140,
"key": "D",
"scale": "minor"
}'
# Check generation status
curl https://pub.finetuning.ai/v1/generations/gen_xyz789 \
-H "X-API-Key: ft_live_your_key_here"
# List your generations
curl "https://pub.finetuning.ai/v1/generations?limit=10&status=completed" \
-H "X-API-Key: ft_live_your_key_here"TypeScript / JavaScript
const API_KEY = process.env.FINETUNING_API_KEY;
const BASE = "https://pub.finetuning.ai";
// Generate a track
const res = await fetch(`${BASE}/v1/generations`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
tags: "lofi chill piano rain",
duration: 120,
bpm: 85,
}),
});
const { data } = await res.json();
console.log("Generation started:", data.id);
// Poll for completion
const poll = async (id: string) => {
const r = await fetch(`${BASE}/v1/generations/${id}`, {
headers: { "X-API-Key": API_KEY },
});
const { data } = await r.json();
if (data.status === "completed") {
console.log("Audio URL:", data.audioUrl);
} else if (data.status === "failed") {
console.error("Failed:", data.errorMessage);
} else {
setTimeout(() => poll(id), 3000);
}
};
poll(data.id);Python
import requests
import time
import os
API_KEY = os.environ["FINETUNING_API_KEY"]
BASE = "https://pub.finetuning.ai"
headers = {"X-API-Key": API_KEY}
# Generate a track
res = requests.post(f"{BASE}/v1/generations", headers=headers, json={
"tags": "jazz smooth saxophone evening",
"duration": 90,
"bpm": 100,
})
gen = res.json()["data"]
print(f"Generation started: {gen['id']}")
# Poll for completion
while True:
r = requests.get(f"{BASE}/v1/generations/{gen['id']}", headers=headers)
data = r.json()["data"]
if data["status"] == "completed":
print(f"Audio: {data['audioUrl']}")
break
elif data["status"] == "failed":
print(f"Error: {data.get('errorMessage')}")
break
time.sleep(3)