# Running Apps

> Execute AI apps on inference shell.

**URL:** https://inference.sh/docs/api/sdk/running-apps
**Last updated:** 2026-10-11

---

Execute AI apps on inference shell.

---

## Basic Usage

<div data-type="codegroup">

```python Python
from inferencesh import inference

client = inference(api_key="inf_your_key")

result = client.tasks.run({
    "app": "infsh/flux",
    "input": {"prompt": "A sunset over mountains"}
})

print(f"Task ID: {result['id']}")
print(f"Output: {result['output']}")
```

```typescript JavaScript
import { inference } from '@inferencesh/sdk';

const client = inference({ apiKey: 'inf_your_key' });

const result = await client.run({
  app: 'infsh/flux',
  input: { prompt: 'A sunset over mountains' }
});

console.log('Task ID:', result.id);
console.log('Output:', result.output);
```

```swift Swift
import InferenceSDK

let client = InferenceClient(apiKey: "inf_your_key")

let task = try await client.tasks.run(ApiAppRunRequest(
    app: "infsh/flux",
    input: ["prompt": "A sunset over mountains"]
))

print("Task ID:", task.id)
print("Output:", task.output)
```

</div>

---

## Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `app` | `string` | App identifier (`namespace/name` or `namespace/name@version`) |
| `input` | `object` | Input matching app schema |
| `setup` | `object` | Setup parameters (affects worker warmth) |
| `infra` | `'cloud' \| 'private' \| 'private_first'` | Infrastructure type (default `cloud`) |
| `workers` | `string[]` | Specific worker IDs (for private) |
| `webhook` | `string` | URL to receive a POST when the task completes |
| `session` | `string` | `'new'` to start a session, or session ID to continue |
| `session_timeout` | `number` | Session idle timeout in seconds (1-3600, only with `session: 'new'`) |

---

## Setup Parameters

Setup parameters configure the app instance. Workers with matching setup are "warm" and start faster:

<div data-type="codegroup">

```python Python
result = client.run({
    "app": "infsh/flux",
    "setup": {"model": "schnell"},
    "input": {"prompt": "A sunset"}
})
```

```typescript JavaScript
const result = await client.run({
  app: 'infsh/flux',
  setup: { model: 'schnell' },
  input: { prompt: 'A sunset' }
});
```

```swift Swift
let task = try await client.tasks.run(ApiAppRunRequest(
    app: "infsh/flux",
    setup: ["model": "schnell"],
    input: ["prompt": "A sunset"]
))
```

</div>

---

## Private Workers

Run on your own infrastructure with `infra: 'private'`, or prefer private workers with automatic cloud fallback using `infra: 'private_first'`:


<div data-type="codegroup">

```python Python
result = client.run({
    "app": "my-team/my-app",
    "input": {...},
    "infra": "private",
    "workers": ["worker-id-1"]  # Optional: specific workers
})
```

```typescript JavaScript
const result = await client.run({
  app: 'my-team/my-app',
  input: {...},
  infra: 'private',
  workers: ['worker-id-1']
});
```

</div>

---

## Task Status

<div data-type="codegroup">

```python Python
from inferencesh import TaskStatus

TaskStatus.QUEUED      # 2 - Waiting
TaskStatus.RUNNING     # 7 - Executing
TaskStatus.CANCELLING  # 8 - Cancelling
TaskStatus.COMPLETED   # 10 - Done
TaskStatus.FAILED      # 11 - Error
TaskStatus.CANCELLED   # 12 - Cancelled
```

```typescript JavaScript
import {
  TaskStatusQueued,      // 2
  TaskStatusRunning,     // 7
  TaskStatusCompleted,   // 10
  TaskStatusFailed,      // 11
  TaskStatusCancelled    // 12
} from '@inferencesh/sdk';

if (task.status === TaskStatusCompleted) {
  console.log('Done!');
}
```

</div>

---

## Sessions

Maintain state across multiple calls with sessions. The worker stays warm, preserving loaded models and in-memory state.

