Python SDK for the AI-MIDI Public API.
Project description
AI-MIDI Python SDK
Small Python SDK for the AI-MIDI Public API.
The SDK requires Python 3.10 or newer. Client is synchronous and uses blocking
HTTP requests. The asynchronous workflow described below refers to server-side
jobs, not to an asyncio Python client.
Installation
The public beta uses the staging API. Install the prerelease from PyPI:
python -m pip install --pre ai-midi==0.1.0rc1
Configure a staging API key and URL before importing aimidi:
export AIMIDI_API_KEY="YOUR_STAGING_API_KEY"
export AIMIDI_BASE_URL="https://staging-api.ai-midi.com"
Do not use production credentials or sensitive audio during the public beta.
After the production release, install the stable ai-midi distribution:
python -m pip install ai-midi
For repository development, install the reviewed SDK source directly:
python -m pip install -e packages/ai_midi_sdk/python
The distribution name contains a hyphen, while the Python import is aimidi:
import aimidi
Configuration
The SDK defaults to the local Public API at http://localhost:8010. It does not
silently select a production service.
Package-level helpers read their initial configuration from these environment variables:
export AIMIDI_API_KEY="YOUR_STAGING_API_KEY"
export AIMIDI_BASE_URL="https://staging-api.ai-midi.com"
For local development, point the same variable at the local Public API:
export AIMIDI_API_KEY="aimidi_test_local"
export AIMIDI_BASE_URL="http://localhost:8010"
Start that local API from this repository with:
START_SUPPORT=false \
START_SITE=false \
START_FRONTEND=false \
START_PUBLIC_API=true \
deploy/ai_midi/local/server/run-stack.sh
Use the same variable for another deployment:
export AIMIDI_BASE_URL="https://public-api.example.com"
Applications using Client pass the deployment URL directly:
import os
import aimidi
client = aimidi.Client(
api_key=os.environ["AIMIDI_API_KEY"],
base_url=os.environ.get("AIMIDI_BASE_URL", "http://localhost:8010"),
)
Package-level configuration can also be assigned in Python:
import aimidi
aimidi.api_key = "aimidi_test_local"
aimidi.base_url = "http://localhost:8010"
Synchronous Conversion
aimidi.convert and Client.convert upload one file to POST /v1/convert and
wait for MIDI bytes:
midi = aimidi.convert(
"song.mp3",
model="piano",
bpm=120,
beat=4,
bar=4,
input_processing_type="original",
idempotency_key="song-2026-07-10",
)
midi.save("song.mid")
Signature:
aimidi.convert(
audio_path: str,
*,
model: str = "piano",
output_path: str | None = None,
timeout_seconds: float = 900.0,
bpm: int = 120,
beat: int = 4,
bar: int = 4,
input_processing_type: str = "original",
input_processing_target: str | None = None,
idempotency_key: str | None = None,
) -> MidiResult
Client.convert has the same conversion arguments. Existing calls such as
aimidi.convert("song.mp3") and client.convert("song.mp3", model="piano")
remain valid.
A successful synchronous conversion must return HTTP 200 with a MIDI media
type, normally Content-Type: audio/midi. The SDK rejects JSON, HTML, missing,
or other non-MIDI content types without constructing or saving a MidiResult,
even when the HTTP status is 2xx. The same media-type check applies to
Client.download_midi.
A keyed /v1/convert replay whose MIDI cannot be streamed returns HTTP 409 with
top-level code/detail and a nested job. The SDK maps
CONVERSION_FAILED to ConversionFailedError, and maps
CONVERSION_NOT_READY plus CONVERSION_OUTPUT_UNAVAILABLE to
TranscriptionNotReadyError. Exception messages retain the top-level contract
code/detail together with the nested job ID and status. Legacy 409 job documents
with top-level status remain supported. The earlier TRANSCRIPTION_* code
names are also accepted as compatibility aliases.
A terminal failure on the first synchronous attempt uses the same nested
envelope with HTTP 500 and code=CONVERSION_FAILED. The SDK raises
ConversionFailedError and preserves the request ID, human detail, contract
code, and nested inference error code in its message. Generic or unstructured
HTTP 5xx responses, including uncertain output-storage failures, remain
ServerUnavailableError.
Supported conversion form values:
| Option | Values |
|---|---|
model |
"piano", "guitar", or "multi" |
bpm |
Positive integer; default 120 |
beat |
Positive integer time-signature numerator; default 4 |
bar |
Positive integer time-signature denominator; default 4 |
input_processing_type |
"original" or "source_separated" |
input_processing_target |
"other", "bass", "drums", or "vocals" |
Source separation requires a target:
midi = client.convert(
"band.wav",
input_processing_type="source_separated",
input_processing_target="bass",
idempotency_key="band-bass-v1",
)
Asynchronous Server Jobs
The asynchronous Public API creates a server-side job. Every Python method below is still a regular blocking HTTP call; polling is explicit and bounded.
job = client.create_transcription(
"song.mp3",
model="piano",
bpm=120,
beat=4,
bar=4,
input_processing_type="original",
idempotency_key="song-async-2026-07-10",
)
print(job.id, job.status) # queued or processing
job = client.wait_for_transcription(
job.id,
timeout_seconds=900,
poll_interval_seconds=2,
)
midi = client.download_midi(job.id, output_path="song.mid")
print(len(midi.content))
submit_transcription is an alias for create_transcription. The async job
methods are:
client.create_transcription(
audio_path,
*,
model="piano",
bpm=120,
beat=4,
bar=4,
input_processing_type="original",
input_processing_target=None,
callback_url=None,
idempotency_key=None,
timeout_seconds=None,
) -> TranscriptionJob
client.get_transcription(
transcription_id,
*,
timeout_seconds=None,
) -> TranscriptionJob
client.wait_for_transcription(
transcription_id,
*,
timeout_seconds=900.0,
poll_interval_seconds=2.0,
) -> TranscriptionJob
client.download_midi(
transcription_id,
*,
output_path=None,
timeout_seconds=None,
) -> MidiDownload
get_transcription_status aliases get_transcription. A TranscriptionJob
contains id, status, model, audio_duration_seconds, credits_used,
created_at, completed_at, error, and optional download_url. Status is one
of queued, processing, completed, or failed.
callback_url remains in the SDK signature for forward compatibility, but
customer callbacks are not currently available. Passing it to the API returns
HTTP 422 before the upload or credit hold begins. Poll the transcription status
instead.
Folder Conversion
convert_folder remains available at package and client level. Each supported
file is converted independently, so one failure is recorded on its
BatchItemResult without aborting the batch.
results = aimidi.convert_folder(
"songs/",
model="piano",
output_dir="midi_out/",
recursive=True,
bpm=120,
beat=4,
bar=4,
idempotency_key_prefix="nightly-2026-07-10",
)
for item in results:
if item.ok:
print(f"{item.source_path} -> {item.output_path}")
else:
print(f"{item.source_path} failed: {item.error}")
Supported extensions are .mp3, .wav, .flac, .ogg, and .m4a. MIDI is
written next to each source by default. A missing or non-directory input raises
NotADirectoryError; a folder with no supported files returns an empty list.
With output_dir, non-recursive files are written directly beneath that
directory. Recursive scans mirror each source-relative subdirectory beneath
output_dir, so files such as takes/one/song.mp3 and takes/two/song.wav
produce distinct one/song.mid and two/song.mid outputs.
Idempotency
Both synchronous and asynchronous submission methods accept
idempotency_key. Retrying the same operation with the same key resolves to the
existing server job, avoiding a second conversion and charge. A new key starts a
new conversion.
sync_result = client.convert(
"song.mp3",
idempotency_key="song-sync-v1",
)
async_job = client.create_transcription(
"song.mp3",
idempotency_key="song-async-v1",
)
For batches, idempotency_key_prefix derives a stable, fixed-length ASCII key
for each file from:
- the caller's prefix
- the path relative to the scanned folder
- a streamed SHA-256 hash of the file bytes
model,bpm,beat,bar,input_processing_type, andinput_processing_target
Timeouts and output paths are deliberately excluded because they do not change conversion semantics. An unchanged file with unchanged semantic options gets the same key on every rerun. Changing the bytes, relative path, model, or another semantic option gets a new key and therefore starts a new conversion.
results = client.convert_folder(
"songs/",
idempotency_key_prefix="release-2026-07-10",
)
Compatibility note: folder keys now use the aimidi-folder-v2: format instead
of the legacy prefix-plus-path hash. The first keyed folder run after upgrading
will therefore submit new keys, even for unchanged files, and may create and
charge new conversions. Subsequent unchanged reruns are stable. Explicit
idempotency_key values passed to convert or create_transcription are not
changed by this migration.
Omitting these values preserves the original behavior: each submission is an independent conversion.
Errors
All HTTP/API errors subclass aimidi.ApiError.
| Exception | Raised when |
|---|---|
InvalidApiKeyError |
The API key is missing or invalid (HTTP 401). |
InsufficientCreditsError |
The account is out of credits (HTTP 402). |
InvalidRequestError |
Request metadata is rejected (HTTP 422). |
UnsupportedAudioFormatError |
The extension is unsupported (HTTP 415); subclasses InvalidAudioError. |
InvalidAudioError |
The file content is unreadable or invalid (HTTP 422). |
FileTooLargeError |
The file exceeds the size limit (HTTP 413). |
AudioTooLongError |
The audio duration exceeds the selected model limit (HTTP 413). |
TooManyJobsError |
Too many conversions are active (HTTP 429). |
TranscriptionNotFoundError |
The requested async job does not exist (HTTP 404). |
TranscriptionNotReadyError |
MIDI is unavailable for a queued, processing, or unavailable completed job (HTTP 409). |
ConversionFailedError |
Polling, a synchronous replay, or a structured first-attempt HTTP 500 reports a failed job. |
ConversionTimeoutError |
An HTTP request times out or bounded polling reaches its deadline. This subclasses ServerUnavailableError for backward-compatible transport error handling. |
ServerUnavailableError |
A connection fails, another request error occurs, or the API returns a generic or unstructured HTTP 5xx. |
Invalid local option values, such as a non-positive bpm or source separation
without a target, raise ValueError before an HTTP request is sent.
try:
job = client.create_transcription(
"song.mp3",
idempotency_key="song-retry-safe-v1",
)
job = client.wait_for_transcription(job.id, timeout_seconds=900)
client.download_midi(job.id, output_path="song.mid")
except aimidi.ConversionTimeoutError as error:
print(error)
except aimidi.ConversionFailedError as error:
print(error)
except aimidi.ServerUnavailableError:
print("AI-MIDI API is unavailable. Retry with the same Idempotency-Key.")
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ai_midi-0.1.0rc1-py3-none-any.whl.
File metadata
- Download URL: ai_midi-0.1.0rc1-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22b571a30e2579fa663ab0f8d0abd25b45b936a94d82b0b9fbeb0fb5088cf6b2
|
|
| MD5 |
356978350b37906833d00ed067d8a6ca
|
|
| BLAKE2b-256 |
93854086a8ea8711905b293027b81b0539501733343a885147737b305d02aeb4
|
Provenance
The following attestation bundles were made for ai_midi-0.1.0rc1-py3-none-any.whl:
Publisher:
publish-ai-midi-sdk.yml on sori-ai/sori
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ai_midi-0.1.0rc1-py3-none-any.whl -
Subject digest:
22b571a30e2579fa663ab0f8d0abd25b45b936a94d82b0b9fbeb0fb5088cf6b2 - Sigstore transparency entry: 2232219145
- Sigstore integration time:
-
Permalink:
sori-ai/sori@b6cf47799acf508900fd3208d5ef810b4b5366ab -
Branch / Tag:
refs/tags/ai-midi-sdk-v0.1.0rc1 - Owner: https://github.com/sori-ai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-ai-midi-sdk.yml@b6cf47799acf508900fd3208d5ef810b4b5366ab -
Trigger Event:
push
-
Statement type: