Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

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, and input_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.")

Metadata

Release files for ai-midi 0.1.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for ai-midi 0.1.0rc1
File Interpreter ABI Platform
ai_midi-0.1.0rc1-py3-none-any.whl Python 3 none any Details

Release files / ai_midi-0.1.0rc1-py3-none-any.whl

Download URL ai_midi-0.1.0rc1-py3-none-any.whl
Size 15.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
22b571a30e2579fa663ab0f8d0abd25b45b936a94d82b0b9fbeb0fb5088cf6b2
BLAKE2b-256 checksum
How to use checksums
93854086a8ea8711905b293027b81b0539501733343a885147737b305d02aeb4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Security for ai-midi 0.1.0rc1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 1 file in this release has been uploaded with provenance metadata.

Attestation for ai_midi-0.1.0rc1-py3-none-any.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page