# Skytells Python SDK
Run AI models with a single function call.

Official Python SDK for the [Skytells](https://skytells.ai) AI platform. Run image, video, audio, music, text, and code models with a single function call.

Zero dependencies — uses Python's stdlib `urllib` for HTTP.

```bash
pip install skytells
```


## Quick Start

First, you'll need to get an API key from the [Skytells Dashboard](https://skytells.ai/dashboard/api-keys).

Then, you can use the SDK to run a model and get the output.

```python
from skytells import SkytellsClient

client = SkytellsClient("sk-your-api-key")

# Run a model and get output
prediction = client.run("truefusion", input={"prompt": "An astronaut riding a rainbow unicorn"})
print(prediction.output)  # "https://..."

# Clean up when done
prediction.delete()
```

Generating a song using [Beatfusion](https://skytells.ai/explore/models/beatfusion-2.0):

```python
prediction = client.run("beatfusion-2.0", input={
    "prompt": "Rap, hip-hop",
    "lyrics": "Let me introduce the voice you hear, Beatfusion by Skytells making it clear.."
})
print(prediction.output)
```

Browse the full model catalog at [skytells.ai/explore/models](https://skytells.ai/explore/models).

> Skytells is committed to the responsible and ethical use of AI. All models are subject to our usage policies and comply with applicable laws and regulations. Learn more at [learn.skytells.ai](https://learn.skytells.ai).



## Installation

```bash
pip install skytells
```

Requires Python 3.8+. No external dependencies.



## Usage

### Client Configuration

```python
from skytells import SkytellsClient

client = SkytellsClient(
    "sk-your-api-key",
    base_url="https://api.skytells.ai/v1",   # optional override
    timeout=30000,                             # ms (default: 60000)
    headers={"X-Custom-Header": "value"},      # extra headers
    retry={"retries": 3, "retry_delay": 1000}, # retry config
)
```

### Run a Model

```python
prediction = client.run("truefusion", input={"prompt": "A sunset over mountains"})
print(prediction.output)   # str or list[str]
print(prediction.id)
print(prediction.status)
```

### Progress Tracking

```python
def on_progress(p):
    print(f"Status: {p['status']}, Progress: {p.get('metrics', {}).get('progress', 'n/a')}")

prediction = client.run(
    "truefusion",
    input={"prompt": "A cat"},
    on_progress=on_progress,
)
```

### Background Predictions

```python
# Create without waiting
response = client.predictions.create({
    "model": "truefusion-ultra",
    "input": {"prompt": "A dog"},
})
print(response["id"], response["status"])  # "pending"

# Wait for completion
result = client.wait(response)
print(result["output"])

# Or poll manually
result = client.predictions.get(response["id"])
```

### Queue & Dispatch

```python
client.queue({"model": "truefusion-ultra", "input": {"prompt": "Cat"}})
client.queue({"model": "flux-pro", "input": {"prompt": "Dog"}})
client.queue({"model": "flux-edge", "input": {"prompt": "Bird"}})

results = client.dispatch()
for pred in results:
    print(pred["id"], pred["status"])
```

### Prediction Lifecycle

```python
# Cancel a running prediction
client.cancel_prediction("pred_abc123")

# Delete a prediction and its assets
client.delete_prediction("pred_abc123")

# Stream endpoint
stream_info = client.stream_prediction("pred_abc123")
print(stream_info["urls"]["stream"])
```

### Models API

```python
# List all models
models = client.models.list()
for model in models:
    print(model["name"], model["type"])

# With schemas
models = client.models.list(fields=["input_schema"])

# Fetch a single model
model = client.models.get("flux-pro")
print(model["name"], model["pricing"])

model = client.models.get("flux-pro", fields=["input_schema", "output_schema"])
print(model["input_schema"])
```

### Predictions API

```python
# List predictions with filters
result = client.predictions.list(model="flux-pro", since="2026-01-01", page=2)
for pred in result["data"]:
    print(pred["id"], pred["status"])

print(result["pagination"])
```



## The Prediction Object

`client.run()` returns a `Prediction` instance:

| Attribute / Method | Description |
|---|---|
| `prediction.id` | Unique prediction ID |
| `prediction.status` | Current status string |
| `prediction.output` | Raw output (`str`, `list[str]`, or `None`) |
| `prediction.outputs()` | Normalised output (unwraps single-element arrays) |
| `prediction.raw()` | Full response dict |
| `prediction.cancel()` | Cancel the prediction |
| `prediction.delete()` | Delete the prediction and its assets |
| `prediction.response` | Full response dict (same as `raw()`) |

### `outputs()` behaviour

| `output` value | `outputs()` returns |
|---|---|
| `None` | `None` |
| `"https://..."` | `"https://..."` |
| `["https://..."]` | `"https://..."` (unwrapped) |
| `["a", "b"]` | `["a", "b"]` |



## Async Client

```python
import asyncio
from skytells import AsyncSkytellsClient

async def main():
    client = AsyncSkytellsClient("sk-your-api-key")
    prediction = await client.run("flux-pro", input={"prompt": "A cat"})
    print(prediction.output)
    await prediction.delete()

asyncio.run(main())
```



## Error Handling

```python
from skytells import SkytellsError

try:
    prediction = client.run("flux-pro", input={"prompt": "test"})
except SkytellsError as e:
    print(e.error_id)    # e.g. "UNAUTHORIZED", "MODEL_NOT_FOUND"
    print(e.http_status) # e.g. 401, 404
    print(e.details)     # human-readable detail string
    print(str(e))        # error message
```

### Error IDs

| Error ID | Source | Meaning |
|---|---|---|
| `UNAUTHORIZED` | API | Invalid or missing API key |
| `INVALID_PARAMETER` | API | A request parameter failed validation |
| `INVALID_DATE_FORMAT` | API | Date string is not `YYYY-MM-DD` |
| `INVALID_DATE_RANGE` | API | `since` is after `until` |
| `MODEL_NOT_FOUND` | API | Model slug does not exist |
| `INTERNAL_ERROR` | API | Unexpected server-side error |
| `INVALID_INPUT` | API | Model input validation failed |
| `INSUFFICIENT_CREDITS` | API | Account has no credits |
| `ACCOUNT_SUSPENDED` | API | Account has been suspended |
| `PAYMENT_REQUIRED` | API | Payment is required to continue |
| `SECURITY_VIOLATION` | API | Request violated a security policy |
| `RATE_LIMIT_EXCEEDED` | API | Too many requests |
| `INFRASTRUCTURE_ERROR` | API | Platform infrastructure failure |
| `PREDICTION_FAILED` | Client | Prediction completed with a failure status |
| `WAIT_TIMEOUT` | Client | `wait()` exceeded `max_wait` |
| `REQUEST_TIMEOUT` | Client | HTTP request timed out |
| `NETWORK_ERROR` | Client | Network-level failure (no HTTP response) |
| `SERVER_ERROR` | Client | Non-JSON or unexpected 5xx response |
| `INVALID_JSON` | Client | Response body could not be parsed as JSON |

For a full reference of API-level and prediction-level errors, see the [Skytells Error Reference](https://learn.skytells.ai/api/v1/errors).



## Client Options

| Option | Type | Default | Description |
|---|---|---|---|
| `api_key` | `str` | `None` | Your Skytells API key (`sk-...`) |
| `base_url` | `str` | `https://api.skytells.ai/v1` | API base URL |
| `timeout` | `int` | `60000` | Request timeout (ms) |
| `headers` | `dict` | `{}` | Extra headers for every request |
| `retry` | `dict` | `{}` | Retry config (`retries`, `retry_delay`, `retry_on`) |



## Type Reference

All types are importable from `skytells.types`:

```python
from skytells.types import (
    PredictionStatus,
    PredictionType,
    PredictionSource,
    ModelType,
    ModelPrivacy,
    PricingUnit,
    ApiErrorId,
)
```

---

## License

MIT — see [LICENSE](LICENSE).