<div data-type="codegroup">

```python Python
# Start a new session
result = client.run({
    "app": "my-app",
    "input": {"action": "init"},
    "session": "new"
})
session_id = result["session_id"]

# Continue the session
result = client.run({
    "app": "my-app",
    "input": {"action": "process"},
    "session": session_id
})
```

```typescript JavaScript
// Start a new session
const result = await client.run({
  app: 'my-app',
  input: { action: 'init' },
  session: 'new'
});
const sessionId = result.session_id;

// Continue the session
await client.run({
  app: 'my-app',
  input: { action: 'process' },
  session: sessionId
});
```

</div>

### Custom Session Timeout

Sessions expire after 60 seconds of inactivity by default. Customize with `session_timeout` (1-3600 seconds):

<div data-type="codegroup">

```python Python
# 5-minute idle timeout
result = client.run({
    "app": "my-app",
    "input": {...},
    "session": "new",
    "session_timeout": 300
})
```

```typescript JavaScript
// 5-minute idle timeout
const result = await client.run({
  app: 'my-app',
  input: {...},
  session: 'new',
  session_timeout: 300
});
```

</div>

Each successful call resets the idle timer. See [Sessions Developer Guide](/docs/extend/sessions) for full documentation.

---

## Task Management

After starting a task without waiting, use the `client.tasks` namespace to check on it, cancel it, or attach to its stream:

<div data-type="codegroup">

```python Python
from inferencesh import inference, TaskStatus

client = inference(api_key="inf_your_key")

task = client.tasks.run({"app": "infsh/flux", "input": {...}}, wait=False)

# Poll or wait (optional timeout in seconds)
state = client.tasks.get(task["id"])
result = client.tasks.wait_for_completion(task["id"], timeout=300)

# Cancel
client.tasks.cancel(task["id"])

# Stream an in-flight task
with client.tasks.stream(task["id"]) as stream:
    for update in stream:
        if update["status"] == TaskStatus.COMPLETED:
            print(update["output"])
            break
```

```typescript JavaScript
import { inference, TaskStatusCompleted } from '@inferencesh/sdk';

const client = inference({ apiKey: 'inf_your_key' });

const task = await client.run({ app: 'infsh/flux', input: {} }, { wait: false });
// v0.14.0+: SDK GETs /tasks/{id} after /apps/run, so `task` already has input/logs/session when present.

// Poll again when you need a fresher snapshot
const { data: state } = await client.tasks.get(task.id);

// Cancel
await client.tasks.cancel(task.id);

// Stream an in-flight task
const source = client.tasks.stream(task.id);
source.onmessage = (event) => {
  const update = JSON.parse(event.data);
  if (update.status === TaskStatusCompleted) {
    console.log(update.output);
    source.close();
  }
};
```

</div>

The Python async client exposes the same methods: `await client.tasks.get(...)`, `await client.tasks.wait_for_completion(..., timeout=...)`, `async with client.tasks.stream(...)`, and `await client.tasks.cancel(...)`.

