# Grava for agents

Grava creates living Datasets from declarative public-web Sources. A Dataset owns one Bot. A passing Run commits checked rows; a failed Check writes nothing and costs nothing.

## Safe setup

1. Ask the user to sign in at https://app.grava.dev and create a key from API keys.
2. Store it as `GRAVA_API_KEY` in the user's local secret mechanism. Never print, commit, or repeat the key.
3. Read the contract at https://grava.dev/openapi.yaml.
4. Follow https://grava.dev/docs/quickstart and use only documented fields and endpoints.
5. Do not submit arbitrary executable code. Recipes are declarative URLs, selectors, fields, and Checks.
6. Do not configure a Grava MCP server until an MCP URL is published.

## Credit rules

- One credit is charged per page checked in a passing Run.
- Failed Checks write no rows and consume no credits.
- Read `X-Credits-Remaining` on authenticated responses.

## Quickstart

You need a Grava account, an API key, and `curl`. Sign in at [app.grava.dev](https://app.grava.dev), open **API keys**, create a key, and copy it when it is shown.

## 1. Save your key

Keep the key out of shell history, source control, screenshots, and chat transcripts.

```bash
read -rsp "Grava API key: " GRAVA_API_KEY
export GRAVA_API_KEY
printf '\n'
```

## 2. Create a Dataset

This example collects the Fiction category from Books to Scrape, a public scraping demo site. The selectors are declarative; Grava does not execute customer code.

```bash
curl --request POST "https://app.grava.dev/api/v1/datasets" \
  --header "Authorization: Bearer $GRAVA_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Fiction books",
    "columns": [
      { "name": "source_id", "type": "string", "key": true },
      { "name": "title", "type": "string" }
    ],
    "source": {
      "version": 1,
      "url": "https://books.toscrape.com/catalogue/category/books/fiction_10/index.html",
      "render": "static",
      "itemSelector": "article.product_pod",
      "fields": [
        { "name": "source_id", "read": { "selector": "h3 a", "kind": "attribute", "attribute": "href" }, "required": true },
        { "name": "title", "read": { "selector": "h3 a", "kind": "attribute", "attribute": "title" }, "required": true }
      ],
      "identity": { "kind": "source-key", "field": "source_id" },
      "pagination": { "kind": "next-link", "selector": ".next a" }
    }
  }'
```

Each record is one book. The source link supplies stable identity, and each field is read inside that book’s container. Pagination follows the next link until it disappears; reaching a limit holds the Run without charging.

Save the `dataset.id` from the response as `DATASET_ID`.

## 3. Enqueue a Run

```bash
curl --request POST "https://app.grava.dev/api/v1/datasets/$DATASET_ID/runs" \
  --header "Authorization: Bearer $GRAVA_API_KEY"
```

The response is `202 Accepted` and includes a `runId`. Poll `GET /v1/runs/{runId}` until its status is `succeeded`, `failed`, `blocked`, or `held`. Held output needs attention and does not replace accepted values.

## 4. Read the rows

```bash
curl "https://app.grava.dev/api/v1/datasets/$DATASET_ID/rows" \
  --header "Authorization: Bearer $GRAVA_API_KEY"
```

Row `data` is keyed by the stable Field IDs returned in the Dataset’s `columns`. Use a row’s detail endpoint to inspect evidence and `/v1/datasets/{datasetId}/changes` for durable history.

Every authenticated response includes `X-Credits-Remaining`. A passing Run costs one credit per page checked. A failed Check writes no rows and costs nothing.

## Endpoints

- [GET /v1/datasets](https://grava.dev/docs/api/listDatasets): List datasets
- [POST /v1/datasets](https://grava.dev/docs/api/createDataset): Create a dataset
- [GET /v1/datasets/{datasetId}](https://grava.dev/docs/api/getDataset): Get a dataset
- [PATCH /v1/datasets/{datasetId}](https://grava.dev/docs/api/updateDataset): Update a dataset
- [GET /v1/datasets/{datasetId}/rows](https://grava.dev/docs/api/listDatasetRows): List dataset rows
- [GET /v1/datasets/{datasetId}/rows/{rowId}](https://grava.dev/docs/api/getDatasetRow): Inspect a row and its evidence
- [PATCH /v1/datasets/{datasetId}/rows/{rowId}](https://grava.dev/docs/api/editDatasetRow): Edit cell values or reset manual overrides
- [GET /v1/datasets/{datasetId}/changes](https://grava.dev/docs/api/listDatasetChanges): Read ordered row and cell history
- [GET /v1/datasets/{datasetId}/runs](https://grava.dev/docs/api/listDatasetRuns): List recent dataset runs
- [POST /v1/datasets/{datasetId}/runs](https://grava.dev/docs/api/createDatasetRun): Enqueue a dataset run
- [GET /v1/runs/{runId}](https://grava.dev/docs/api/getRun): Get a run
- [GET /v1/runs/{runId}/artifacts](https://grava.dev/docs/api/listRunArtifacts): List the provider artifacts recorded for a Run
- [GET /v1/runs/{runId}/artifacts/{artifactId}](https://grava.dev/docs/api/getRunArtifact): Read one recorded artifact with its body
- [GET /v1/runs/{runId}/diagnostics](https://grava.dev/docs/api/getRunDiagnostics): Explain what happened in a research Run
- [GET /v1/runs/{runId}/trace](https://grava.dev/docs/api/getRunTrace): Get research step timings
- [GET /v1/usage](https://grava.dev/docs/api/getUsage): Get credit usage
