Skip to main content

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ai_midi-0.1.0rc1-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

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

Hashes for ai_midi-0.1.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 22b571a30e2579fa663ab0f8d0abd25b45b936a94d82b0b9fbeb0fb5088cf6b2
MD5 356978350b37906833d00ed067d8a6ca
BLAKE2b-256 93854086a8ea8711905b293027b81b0539501733343a885147737b305d02aeb4

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page