API Reference
The MaestroAI API identifies musical instruments in audio files using AI analysis.
All responses are JSON. Base URL: https://your-domain.com
Overview
The MaestroAI REST API lets you identify instruments in audio files, manage analysis history, create shareable results, and integrate via webhooks.
Base URL: All endpoints are relative to your deployment URL. For local development: http://localhost:8000
Supported Audio Formats
MP3, WAV, FLAC, OGG, AAC, M4A, WMA — up to 50 MB per file.
Analysis Modes
| Mode | Description | Credits |
| offline | Rule-based, 30 instruments, no credits needed, instant | Free |
| ai | Full AI pipeline, highest accuracy, 50+ instruments | 1 credit |
| auto | Uses AI if credits available, otherwise offline | 1 credit |
Authentication
MaestroAI supports two authentication methods:
Method 1 — JWT Bearer Token (browser / server)
Authorization: Bearer <your_jwt_token>
Method 2 — API Key (integrations / scripts)
X-API-Key: msk_xxxxxxxxxxxxxxxxxxxxxxxx
API Key limits: Each key allows 1,000 requests per day by default. Rate limit resets at midnight UTC.
Exceed the limit and you'll receive a 429 Too Many Requests response.
Errors & Rate Limits
| Status | Meaning |
| 200 | Success |
| 201 | Resource created |
| 202 | Accepted — async job queued |
| 204 | No content — operation succeeded with no body |
| 400 | Bad request — invalid input |
| 401 | Unauthorized — missing or invalid credentials |
| 402 | Payment required — no credits remaining |
| 404 | Not found |
| 422 | Validation error — e.g. password too short |
| 429 | Rate limit exceeded |
| 500 | Server error |
All error responses include a detail field with a human-readable message.
"detail": "You have no credits remaining. Use ?mode=offline for free rule-based analysis."
IP-based Rate Limits
| Endpoint | Limit |
| /api/auth/register | 5 / minute per IP |
| /api/auth/login | 10 / minute per IP |
| /api/auth/forgot-password | 3 / minute per IP |
| /api/analyze | 30 / minute per IP |
| /api/analyze/bulk | 5 / minute per IP |
Auth
Creates a new user account. Returns a JWT token and sends a welcome email.
Request body (JSON)
| Field | Type | Required | Description |
| email | string | required | Valid email address |
| password | string | required | Minimum 8 characters |
201 Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"user": {
"id": 1,
"email": "you@example.com",
"credits": 1
}
}
cURL
curl -X POST https://your-domain.com/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"securepass"}'
Authenticates with email and password. Returns a JWT token valid for 7 days.
Request body (JSON)
| Field | Type | Required | Description |
| email | string | required | Registered email |
| password | string | required | Account password |
200 Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"user": { "id": 1, "email": "you@example.com", "credits": 5 }
}
Sends a password-reset email if the address is registered. Always returns 202 to prevent email enumeration.
Request body (JSON)
| Field | Type | Required | Description |
| email | string | required | Email to send the reset link to |
202 Response
{ "message": "If that email is registered, you will receive a reset link shortly." }
Consumes a one-time reset token (expires 1 hour after issue) and sets the new password.
Request body (JSON)
| Field | Type | Required | Description |
| token | string | required | Token from the reset email URL |
| new_password | string | required | New password, minimum 8 characters |
204 No content on success.
User
200 Response
{
"id": 1,
"email": "you@example.com",
"credits": 5,
"is_admin": false,
"member_since": "2026-01-15T10:30:00"
}
Request body (JSON)
| Field | Type | Required | Description |
| current_password | string | required | Your current password |
| new_password | string | required | New password, minimum 8 characters |
204 No content on success.
Irreversible. All analyses, API keys, webhooks, and payments are deleted. This cannot be undone.
204 No content on success.
Analysis
Upload an audio file for instrument identification. Returns the result synchronously, or a job ID if async_mode=true.
Query parameters
| Parameter | Type | Default | Description |
| mode | string | auto | auto | offline | ai |
| async_mode | bool | false | If true, returns 202 with a job_id immediately |
| rights_attested | bool | required | Must be true — you attest that you own the file or have the right to analyse it (see Terms of Use). Requests without it are rejected with 400. |
| window_start | float | auto | Optional section start in seconds. Requires window_end and deep analysis mode. Without it, the most energetic 90s window is auto-detected. |
| window_end | float | auto | Optional section end in seconds. Span must be 5–90 seconds. |
Form data
| Field | Type | Required | Description |
| file | file | required | Audio file (mp3, wav, flac, ogg, aac, m4a, wma) — max 50 MB |
200 Sync response
{
"analysis_id": 42,
"filename": "track.wav",
"credits_remaining": 4,
"result": {
"analysis": {
"instruments": [
{ "model": "Fender Precision Bass", "confidence_pct": 94.2,
"family": "bass", "role_in_mix": "rhythm" }
],
"genre": [
{ "genre": "Rock", "confidence_pct": 100 },
{ "genre": "Blues / Soul", "confidence_pct": 72 }
],
"mood": { "label": "Energetic & Upbeat", "energy": 78, "valence": 70,
"danceability": 65, "acousticness": 42 },
"tonality": { "key": "A", "mode": "minor", "tempo_bpm": 120 }
}
}
}
202 Async response (async_mode=true)
{ "job_id": "a1b2c3d4...", "status": "pending" }
cURL
curl -X POST "https://your-domain.com/api/analyze?mode=offline&rights_attested=true" \
-H "X-API-Key: msk_xxxxxx" \
-F "file=@track.wav"
Analyzes multiple files in a single call. Defaults to offline mode to protect credits.
Query parameters
| Parameter | Type | Default | Description |
| mode | string | offline | Analysis mode — defaults to offline for bulk |
| rights_attested | bool | required | Must be true — same ownership attestation as /api/analyze |
Form data
| Field | Type | Required | Description |
| files | file[] | required | Up to 10 audio files |
200 Response
{
"total": 3,
"ok": 3,
"credits_used": 0,
"credits_remaining": 5,
"results": [...]
}
Returns the status of an async job. Poll until status is done or failed.
Path parameters
| Parameter | Type | Description |
| job_id | string | Job ID returned by POST /api/analyze with async_mode=true |
200 Response
{
"job_id": "a1b2c3...",
"status": "done",
"filename": "track.wav",
"analysis_id": 42,
"result": { ... }
}
Query parameters
| Parameter | Type | Default | Description |
| page | int | 1 | Page number |
| per_page | int | 20 | Results per page (max 100) |
| instrument | string | — | Filter by instrument name (partial match) |
200 Response
{
"analyses": [
{
"id": 42,
"filename": "track.wav",
"primary_instrument": "Fender Precision Bass",
"created_at": "2026-04-18T10:30:00"
}
],
"total": 1,
"page": 1,
"has_next": false
}
Path parameters
| Parameter | Type | Description |
| analysis_id | int | Analysis ID |
Generates a public URL for sharing results without authentication. Idempotent — calling it twice returns the same URL.
200 Response
{ "share_url": "/r/abc123xyz", "token": "abc123xyz" }
Returns the full analysis result for a share token. No authentication required — suitable for embedding in other apps.
API Keys
One-time display. The raw key is only shown in this response. Store it securely — it cannot be retrieved again.
Request body (JSON)
| Field | Type | Required | Description |
| label | string | optional | Human-readable name (max 64 chars). Default: "My API Key" |
201 Response
{
"id": 1,
"key": "msk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"prefix": "msk_xxxxxxxx",
"label": "My Server",
"created_at": "2026-04-18T10:00:00",
"warning": "Save this key now — it will not be shown again."
}
200 Response
{
"keys": [
{
"id": 1,
"prefix": "msk_xxxxxxxx…",
"label": "My Server",
"is_active": true,
"requests_count": 142,
"requests_today": 7,
"daily_limit": 1000,
"last_used_at": "2026-04-18T09:45:00",
"created_at": "2026-04-01T12:00:00"
}
]
}
Deactivates the key. Any subsequent requests using this key will return 401.
Path parameters
| Parameter | Type | Description |
| key_id | int | Key ID from list response |
204 No content on success.
Webhooks
HMAC-SHA256 Signatures. Every webhook delivery includes an
X-MaestroAI-Signature header. Verify it with your signing secret to
ensure requests are genuine.
Signature verification (Python)
import hmac, hashlib
def verify_signature(secret: str, payload: bytes, sig_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig_header)
Webhook payload structure
{
"event": "analysis.complete",
"timestamp": "2026-04-18T10:30:00Z",
"data": {
"analysis_id": 42,
"filename": "track.wav",
"result": { ... }
}
}
Available events
| Event | Fired when |
| analysis.complete | An analysis finishes (both sync and async) |
| webhook.test | Manually triggered via the test endpoint |
One-time display. The signing secret is only shown in this response. Max 5 webhooks per account.
Request body (JSON)
| Field | Type | Required | Description |
| url | string | required | HTTPS URL to receive events |
| events | string | optional | Comma-separated event names. Default: analysis.complete |
201 Response
{
"id": 1,
"url": "https://your-server.com/webhooks/maestro",
"secret": "your_signing_secret",
"events": "analysis.complete",
"created_at": "2026-04-18T10:00:00"
}
200 Response
{
"webhooks": [
{
"id": 1,
"url": "https://your-server.com/webhooks/maestro",
"events": "analysis.complete",
"is_active": true,
"last_triggered_at": "2026-04-18T09:00:00",
"last_status_code": 200
}
]
}
204 No content on success.
Fires a webhook.test event to verify your endpoint is reachable.
200 Response
{ "delivered": true, "status_code": 200 }
Collections
Group analyses into albums, sessions or projects. Collections are private to the
authenticated user and do not consume credits. Every endpoint below requires
authentication.
Returns every collection owned by the authenticated user, ordered by most recently updated.
200 Response
[
{
"id": 1,
"name": "My Album Session",
"description": "Tracking for the new LP",
"color": "#EF4444",
"item_count": 12,
"created_at": "2026-04-10T09:00:00",
"updated_at": "2026-04-18T15:22:00"
}
]
Request body (JSON)
| Field | Type | Required | Description |
| name | string | required | Display name (max 80 chars) |
| description | string | optional | Free-form description |
| color | string | #7C3AED | Hex color used to tint the collection in the UI |
201 Response
{
"id": 10,
"name": "My Album Session",
"description": null,
"color": "#EF4444",
"item_count": 0,
"created_at": "2026-04-19T10:00:00"
}
cURL
curl -X POST https://your-domain.com/api/collections \
-H "X-API-Key: msk_xxxxxx" \
-H "Content-Type: application/json" \
-d '{"name":"My Album Session","color":"#EF4444"}'
Path parameters
| Parameter | Type | Description |
| collection_id | int | Collection ID returned by list or create |
200 Response
{
"id": 10,
"name": "My Album Session",
"description": null,
"color": "#EF4444",
"item_count": 2,
"analyses": [
{
"id": 42,
"filename": "track01.wav",
"primary_instrument": "Fender Precision Bass",
"primary_genre": "Rock",
"added_at": "2026-04-18T10:30:00"
}
]
}
Adds an existing analysis owned by the caller to the given collection. Idempotent — adding the same analysis twice is a no-op.
Request body (JSON)
| Field | Type | Required | Description |
| analysis_id | int | required | ID of the analysis to add |
201 Response
{
"collection_id": 10,
"analysis_id": 42,
"added_at": "2026-04-19T10:01:00"
}
Removes the link between an analysis and a collection. The analysis itself is not deleted.
204 No content on success.
Analyses are preserved. Only the collection and its membership links are removed — your analyses stay in history.
204 No content on success.
Returns a rolled-up summary of the authenticated user's analysis history — top instruments, genres, mood distribution and tonality distribution. Useful for dashboards.
200 Response
{
"total": 87,
"top_instruments": [
{ "label": "Fender Precision Bass", "count": 31 },
{ "label": "Gibson Les Paul", "count": 14 }
],
"top_genres": [
{ "label": "Rock", "count": 40 },
{ "label": "Blues", "count": 12 }
],
"mood_distribution": {
"Energetic & Upbeat": 22,
"Chill": 18
},
"tonality_distribution": {
"A minor": 12,
"C major": 9
}
}
Python SDK
A zero-dependency Python client that wraps the REST API. Requires Python 3.8+.
Installation
pip install maestroai
Quick start
from maestroai import MaestroAI
client = MaestroAI(api_key="msk_...")
result = client.analyze("track.wav", mode="offline")
print(result.instruments[0].model)
print(result.instruments[0].confidence_pct)
job = client.analyze("track.wav", async_mode=True)
result = client.wait_for_job(job.job_id)
share = client.share(result.analysis_id)
print(share.share_url)
Available methods
| Method | Description |
| analyze(path, mode, async_mode) | Analyze an audio file |
| wait_for_job(job_id, interval, timeout) | Poll until async job is done |
| job_status(job_id) | Check a specific async job |
| me() | Get your profile and credits |
| history(page, per_page, instrument, genre, mood) | List your analysis history (now with genre & mood filters) |
| insights() | Aggregated stats for your history |
| collections() | List your collections |
| create_collection(name, description, color) | Create a new collection |
| get_collection(collection_id) | Fetch a collection with its analyses |
| add_to_collection(collection_id, analysis_id) | Add an analysis to a collection |
| remove_from_collection(collection_id, analysis_id) | Remove an analysis from a collection |
| delete_collection(collection_id) | Delete a collection (analyses are kept) |
| create_api_key(label) | Create a new API key |
| revoke_api_key(key_id) | Revoke an API key |
| share(analysis_id) | Create a public share link |
| login(email, password) | Authenticate and store JWT token |
SDK — New Methods
The latest release adds first-class access to genre, mood and chord fields on AnalysisResult, plus full Collections and Insights support.
Genre, Mood & Chords
from maestroai import MaestroAI
client = MaestroAI(api_key="msk_...")
result = client.analyze("track.wav", mode="offline")
print(result.primary_genre)
print(result.mood_label)
print(result.chord_progression)
print(result.genre)
print(result.mood)
print(result.chords)
Collections
coll = client.create_collection("My Album Session", color="#EF4444")
client.add_to_collection(coll["id"], result.analysis_id)
collections = client.collections()
detail = client.get_collection(coll["id"])
print(detail["item_count"], "analyses")
client.remove_from_collection(coll["id"], result.analysis_id)
client.delete_collection(coll["id"])
Insights & filtered history
data = client.insights()
print(data["top_instruments"][0]["label"])
print(data["mood_distribution"])
rock_tracks = client.history(genre="Rock")
chill_tracks = client.history(mood="Chill", per_page=50)