Skip to main content

Python SDK for the ai& API, generated from the public OpenAPI spec.

Project description

aiand-python

Use the ai& API with Python.

This package is generated from the public ai& OpenAPI spec with OpenAPI Generator. It covers the OpenAI-compatible endpoints currently present in the spec: models, chat completions, legacy completions, responses, files, and chunked uploads.

ai& also publishes docs at docs.aiand.com. The SDK update script regenerates from the public OpenAPI spec, then applies a small post-generation compatibility layer for Python generator edge cases.

Installation

From this checkout:

cd aiand-python
python -m pip install -e .

With uv:

cd aiand-python
uv sync --extra test --extra dev

Once this package is published, install it as:

python -m pip install aiand

The current SDK version is 0.1.0. See CHANGELOG.md for release notes.

Usage

Set your API key in the environment:

export AIAND_API_KEY="sk-..."

Create a client:

import os

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])

with aiand.ApiClient(configuration) as api_client:
    client = aiand.OpenaiApi(api_client)
    models = client.list_models()

print(models.data[0].id)

The generated base URL is https://api.aiand.com. The OpenAPI paths include /v1, so SDK calls resolve to URLs like https://api.aiand.com/v1/models.

Chat

import os

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])

request = aiand.CreateChatCompletionRequest.from_dict(
    {
        "model": "openai/gpt-oss-120b",
        "messages": [
            {"role": "system", "content": "You are concise and practical."},
            {"role": "user", "content": "Give me one sentence about ai&."},
        ],
        "temperature": 0.2,
    }
)

with aiand.ApiClient(configuration) as api_client:
    client = aiand.OpenaiApi(api_client)
    response = client.create_chat_completion(request)

print(response.choices[0].message.content)

Completions

import os

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])

request = aiand.CreateCompletionRequest.from_dict(
    {
        "model": "openai/gpt-oss-120b",
        "prompt": "Write a short product tagline for ai&:",
        "max_tokens": 32,
    }
)

with aiand.ApiClient(configuration) as api_client:
    client = aiand.OpenaiApi(api_client)
    response = client.create_completion(request)

print(response.choices[0].text)

Responses

import os

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])

request = aiand.CreateResponseRequest(
    model="openai/gpt-oss-120b",
    input=aiand.ResponseInput("Give me one practical sentence about ai&."),
    temperature=0.2,
    max_output_tokens=64,
    parallel_tool_calls=False,
    truncation="disabled",
)

with aiand.ApiClient(configuration) as api_client:
    client = aiand.OpenaiApi(api_client)
    response = client.create_response(request)

print(response.to_dict()["output"])

The typed constructor omits unset optional parameters from the request body.

Models And Pricing

import os

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])

with aiand.ApiClient(configuration) as api_client:
    client = aiand.OpenaiApi(api_client)
    models = client.list_models()

for model in models.data:
    print(
        model.id,
        model.provider,
        model.context_window,
        model.capabilities,
        model.input_per_1m,
        model.output_per_1m,
    )

The docs describe model pricing as precise string fields. This SDK keeps input_per_1m and output_per_1m as strings instead of floats.

Files

Upload a file once, then reference the returned file_id from chat completion requests.

import os
from pathlib import Path

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])
image_path = Path("diagram.png")

with aiand.ApiClient(configuration) as api_client:
    files = aiand.FilesApi(api_client)
    uploaded = files.upload_file(
        file=(image_path.name, image_path.read_bytes()),
        purpose="vision",
    )

print(uploaded.id)

The file purpose values are vision, video, audio, and document.

Chunked Uploads

For larger assets, create an upload, add parts in order, then complete it.

import os
from pathlib import Path

import aiand

configuration = aiand.Configuration(access_token=os.environ["AIAND_API_KEY"])
video_path = Path("clip.mp4")
part_bytes = video_path.read_bytes()

with aiand.ApiClient(configuration) as api_client:
    uploads = aiand.UploadsApi(api_client)

    upload = uploads.create_upload(
        aiand.CreateUploadRequest.from_dict(
            {
                "filename": video_path.name,
                "purpose": "video",
                "bytes": len(part_bytes),
                "mime_type": "video/mp4",
            }
        )
    )

    part = uploads.add_upload_part(upload.id, data=("part-1", part_bytes))
    completed = uploads.complete_upload(
        upload.id,
        aiand.CompleteUploadRequest.from_dict({"part_ids": [part.id]}),
    )

print(completed.file.id)

Timeouts And Headers

Every generated operation accepts OpenAPI Generator's standard request controls:

response = client.list_models(
    _request_timeout=(5, 30),
    _headers={"X-Request-Source": "aiand-python"},
)

Use Configuration(access_token=...) for API-key auth. The docs note that browser/JWT auth can require X-Org-ID; for server-side API keys, the organization is resolved from the key.

Errors

The generated client raises aiand.ApiException for non-2xx responses.

import aiand

try:
    client.list_models()
except aiand.ApiException as error:
    print(error.status)
    print(error.body)

Testing

Run the unit tests without making network calls:

uv run --extra test pytest

Run the focused linter for hand-maintained code:

uv run --extra dev ruff check tests scripts

Generated code under aiand/ is intentionally excluded from Ruff. It is recreated by OpenAPI Generator and should be reviewed for behavior, not reformatted by hand.

Recording VCR Cassettes

Tests use vcrpy for live API coverage. Cassettes live in tests/cassettes.

To record cassettes, create .env.test:

AIAND_API_KEY=sk-your-real-aiand-api-key

Then run:

./scripts/record-cassettes

The VCR config filters the Authorization header and common request/organization headers. The recording script sets AIAND_VCR_RECORD_MODE=once. Do not commit .env.test. Only commit sanitized cassette files.

The VCR suite records one compact cassette per public endpoint group:

  • list_models.yaml
  • chat_completion.yaml
  • completion.yaml
  • response.yaml
  • files_lifecycle.yaml
  • uploads_complete.yaml
  • uploads_cancel.yaml

Together those cassettes hit every endpoint currently generated from the OpenAPI spec: GET /v1/models, POST /v1/chat/completions, POST /v1/completions, POST /v1/responses, GET /v1/files, POST /v1/files, GET /v1/files/{id}, GET /v1/files/{id}/content, DELETE /v1/files/{id}, POST /v1/uploads, POST /v1/uploads/{id}/parts, POST /v1/uploads/{id}/complete, and POST /v1/uploads/{id}/cancel.

Updating The SDK

Prerequisites:

  • Java, required by OpenAPI Generator.
  • Node/npm with npx, used to run @openapitools/openapi-generator-cli@2.34.0.
  • Python 3.10 or newer.
  • uv for development and tests.

Regenerate from the latest published spec:

./scripts/update-sdk

That script:

  1. Downloads https://api.aiand.com/openapi.json to openapi/openapi.json.
  2. Runs OpenAPI Generator with openapi-generator-config.yaml.
  3. Applies scripts/patch_generated_client.py for generator-specific Python compatibility.

After regenerating:

uv run --extra test pytest
uv run --extra dev ruff check tests scripts

Review the generated diff in aiand/, docs/, and openapi/openapi.json. If the public spec has gained endpoints or corrected a generator edge case, update scripts/patch_generated_client.py so the post-generation patch layer remains small and obvious.

The npm wrapper is pinned in scripts/update-sdk, and the OpenAPI Generator version is pinned in openapitools.json. To upgrade either one, edit the pinned version, regenerate, and review the generated diff carefully.

Development Notes

Most SDK files are generated. The main hand-maintained files are:

  • README.md
  • CHANGELOG.md
  • LICENSE
  • pyproject.toml
  • scripts/update-sdk
  • scripts/patch_generated_client.py
  • scripts/record-cassettes
  • tests/

Bug reports and pull requests are welcome.

License

This project is licensed under the Apache License 2.0.

Project details


Download files

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

Source Distribution

aiand-0.1.0.tar.gz (73.7 kB view details)

Uploaded Source

Built Distribution

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

aiand-0.1.0-py3-none-any.whl (205.1 kB view details)

Uploaded Python 3

File details

Details for the file aiand-0.1.0.tar.gz.

File metadata

  • Download URL: aiand-0.1.0.tar.gz
  • Upload date:
  • Size: 73.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for aiand-0.1.0.tar.gz
Algorithm Hash digest
SHA256 726ee25a278789be476ed01f39e93c4b7f6ef373305493a40fc7d252c0fc538b
MD5 ef9ba735e82224ea0d52d9388842ba0a
BLAKE2b-256 93a60337c2ccce09407688c6e751801a65e070cf7ec3474692916f74746549e3

See more details on using hashes here.

File details

Details for the file aiand-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: aiand-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 205.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for aiand-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 60f0350e4feaebecb305d38c94ec0be86719cc4eb3f9b7bbac663d91b6783572
MD5 06ab3c5b1d66df0d14c5d65d5a4c8616
BLAKE2b-256 f4a872796e30fd44c43de05a38855350052987774567a8721be04fa341d5e319

See more details on using hashes here.

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