Skip to content

Python SDK

The official Python client for the BinDist API. Aimed at tooling and scripting — release pipelines, internal CLIs, ad-hoc admin scripts — rather than embedding into a long-running application.

For a richer typed integration into a runtime, prefer the Go SDK (executable integration) or the JavaScript SDK (web).

Source: github.com/BinDist/bindist-api-python.

Requirements

  • Python 3.10+
  • requests

Installation

The package is not yet on PyPI. Install from source:

git clone https://github.com/BinDist/bindist-api-python.git
cd bindist-api-python
pip install -e .

API key format

The shape of the API key depends on which deployment you're talking to:

  • Hosted (api.bindist.eu, multi-tenant): {tenant_id}.{secret} — the tenant ID (a UUID issued when your customer account is created) and the secret (returned once when the key is provisioned), joined by a literal dot. A bare secret will be rejected.
  • Self-hosted (single-tenant): just the secret. There's only one possible tenant, so the prefix is unnecessary.
# Hosted
api_key = f"{tenant_id}.{secret}"
client = CustomerClient("https://api.bindist.eu", api_key)

# Self-hosted
client = CustomerClient("https://bindist.internal.example.com", secret)

Quick Start

from bindist import CustomerClient

client = CustomerClient("https://api.bindist.eu", "YOUR_API_KEY")

apps = client.list_applications()
if apps.success:
    for app in apps.data["applications"]:
        print(f"{app['name']} ({app['applicationId']})")
else:
    print(f"Error: {apps.error['message']}")

Two clients

from bindist import CustomerClient, AdminClient

client = CustomerClient("https://api.bindist.eu", "your-api-key")
admin = AdminClient("https://api.bindist.eu", "admin-api-key")
Client Purpose
CustomerClient List applications and versions, fetch download URLs, download files, create share links, read stats.
AdminClient Create and update applications, upload binaries (small or large), manage customers and API keys, list activity.

Pick whichever matches the API key you have.

Customer client

# List applications (with optional filters)
client.list_applications(search="myapp", tags=["windows"], page=1, page_size=20)

# Get a single application
client.get_application("myapp")

# List versions (set test_channel=True to include disabled/pre-release)
client.list_versions("myapp", test_channel=True)

# List files in a version
client.list_version_files("myapp", "1.0.0")

# Get a download URL (file_id is optional, for multi-file versions)
client.get_download_url("myapp", "1.0.0")

# Create a share link (default 30 minutes, max 1440)
client.create_share_link("myapp", "1.0.0", expires_minutes=60)

# Download statistics
client.get_stats("myapp")

Downloading files

download_file returns a (bytes, metadata) tuple, not an ApiResponse. It verifies the SHA256 checksum by default and raises ValueError on mismatch.

content, metadata = client.download_file("myapp", "1.0.0")
with open(metadata["fileName"], "wb") as f:
    f.write(content)
print(f"Downloaded {metadata['fileName']} ({metadata['fileSize']} bytes)")

# Skip verification (not recommended)
content, metadata = client.download_file("myapp", "1.0.0", verify_checksum=False)

# Download from the test channel
content, metadata = client.download_file("myapp", "1.2.3-rc1", test_channel=True)

Admin client

# Create a customer (returns the new API key once)
customer = admin.create_customer(name="Acme Corp", notes="Enterprise customer")
print(customer.data["apiKey"])  # save it now — not retrievable later

# Create an application
admin.create_application(
    application_id="myapp",
    name="My Application",
    customer_ids=["customer-1"],
    description="A great application",
    tags=["windows", "desktop"],
)

# Upload a small file (< 10 MB) — single request
with open("app.exe", "rb") as f:
    admin.upload_small_file("myapp", "1.0.0", "app.exe", f.read(),
                            release_notes="Initial release")

# Upload a large file (>= 10 MB) — handles the pre-signed S3 flow internally
with open("large-app.exe", "rb") as f:
    admin.upload_large_file("myapp", "2.0.0", "large-app.exe", f.read(),
                            release_notes="Major update")

# Toggle release state / update notes
admin.update_version("myapp", "1.0.0", is_enabled=True,
                     release_notes="Updated release notes")

# Customers and activity
admin.update_customer("customer-1", name="New Name", is_active=True)
admin.delete_application("myapp")  # soft delete
admin.list_activity(activity_type="download", page=1)
admin.list_customers()

The two upload methods correspond to the Upload Binary and Large File Upload endpoints. Use upload_large_file for anything ≥10 MB; it transparently calls get_large_upload_url, PUTs to S3, and then calls complete_large_upload.

Response shape

Every method (except download_file) returns an ApiResponse dataclass:

@dataclass
class ApiResponse:
    success: bool
    status_code: int
    data: dict | None
    error: dict | None
    meta: dict | None
    raw: dict

data, error, and meta mirror the JSON structure returned by the API. There are no typed model objects — you index into dicts (response.data["applications"]).

Error handling

response = client.list_applications()

if response.success:
    for app in response.data["applications"]:
        process(app)
else:
    print(f"Error {response.status_code}: {response.error.get('message')}")
    print(f"Code: {response.error.get('code')}")

If the response body isn't valid JSON (for example, an HTML error page from a proxy), the SDK still produces an ApiResponse with success=False, status_code set to the underlying HTTP status, and error={"message": <raw text>}.

License

The Python SDK is released under the MIT License.