> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lazycloud.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Endpoints, ASGI, and realtime

> HTTPS and WebSocket services in Python, each with a stable URL and bearer-token auth by default.

Pick by what you already have:

| You have                                       | Use             |
| ---------------------------------------------- | --------------- |
| A Python function that answers HTTP requests   | `@app.endpoint` |
| A FastAPI or Starlette app with several routes | `app.asgi(...)` |
| A WebSocket message handler                    | `@app.realtime` |

Each gets a stable HTTPS URL in the cloud when deployed, and previews with
[`lazycloud serve`](/concepts/workflow#live-preview).

## Endpoints

Save this as `api_demo.py`:

```python theme={null}
from lazycloud import App
from pydantic import BaseModel

app = App("api_demo")


class WordCount(BaseModel):
    words: int


@app.endpoint(name="count_words", route="/word-count", methods=["POST"])
def count_words(text: str) -> WordCount:
    return WordCount(words=len(text.split()))
```

LazyCloud maps the JSON request body to the function's arguments and sends the
returned model as JSON. The model also becomes the response schema that
[typed client packages](/concepts/apps#call-deployed-workloads-from-other-projects)
use. `route` defaults to `/` and `methods` to `GET` and `POST`.

Deployed, it answers at the printed URL with an
[access token](/platform/auth#ci-and-scripts):

```bash theme={null}
uv run lazycloud deploy api_demo:count_words
curl --fail --show-error "<endpoint-url>" \
  -H "Authorization: Bearer $LAZYCLOUD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "Run Python on LazyCloud"}'
```

The response is `{"words": 4}`.

In Python, the decorated function is also the client:

```python theme={null}
from api_demo import count_words

response = count_words.request("Run Python on LazyCloud")
print(response.status_code, response.json())
```

`request()` uses your `lazycloud login` sign-in and goes to a running preview or
the deployment. See [Where a call goes](/concepts/workflow#where-a-call-goes).

## ASGI applications

`app.asgi(...)` wraps an existing FastAPI or Starlette app. FastAPI needs to be
installed locally too, with `uv add fastapi`, because the CLI imports the file:

```python theme={null}
from fastapi import FastAPI
from lazycloud import App, Image

app = App("web_app")
api = FastAPI()


@api.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}


service = app.asgi(
    name="service",
    image=Image().add_python_packages(["fastapi"]),
)(api)
```

```bash theme={null}
uv run lazycloud deploy web_app:service
```

`request()` sends a full request to a route:

```python theme={null}
from web_app import service

response = service.request(method="GET", path="/health", target="deployed")
print(response.json())
```

It takes `method`, `path`, `json`, `data`, `headers`, and `params`. An
[exported client](/concepts/apps#call-deployed-workloads-from-other-projects)
gives each route a typed method instead. ASGI apps take the endpoint options,
with `concurrent_requests` for `concurrency` and `keep_warm_seconds` for
`keep_warm`.

## Realtime handlers

`@app.realtime` turns a function into a WebSocket handler. Each incoming
message calls the handler, and LazyCloud sends the return value back. An
iterable return value sends each item as its own message.

```python theme={null}
@app.realtime(name="echo")
def echo(message: str) -> str:
    return message
```

Realtime handlers take the ASGI options and deploy, preview, and scale the same
way.

## Authentication

All three require `Authorization: Bearer <token>` with an
[access token](/platform/auth#ci-and-scripts) for the workspace, and the Python
clients send your sign-in. `authorized=False` makes the URL public, leaving
access control to your code.

## Capacity

An endpoint starts as one process handling one request at a time. Three
settings raise that:

```python theme={null}
from lazycloud import Autoscaler


@app.endpoint(
    name="word-count-scaled",
    cpu=2,
    workers=2,
    concurrency=8,
    autoscaler=Autoscaler(max_containers=8, tasks_per_container=16),
)
def count_words_scaled(text: str) -> WordCount:
    return WordCount(words=len(text.split()))
```

Two workers of eight requests make 16 per container, and the autoscaler adds a
container per 16 in flight, up to 8. Past `max_pending_tasks`, 100 by default,
requests get `429`. [Scaling](/concepts/scaling) covers choosing the numbers.
`checkpoint_enabled=True` lets new containers restore a warmed worker instead
of rerunning `on_start`.

## Custom domains

On Team and Business plans, `domain="api.example.com"` serves any of the three
from a hostname you own. See [Custom domains](/platform/domains).

## Stop and clean up

```bash theme={null}
uv run lazycloud deployment stop count_words
uv run lazycloud deployment delete count_words
```

`stop` takes it offline and keeps the definition, and `delete` removes it, as
does [`--prune`](/concepts/apps#remove-workloads) once it's gone from the code.

## Shared settings

[Images](/concepts/images), [resources](/concepts/resources), and
[scaling](/concepts/scaling).
