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
ModeDescriptionCredits
offlineRule-based, 30 instruments, no credits needed, instantFree
aiFull AI pipeline, highest accuracy, 50+ instruments1 credit
autoUses AI if credits available, otherwise offline1 credit

Authentication

MaestroAI supports two authentication methods:

Method 1 — JWT Bearer Token (browser / server)
# After login, include the token in the Authorization header
Authorization: Bearer <your_jwt_token>
Method 2 — API Key (integrations / scripts)
# Create an API key in the dashboard, then pass it via header
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

StatusMeaning
200Success
201Resource created
202Accepted — async job queued
204No content — operation succeeded with no body
400Bad request — invalid input
401Unauthorized — missing or invalid credentials
402Payment required — no credits remaining
404Not found
422Validation error — e.g. password too short
429Rate limit exceeded
500Server 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
EndpointLimit
/api/auth/register5 / minute per IP
/api/auth/login10 / minute per IP
/api/auth/forgot-password3 / minute per IP
/api/analyze30 / minute per IP
/api/analyze/bulk5 / minute per IP

Auth

POST /api/auth/register Create a new account

Creates a new user account. Returns a JWT token and sends a welcome email.

Request body (JSON)
FieldTypeRequiredDescription
emailstringrequiredValid email address
passwordstringrequiredMinimum 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"}'
POST /api/auth/login Authenticate and get a token

Authenticates with email and password. Returns a JWT token valid for 7 days.

Request body (JSON)
FieldTypeRequiredDescription
emailstringrequiredRegistered email
passwordstringrequiredAccount password
200 Response
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "user": { "id": 1, "email": "you@example.com", "credits": 5 }
}
POST /api/auth/forgot-password Request a password reset link

Sends a password-reset email if the address is registered. Always returns 202 to prevent email enumeration.

Request body (JSON)
FieldTypeRequiredDescription
emailstringrequiredEmail to send the reset link to
202 Response
{ "message": "If that email is registered, you will receive a reset link shortly." }
POST /api/auth/reset-password Set a new password via reset token

Consumes a one-time reset token (expires 1 hour after issue) and sets the new password.

Request body (JSON)
FieldTypeRequiredDescription
tokenstringrequiredToken from the reset email URL
new_passwordstringrequiredNew password, minimum 8 characters
204 No content on success.

User

GET /api/user/me Get your profile Auth required
200 Response
{
  "id": 1,
  "email": "you@example.com",
  "credits": 5,
  "is_admin": false,
  "member_since": "2026-01-15T10:30:00"
}
PATCH /api/user/me/password Change your password Auth required
Request body (JSON)
FieldTypeRequiredDescription
current_passwordstringrequiredYour current password
new_passwordstringrequiredNew password, minimum 8 characters
204 No content on success.
DELETE /api/user/me Permanently delete your account Auth required
Irreversible. All analyses, API keys, webhooks, and payments are deleted. This cannot be undone.
204 No content on success.

Analysis

POST /api/analyze Identify instruments in an audio file Auth required

Upload an audio file for instrument identification. Returns the result synchronously, or a job ID if async_mode=true.

Query parameters
ParameterTypeDefaultDescription
modestringautoauto | offline | ai
async_modeboolfalseIf true, returns 202 with a job_id immediately
rights_attestedboolrequiredMust 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_startfloatautoOptional section start in seconds. Requires window_end and deep analysis mode. Without it, the most energetic 90s window is auto-detected.
window_endfloatautoOptional section end in seconds. Span must be 5–90 seconds.
Form data
FieldTypeRequiredDescription
filefilerequiredAudio 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"
POST /api/analyze/bulk Analyze up to 10 files in one request Auth required

Analyzes multiple files in a single call. Defaults to offline mode to protect credits.

Query parameters
ParameterTypeDefaultDescription
modestringofflineAnalysis mode — defaults to offline for bulk
rights_attestedboolrequiredMust be true — same ownership attestation as /api/analyze
Form data
FieldTypeRequiredDescription
filesfile[]requiredUp to 10 audio files
200 Response
{
  "total": 3,
  "ok": 3,
  "credits_used": 0,
  "credits_remaining": 5,
  "results": [...]
}
GET /api/analyze/jobs/{job_id} Poll an async analysis job Auth required

Returns the status of an async job. Poll until status is done or failed.

Path parameters
ParameterTypeDescription
job_idstringJob ID returned by POST /api/analyze with async_mode=true
200 Response
{
  "job_id": "a1b2c3...",
  "status": "done",  // "pending" | "processing" | "done" | "failed"
  "filename": "track.wav",
  "analysis_id": 42,        // only when done
  "result": { ... }         // full result object when done
}
GET /api/analyses List your analysis history Auth required
Query parameters
ParameterTypeDefaultDescription
pageint1Page number
per_pageint20Results per page (max 100)
instrumentstringFilter 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
}
GET /api/analyses/{analysis_id} Get a specific analysis Auth required
Path parameters
ParameterTypeDescription
analysis_idintAnalysis ID
POST /api/analyses/{analysis_id}/share Create a public share link Auth required

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" }
GET /api/r/{token} Get a shared result (no auth)

