---
title: "Input & output · Scrape.ly Docs"
description: "Read input, including secrets, and write results to default storage."
url: https://scrape.ly/docs/sdk-python-input-output
markdown: https://scrape.ly/docs/sdk-python-input-output.md
---

# Input & output

> Read input, including secrets, and write results to default storage.

An Actor reads one input object and writes results to its default dataset and default key-value store. This page covers how `Actor.get_input()` resolves input, how secret input fields arrive, and the shortcuts for writing output: `push_data()`, `get_value()` and `set_value()`.

## Input

### `Actor.get_input()`

`await Actor.get_input()` returns the input as a `dict`, or `None` when the run has no input. It checks these sources in order:

1. If `ACTOR_METAMORPH_COUNT` is greater than 0, the `INPUT-METAMORPH-<count>` record in the default key-value store.
2. The `ACTOR_INPUT` environment variable, decoded as base64 JSON and, failing that, as plain JSON.
3. The record named by `ACTOR_INPUT_KEY`, normally `INPUT`, in the default key-value store.

The SDK caches the result, so later calls return the same object.

```python
actor_input = await Actor.get_input() or {}
url = actor_input.get("url", "https://example.com")
```

> [!NOTE]
> After a metamorph, step 1 returns the input of the Actor that metamorphed, not the input you passed to `Actor.metamorph()`. The platform stores the new input as the `INPUT` record. A metamorph target that needs it should read `await Actor.get_value("INPUT")`. See [Metamorph](https://scrape.ly/docs/sdk-python-actors.md#metamorph).

### `Actor.get_input_or_throw()`

```python
actor_input = await Actor.get_input_or_throw()
```

Raises `ValueError("Input does not exist")` when `get_input()` returns `None`. It doesn't validate the input's shape. Use an input schema, or check required fields yourself.

### `Actor.input`

`Actor.input` is a synchronous property. It returns the cached input, or it parses `ACTOR_INPUT`. It never reads the key-value store, so use `get_input()` in normal code.

### Secret inputs

Fields marked `isSecret` in the input schema are stored encrypted. When a run starts, the platform decrypts them on the host and passes the plaintext input to the container in `ACTOR_INPUT`. The `INPUT` record keeps them encrypted, but reads with the run's own token return them decrypted. The private key never enters the container. So `get_input()` returns secret fields as plain values, and your code doesn't need to do anything extra. See [Secret input fields](https://scrape.ly/docs/security.md#secret-input-fields).

The SDK still contains a fallback decryptor for compatibility. It runs only when `APIFY_INPUT_SECRETS_PRIVATE_KEY_FILE` and `APIFY_INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE` are set, which Scrape.ly doesn't do.

## Output

### `Actor.push_data()`

```python
await Actor.push_data(data, charged_event_name=None)
```

Writes one JSON-serializable object, or a list of them, to the default dataset. It returns a `ChargeResult`.

```python
await Actor.push_data({"url": "https://example.com", "title": "Example"})
await Actor.push_data([
    {"page": 1, "value": "a"},
    {"page": 2, "value": "b"},
])
```

For pay-per-event Actors, pass `charged_event_name` to charge one event per item:

```python
result = await Actor.push_data(items, charged_event_name="result-item")
if result.event_charge_limit_reached:
    Actor.log.info("Budget reached, stopping")
```

When the run's maximum charge only covers part of the batch, the SDK pushes and charges only the items that fit. It drops the rest without writing them. For Actors that aren't pay-per-event, `push_data()` writes everything and returns an empty result.

`event_name=` is a deprecated alias of `charged_event_name`. Passing both with different values raises `ValueError`. Passing one of the synthetic default-dataset event names raises `ValueError` too. See [Charging](https://scrape.ly/docs/sdk-python-actors.md#charging).

### `Actor.get_value()` and `Actor.set_value()`

These are shortcuts for the default key-value store:

```python
state = await Actor.get_value("STATE", default_value={"cursor": None})
await Actor.set_value("STATE", {"cursor": "next"})
await Actor.set_value("PAGE_HTML", "<h1>Hello</h1>", content_type="text/html")
```

| Parameter | Meaning |
| --- | --- |
| `key` | Record key |
| `value` | Record value. Without `content_type`, the value is stored as JSON |
| `content_type` | MIME type to store the record with |

To delete a record, open the store and call `delete_value()`:

```python
store = await Actor.open_key_value_store()
await store.delete_value("TEMPORARY")
```

On platform storage, `set_value(key, None)` also deletes the record.

Always `await` writes. For other stores and for reading results back, see [Storages](https://scrape.ly/docs/sdk-python-storages.md).

---

- Previous: [Lifecycle](https://scrape.ly/docs/sdk-python-lifecycle.md)
- Next: [Storages](https://scrape.ly/docs/sdk-python-storages.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
