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

# Call actors from your app

> Read an actor's state, call its handlers and follow its events from Python, from scripts, and over HTTP

Once an actor is deployed, your own apps and scripts can read it and call it. There are three ways in, and the right one depends on what you're building and who should pay:

| | Python client | `cowboy … --json` | HTTP routes |
| - | - | - | - |
| Good for | Python apps | Scripts and CI jobs | Browsers, webhooks, any language |
| Who pays | Your wallet | The CLI's wallet | The actor, so visitors need no wallet |

This page builds a counter actor and calls it all three ways.

## The actor

Create the project. The last line removes a test for the starter actor you're about to replace:

```bash theme={"system"}
cowboy init counter --dir counter
cd counter
rm tests/test_counter.py
```

```text Output theme={"system"}
✓ Project initialized.
→ Preparing Python environment
✓ Python environment ready · .venv (0.1s)

Yeehaw! Project ready: /path/to/counter
```

Replace `actors/counter/main.py` with:

```python theme={"system"}
from cowboy_sdk import Actor, public, runtime
from cowboy_sdk.routes import get, mount


class CounterActor(Actor):
    routes = (mount("/*", volume="web", fallback="index.html"),)

    @public
    def init(self, payload: bytes | None = None) -> None:
        self.storage["count"] = 0

    @public
    def increment(self, amount: int) -> int:
        count = self.storage["count"] + amount
        self.storage["count"] = count
        runtime.emit_event("counter.changed", {"count": count})
        return count

    @public
    def count(self, _payload: object = None) -> int:
        return self.storage["count"]

    @public
    @get("/api/count")
    def count_page(self, request: dict) -> dict:
        return {
            "status": 200,
            "headers": [["content-type", "application/json"]],
            "body": f'{{"count": {self.storage["count"]}}}'.encode(),
        }
```