`wait_for_completion` streams task updates and GET-reconciles if the stream ends without a terminal event. See [Streaming — stream completion and GET reconciliation](/docs/api/sdk/streaming#stream-completion-and-get-reconciliation-python). JavaScript has no `wait_for_completion`; pass `wait: true` (the default) to `client.run`, or drive the stream yourself with [`StreamManager`](/docs/api/sdk/streaming).

`client.cancel(id)`, `client.getTask(id)` and `client.streamTask(id)` are shorthands for the three `client.tasks` calls above.

### Deleting tasks and their files

A finished task can be deleted, with or without the files it consumed and produced. You can also list or delete the files and keep the task.

<div data-type="codegroup">

```python Python
# List the task's files: each has id, uri and role ("input" or "output")
files = client.tasks.files(task_id).data

# Delete the files, keep the task
result = client.tasks.delete_files(task_id).data
print(result["deleted"], result["skipped"])

# Delete only the outputs
client.tasks.delete_files(task_id, role="output")

# Delete the task; files=True deletes its files too
client.tasks.delete(task_id, files=True)
```

```typescript JavaScript
// List the task's files: each has id, uri and role ("input" or "output")
const { data: files } = await client.tasks.files(task.id);

// Delete the files, keep the task
const { data: result } = await client.tasks.deleteFiles(task.id);
console.log(result.deleted, result.skipped);

// Delete only the outputs
await client.tasks.deleteFiles(task.id, 'output');

// Delete the task; files: true deletes its files too
await client.tasks.delete(task.id, { files: true });
```

</div>

These calls need a finished task; a queued or running one answers `409`. Files are deleted even when another task, flow or chat still uses them, so pass `role="output"` if you reuse an uploaded input. Without `files`, deleting a task leaves its files in your workspace. See [Tasks API](/docs/api/rest/tasks#delete-task) for the response fields.

---

## Concurrent Tasks

Each call is an independent request, so run them with the concurrency primitive of the language:

<div data-type="codegroup">

```python Python
import asyncio
from inferencesh import async_inference

async def main():
    client = async_inference(api_key="inf_your_key")

    results = await asyncio.gather(*[
        client.tasks.run({"app": "infsh/flux", "input": {"prompt": p}})
        for p in prompts
    ])
```

```typescript JavaScript
const results = await Promise.all(
  prompts.map((prompt) =>
    client.run({ app: 'infsh/flux', input: { prompt } })
  )
);
```

</div>

Concurrent tasks count against your plan's `concurrency` limit. Every task is still accepted; at the cap the scheduler either dispatches anyway or holds the task **Queued** until a slot frees, depending on the plan's enforcement. See [Concurrent task limits](/docs/api/rest/entitlements#concurrent-task-limits).

---

## Webhooks

Get notified when a task completes by providing a `webhook` URL. When the task reaches a terminal state (completed, failed, or cancelled), a POST request is sent to your URL with the task result.

<div data-type="codegroup">

```python Python
result = client.run({
    "app": "my-app",
    "input": {"prompt": "Hello"},
    "webhook": "https://your-server.com/webhook"
}, wait=False)
# Your webhook receives a POST when the task finishes
```

```typescript JavaScript
const result = await client.run({
  app: 'my-app',
  input: { prompt: 'Hello' },
  webhook: 'https://your-server.com/webhook'
}, { wait: false });
// Your webhook receives a POST when the task finishes
```

</div>

### Webhook Payload

Your endpoint receives a JSON POST with the task result:

```json
{
  "event": "task.completed",
  "timestamp": "2024-01-15T10:30:05Z",
  "data": {
    "id": "task_abc123",
    "short_id": "abc123",
    "status": 10,
    "status_text": "completed",
    "output": { "image": { "uri": "https://..." } },
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:05Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `event` | string | `task.completed`, `task.failed`, or `task.cancelled` |
| `timestamp` | string | When the webhook was sent (ISO 8601, UTC) |
| `data` | object | The task result, fields below |

`data` fields:

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Task ID |
| `short_id` | string | Short task ID |
| `status` | number | Terminal status code (10=completed, 11=failed, 12=cancelled) |
| `status_text` | string | Status name (`completed`, `failed`, `cancelled`) |
| `output` | object | Task output (when completed; `null` when there is none) |
| `error` | string | Error message. Present only when the task has one |
| `session_id` | string | Session ID. Present only when the task ran in a session |
| `created_at` | string | ISO timestamp |
| `updated_at` | string | ISO timestamp |
| `run_at` | string | Scheduled start time (UTC). Present only for scheduled tasks |

The webhook is sent when the task reaches a terminal status. Delivery is not retried: a failed request or a 4xx/5xx response is logged and dropped. The request times out after 10 seconds, and only public addresses are called.

---

## Next Steps

- [Streaming](/docs/api/sdk/streaming) — Real-time progress updates
- [Files](/docs/api/sdk/files) — File uploads and downloads
