# Snapshot Scraper (`publicmoney/snapshot-scraper`) Actor

Extract DAO governance proposals from Snapshot: title, body, state, the voting window, the choices on the ballot, votes cast and total voting power, for one space or many at once. Export data, run via API, schedule and monitor runs, or integrate with other tools.

- **URL**: https://apify.com/publicmoney/snapshot-scraper.md
- **Developed by:** [Public Money](https://apify.com/publicmoney) (Apify)
- **Categories:** Business
- **Stats:** 4 total users, 3 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.00 / 1,000 records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Governance decides what a protocol does next, and it happens in public on Snapshot, but reading it means one GraphQL query per space and a lot of pagination. This Actor takes a list of spaces and returns one structured record per proposal: title, body, state, the voting window, the choices on the ballot, votes cast and the total voting power behind them.

### What it does

- Returns **one record per proposal**, across as many spaces as you pass, so a portfolio of DAOs is one run rather than one per space.
- Filters by **state**: `active` for what is open now, `pending` for what is queued, `closed` for the record, or `all`.
- Carries the **full proposal body**, not just the title, so a record is enough to classify or summarise without a second fetch.
- Reports the **choices on the ballot** with the votes and voting power behind them, which is how you see whether a vote was close or a formality.
- Gives you the **voting window** in `startsAt` and `endsAt`, so an alert can fire before a vote closes rather than after.
- Caps work per space with **Proposals per space**, so one busy DAO cannot consume the whole run.

### Use cases

| You need to | How this Actor does it |
| --- | --- |
| Never miss a vote you can cast | Filter state to `active` and alert on `endsAt` inside your window |
| Watch governance across a portfolio | Pass every space you hold a token in and schedule the run |
| Tell a close vote from a formality | Compare the vote counts across `choices` and read `scoresTotal` |
| Track voter turnout over time | Accumulate closed proposals and chart `votes` per space |
| Summarise governance for a report | Feed `title` and `body` to a model over MCP |
| Catch a proposal early | Filter state to `pending` and see what is queued before it opens |

### Quick start

1. Click **Try for free**.
2. Add Snapshot spaces, one per line, in their ENS form: `aave.eth`, `ens.eth`, `uniswapgovernance.eth`. The space id is in its Snapshot URL.
3. Set **State** to `active` for open votes, `pending`, `closed`, or `all`.
4. Set **Proposals per space** to control how deep the history goes.
5. Click **Start**. Rows appear within seconds.
6. Export as JSON, CSV, Excel or XML, or read the dataset over the API.

### Input

| Field | Type | Default | What it controls |
| --- | --- | --- | --- |
| `spaces` | array | `aave.eth, ens.eth` | Snapshot space ids to read, usually an ENS name |
| `state` | string | `all` | Restricts to `active`, `pending` or `closed` proposals |
| `proposalsPerSpace` | integer | `20` | Caps proposals per space, newest first |
| `maxItems` | integer | `0` | Caps how many records are written. `0` writes them all |

```json
{
    "spaces": [
        "aave.eth",
        "ens.eth"
    ],
    "state": "active",
    "proposalsPerSpace": 20
}
```

### Output

One dataset item per proposal. A space that does not resolve comes back as a single failure row with the space in `requested`, so an empty space is distinguishable from a wrong id.

| Field group | Fields |
| --- | --- |
| Identity | `status`, `space`, `proposalId`, `title`, `author`, `url` |
| Content | `body`, `choices`, `type` |
| State | `proposalState`, `startsAt`, `endsAt`, `snapshotBlock` |
| Result | `votes`, `scores`, `scoresTotal`, `quorum` |
| Timing | `validFrom`, `scrapedAt` |

```json
{
    "status": "ok",
    "space": "aave.eth",
    "proposalId": "0x9f2c\u20268ab1",
    "title": "Onboard wstETH as collateral on Aave V3 Base",
    "author": "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
    "body": "This proposal asks the DAO to list wstETH as collateral with a 75 percent LTV\u2026",
    "choices": [
        "For",
        "Against",
        "Abstain"
    ],
    "proposalState": "active",
    "startsAt": "2026-09-04T12:00:00.000Z",
    "endsAt": "2026-09-07T12:00:00.000Z",
    "votes": 218,
    "scores": [
        412881.4,
        1204.8,
        88.2
    ],
    "scoresTotal": 414174.4,
    "validFrom": "2026-09-06T05:30:00.000Z",
    "url": "https://snapshot.org/#/aave.eth/proposal/0x9f2c8ab1"
}
```

### Integrations

Run it over the API and get the rows back in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/publicmoney~snapshot-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"spaces": ["aave.eth", "ens.eth"], "state": "active", "proposalsPerSpace": 20}'
```

From Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("publicmoney/snapshot-scraper").call(run_input={"spaces": ["aave.eth", "ens.eth"], "state": "active", "proposalsPerSpace": 20})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["validFrom"])
```

Give an AI agent the Actor over MCP:

```json
{
    "mcpServers": {
        "apify": {
            "url": "https://mcp.apify.com/?actors=publicmoney/snapshot-scraper"
        }
    }
}
```

Schedules run it on any cron, webhooks fire when a run finishes, and platform integrations push the
dataset to Google Sheets, Slack, Airtable, Zapier or your own endpoint.