Returns the full analysis result for a share token. No authentication required — suitable for embedding in other apps.


API Keys

POST /api/keys Create an API key Auth required
One-time display. The raw key is only shown in this response. Store it securely — it cannot be retrieved again.
Request body (JSON)
FieldTypeRequiredDescription
labelstringoptionalHuman-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."
}
GET /api/keys List your API keys with usage stats Auth required
200 Response
{
  "keys": [
    {
      "id": 1,
      "prefix": "msk_xxxxxxxx…",
      "label": "My Server",
      "is_active": true,
      "requests_count": 142,      // lifetime total
      "requests_today": 7,        // resets at midnight UTC
      "daily_limit": 1000,
      "last_used_at": "2026-04-18T09:45:00",
      "created_at": "2026-04-01T12:00:00"
    }
  ]
}
DELETE /api/keys/{key_id} Revoke an API key Auth required

Deactivates the key. Any subsequent requests using this key will return 401.

Path parameters
ParameterTypeDescription
key_idintKey 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
EventFired when
analysis.completeAn analysis finishes (both sync and async)
webhook.testManually triggered via the test endpoint
POST /api/webhooks Register a webhook endpoint Auth required
One-time display. The signing secret is only shown in this response. Max 5 webhooks per account.
Request body (JSON)
FieldTypeRequiredDescription
urlstringrequiredHTTPS URL to receive events
eventsstringoptionalComma-separated event names. Default: analysis.complete
201 Response
{
  "id": 1,
  "url": "https://your-server.com/webhooks/maestro",
  "secret": "your_signing_secret",  // shown ONCE
  "events": "analysis.complete",
  "created_at": "2026-04-18T10:00:00"
}
GET /api/webhooks List your webhooks Auth required
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
    }
  ]
}
DELETE /api/webhooks/{webhook_id} Delete a webhook Auth required
204 No content on success.
POST /api/webhooks/{webhook_id}/test Send a test event Auth required

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.

GET /api/collections List your collections Auth required

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"
  }
]
POST /api/collections Create a new collection Auth required
Request body (JSON)
FieldTypeRequiredDescription
namestringrequiredDisplay name (max 80 chars)
descriptionstringoptionalFree-form description
colorstring#7C3AEDHex 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"}'
GET /api/collections/{collection_id} Get a collection with its analyses Auth required
Path parameters
ParameterTypeDescription
collection_idintCollection 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"
    }
  ]
}
POST /api/collections/{collection_id}/items Add an analysis to a collection Auth required

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)
FieldTypeRequiredDescription
analysis_idintrequiredID of the analysis to add
201 Response
{
  "collection_id": 10,
  "analysis_id": 42,
  "added_at": "2026-04-19T10:01:00"
}
DELETE /api/collections/{collection_id}/items/{analysis_id} Remove an analysis from a collection Auth required

Removes the link between an analysis and a collection. The analysis itself is not deleted.

204 No content on success.
DELETE /api/collections/{collection_id} Delete a collection Auth required
Analyses are preserved. Only the collection and its membership links are removed — your analyses stay in history.
204 No content on success.
GET /api/analyses/insights Aggregated insights from your history Auth required

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_...")

# Synchronous analysis
result = client.analyze("track.wav", mode="offline")
print(result.instruments[0].model)   # "Fender Precision Bass"
print(result.instruments[0].confidence_pct)  # 94.2

# Async with polling
job = client.analyze("track.wav", async_mode=True)
result = client.wait_for_job(job.job_id)

# Share a result
share = client.share(result.analysis_id)
print(share.share_url)
Available methods
MethodDescription
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)      # "Rock"
print(result.mood_label)         # "Energetic & Upbeat"
print(result.chord_progression)  # "I-V-vi-IV"

# Full dicts are available too
print(result.genre)   # [{"genre": "Rock", "confidence_pct": 100}, ...]
print(result.mood)    # {"label": ..., "energy": 78, "valence": 70, ...}
print(result.chords)  # {"root_chord": "A", "quality": "minor", ...}
Collections
# Create a collection and add analyses to it
coll = client.create_collection("My Album Session", color="#EF4444")
client.add_to_collection(coll["id"], result.analysis_id)

# List, inspect, and clean up
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
# Aggregated stats across all your analyses
data = client.insights()
print(data["top_instruments"][0]["label"])  # "Fender Precision Bass"
print(data["mood_distribution"])

# Filter your history by genre or mood
rock_tracks = client.history(genre="Rock")
chill_tracks = client.history(mood="Chill", per_page=50)