`count_page` and the `routes` line are for [HTTP](#over-http).

Add the website's files and a `[web]` table. The table needs `schema_version = 4`:

```bash theme={"system"}
mkdir -p web
echo '<h1>Counter</h1>' > web/index.html
echo '<h1>About</h1>' > web/about.html
printf 'schema_version = 4\n\n[web]\n' > cowboy.toml
```

Run `init`, then one call, on your machine:

```bash theme={"system"}
cowboy run --handler init
cowboy run --handler increment --payload-json 5
```

```text Output theme={"system"}
5
State:
  …
  count: 5
Events:
  counter.changed: {"count":5}
```

To put it on Cloud, [deploy it](https://docs.cowboy.inc/build/deploy.md). The deploy also publishes `web/`, and to change the website's files later, run `cowboy deploy --web-only`.

## From Python

For a Python app, use `cowboy-sdk`, the client library, imported as `cowboy`. It needs Python 3.10 or later. Install it in your app's environment:

```bash theme={"system"}
uv pip install cowboy-sdk==0.2.1
```

`CowboyClient` is the client, and `AsyncCowboyClient` has the same methods, awaited.

### Connect to Cloud

A client without a key can only read, which is enough to get started:

```python theme={"system"}
from cowboy import CowboyClient

client = CowboyClient("https://rpc.cloud.cowboy.inc")
print(client.get_height())
```

To call handlers, give it a key file and the network you expect:

```python theme={"system"}
from pathlib import Path

from cowboy import CowboyClient

client = CowboyClient(
    "https://rpc.cloud.cowboy.inc",
    private_key=Path("~/.cowboy/keys/default").expanduser().read_text(),
    expected_chain_id=26926904,
    expected_network="cloud",
)
```

Calls are paid from this wallet, so keep the key file out of your code and your repository. The network values are a safeguard: the client refuses to sign unless the endpoint matches them, so a wrong URL can't spend your money.

### Read the actor's state

The examples from here on use that `client`. Reading is free and needs no key:

```python theme={"system"}
print(client.get_actor_storage_decoded("<actor address>", key="count"))
```

To read through a handler instead, run it read-only with `actor_read`:

```python theme={"system"}
from cowboy import decode

result = client.actor_read("<actor address>", "count")
print(decode(result.result))
```

### Call a handler and wait for the result

A handler receives bytes, and `encode`, from `cowboy.serialization`, turns a Python value into them:

```python theme={"system"}
from cowboy.serialization import encode

response = client.execute_actor_wait(
    "<actor address>", "increment", payload=encode(5), timeout_ms=10_000
)
outcome = response.results[0]
print(outcome.status, outcome.decoded(), outcome.fee_paid)
```

`status` is `succeeded`, `reverted` or `timed_out`. On `succeeded`, `decoded()` is the handler's return value. On `timed_out`, the call may still go through. `fee_paid` is what the call cost, in base units (one CBY is a billion). If the call isn't accepted, `execute_actor_wait` raises `TransactionSkipped`.

### Follow events

`get_actor_logs` returns at most 50 events, oldest first. To follow new ones, pass `reverse=True` and poll, skipping events you've seen. `block_height` says when the event happened, where a block is the network's unit of about one second.

```python theme={"system"}
from cowboy import decode

for event in client.get_actor_logs("<actor address>", reverse=True):
    if event.topic == "counter.changed":
        print(event.block_height, decode(event.data_bytes))
```

Events are public, like the actor's state.

## From scripts

For scripts and CI jobs, every `cowboy` command takes `--json` and has fixed exit codes. See [Script the CLI](https://docs.cowboy.inc/references/cli-json.md).

## Over HTTP

An actor can answer HTTP requests, so browsers, webhooks and apps in any language can call it with no Cowboy library.

### Handlers as routes

`@get("/api/count")` makes `count_page` answer `GET /api/count`, and `cowboy_sdk.routes` also has `post`, `put`, `patch`, `delete` and `head` ([SDK reference](https://docs.cowboy.inc/developers/sdk-reference.md#http-routes-cip-15)).

A route handler needs `@public`, because a route decorator grants no access by itself. It sees the gateway as the caller, not the visitor, so don't trust `self.sender` here.

The handler gets the request as a dict, with `method`, `path`, `path_params`, `query`, `headers` (a list of `[name, value]` pairs) and `body` (bytes). It returns the response as a dict too, with `status`, `headers` and `body`.

`GET` and `HEAD` requests run the handler read-only. Other verbs can change state.

Try the handler on your machine:

```bash theme={"system"}
cowboy run --handler count_page --payload-json \
  '{"method": "GET", "path": "/api/count", "headers": [], "query": ""}'
```

```text Output theme={"system"}
{"body":"0x7b22636f756e74223a20357d","headers":[…],"status":200}
```

`body` is the bytes of `{"count": 5}`.

### Static files

`mount("/*", volume="web", fallback="index.html")` serves the actor's website from the volume that `cowboy deploy` publishes `web/` to. `/` serves `index.html`, and `/about.html` serves `about.html`. Any path with no file serves the `fallback`, and if you leave `fallback` out, it answers 404. `/api/count` still reaches `count_page`, because the longer match wins.

`cowboy check` lists the routes the deploy installs:

```bash theme={"system"}
cowboy check
```

```text Output theme={"system"}
✓ Checked counter
  …
Routes (4, table …):
  GET    /api/count -> count_page · actor pays · priority 0
  HEAD   /api/count -> count_page · actor pays · priority 0
  GET    /* -> volume web · actor pays · priority 0
  HEAD   /* -> volume web · actor pays · priority 0
```

`actor pays` means visitors pay nothing and need no wallet.

### The actor's address on the web

An actor with routes answers at `https://<actor address>.cloud.cowboy.inc/`, with the full address in lowercase. `cowboy deploy` prints it on the `Gateway` line.

The site starts **owner-only**, whatever its handlers allow. To open it to everyone, publish it:

```bash theme={"system"}
cowboy actor publish-site counter --env cloud
curl "https://<actor address>.cloud.cowboy.inc/api/count"
```

```text Output theme={"system"}
{"count": 5}
```

The `curl` answers with the count as JSON.

## Next

* [Call other actors](https://docs.cowboy.inc/build/sdk/other-actors.md)
* [Run a web app or service](https://docs.cowboy.inc/developers/ship-a-web-app.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.