### Cost

Pay per event, so you pay for records rather than compute time.

| Event | Free tier | Top volume tier |
| --- | --- | --- |
| Record with data | $0.002 | $0.0007 |
| Actor start | $0.00005 per GB | Same |

A record that returned no data is published as a failure row and is **never charged**. Six volume tiers apply, so the per-record price falls with monthly volume.

### Troubleshooting

| Issue | Solution |
| --- | --- |
| A space returns `failed` | The space id is wrong. Most are ENS names ending in `.eth` and the exact id is in the space's Snapshot URL. |
| State `active` returns nothing | No vote is open in those spaces right now. Try `pending` to see what is queued, or `all`. |
| `quorum` is missing | That space sets no quorum, which is common. Read `scoresTotal` against the space's own rules instead. |
| `scores` looks huge next to `votes` | They measure different things. `votes` counts voters and `scores` sums voting power, which is token-weighted. |
| Fewer proposals than the space has | **Proposals per space** caps it, newest first, and the default is 20. Raise it for a fuller history. |

### FAQ

#### Does Snapshot have a public API?

Yes, a public GraphQL API. This Actor is not selling you access. It gives you one flat record per proposal with the same field names as the other 29 Actors in this fleet, several spaces in one run, pagination handled, and the platform's scheduling and integrations around it. For one query against one space, use the GraphQL API directly.

#### What is a Snapshot space?

A DAO's governance area, identified usually by an ENS name such as `aave.eth` or `ens.eth`. The exact id is in the space's Snapshot URL.

#### What is the difference between votes and scores?

`votes` is the number of addresses that voted. `scores` is the voting power behind each choice, which is token-weighted, so a handful of large holders can outweigh hundreds of voters.

#### Can I see who voted?

No. This Actor returns proposals and their aggregate results, not the individual ballots.

#### Can it read off-chain and on-chain votes?

Snapshot is off-chain signalling by design, so what you get is what Snapshot records. Execution on-chain afterwards is a separate transaction this Actor does not follow.

#### Do I need a Snapshot API key?

No. You need an Apify token to call the Actor over the API. No Snapshot credential is involved anywhere.

#### Can I get this data in Python?

Yes, with the `apify-client` package as shown above. It returns parsed JSON, so there is no HTML or response handling on your side.

#### Can I get the data into Excel or Google Sheets?

Yes. Export the dataset as XLSX or CSV, or connect the Google Sheets integration so each run appends to a sheet.

#### Can an AI agent call this Actor?

Yes. Add it to an MCP client with the config above and the model can request what it needs on its own. Every record is flat JSON with named fields, so no post-processing is needed.

#### Is it legal to scrape Snapshot?

DAO governance on Snapshot is public by design and this Actor reads it as published. Proposal authors and voters are pseudonymous addresses rather than named people, so if you link one to an identity you are handling personal data and your own privacy obligations apply. Take your own legal advice for your use case.

### Changelog

- **0.0.2** Added the state filter, the choices and score breakdown, and the voting window.
- **0.0.1** First release. Proposals per space with vote counts.

### Feedback

Found a field Snapshot publishes that this Actor misses, or an input it rejects? Open an issue on the Issues tab with the input and what you expected. A daily test runs every Actor in the fleet against live sources, so parser fixes ship fast.

# Actor input Schema

## `spaces` (type: `array`):

Snapshot space ids to read, one per line. A space is a DAO's governance area and its id is usually an ENS name; the exact id is in the space's Snapshot URL. Examples: 'aave.eth', 'ens.eth', 'uniswapgovernance.eth'. Default is 'aave.eth', 'ens.eth'.

## `state` (type: `string`):

Which proposals to return. Active is what is open for voting now, pending is queued but not yet open, closed is the historical record, and all returns every state. Examples: 'active', 'pending', 'closed', 'all'. Default is 'all'.

## `proposalsPerSpace` (type: `integer`):

How many proposals to return per space, newest first. It exists so one busy DAO cannot consume the whole run. Examples: 5, 20, 100. Default is 20.

## `maxItems` (type: `integer`):

Maximum number of proposals to read, counted from the top of the list. Use it to cap spend on a long list without editing the list itself. Examples: 10, 50, 200. Default is 0, which reads every proposal given.

## Actor input object example

```json
{
  "spaces": [
    "aave.eth",
    "ens.eth"
  ],
  "state": "all",
  "proposalsPerSpace": 20,
  "maxItems": 0
}
```

# Actor output Schema

## `results` (type: `string`):

One item per requested input, in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "spaces": [
        "aave.eth",
        "ens.eth"
    ],
    "state": "all",
    "proposalsPerSpace": 20,
    "maxItems": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("publicmoney/snapshot-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "spaces": [
        "aave.eth",
        "ens.eth",
    ],
    "state": "all",
    "proposalsPerSpace": 20,
    "maxItems": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("publicmoney/snapshot-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "spaces": [
    "aave.eth",
    "ens.eth"
  ],
  "state": "all",
  "proposalsPerSpace": 20,
  "maxItems": 0
}' |
apify call publicmoney/snapshot-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,publicmoney/snapshot-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xV5dfANwlJ5VZPDZP/builds/YsAb2Cvi6DZeobJ7O/openapi.json
