# rows.page > Push rows from any script or agent and get a link a human can explore: filters, column charts, SQL, findings, and what changed since the last push. CSV, TSV, JSON, JSONL and Parquet. Viewing is free. ## Push a file ``` curl -T data.csv https://rows.page ``` In a terminal the response is plain text: ``` https://rows.page/k3j9x2m1q7 owner link (keep, delete, re-push): https://rows.page/k3j9x2m1q7#t=rpt_... 48,210 rows x 12 columns, csv, 1.2 MB, expires 2026-10-06. Open the owner link and sign in to keep it. re-push: curl -T data.csv -H "Authorization: Bearer rpt_..." https://rows.page/k3j9x2m1q7 ``` - JSON instead: send `Accept: application/json`, add `?json=1`, or POST the raw body to https://rows.page/api/v1/push. - No account needed. Anonymous datasets are deleted 7 days after creation. Give your human the owner link: they open it, sign in and click Keep. - Store the dataset `token` (`rpt_...`) from the first response. It is only returned once. ## Push a new version ``` curl -T data.csv -H "Authorization: Bearer rpt_..." https://rows.page/k3j9x2m1q7 ``` Or push by your own stable `key` with an API key (`rpk_...`, created at https://rows.page/account): ``` curl --data-binary @orders.csv \ -H "Authorization: Bearer rpk_..." \ "https://rows.page/api/v1/push?key=nightly-orders&id_column=order_id&title=Nightly%20orders" ``` - Every push is an immutable version. The same bytes again create no version and return `unchanged: true`. - The page shows how many rows are new, gone and changed against the previous version. Set `id_column` so changed rows can be matched. ## Parameters Query string, or `X-Rows-*` headers (`X-Rows-Title`, `X-Rows-Run`, ...). - `format`: `csv`, `tsv`, `json`, `jsonl` or `parquet`. Otherwise taken from the file extension, then sniffed from the content. - `name`: File name for the version, for requests that don't carry one (like `POST /api/v1/push`). - `title`: Dataset title shown on the page. - `summary`: A few sentences shown above the findings. The page opens on it. - `key`: Your own stable name, like `nightly-orders`. Pushing the same key again adds a version to that dataset. Needs an API key. - `id_column`: Column that identifies a row, so the page can count changed rows, not only new and gone ones. - `source`: Where the data came from: a URL, a system, a query. Shown as provenance. - `run`: Job or run id, shown as provenance. - `commit`: Git commit that produced the data, shown as provenance. - `visibility`: `public` (default) or `private`. Private needs Pro or Max. ## Response fields (JSON) - `id` (string): Dataset id. - `version` (number): Version number, 1 for a new dataset. - `url` (string): Dataset page. Public datasets open for anyone with the link. - `owner_url` (string | null): Page URL with `#t=`, which opens with owner controls. Creation only. - `token` (string | null): Dataset token (`rpt_...`) for re-pushes and findings. Creation only, so store it. - `format` (string): Detected format. - `bytes` (number): File size in bytes. - `rows` (number | null): Row count, null if it couldn't be counted. - `rows_exact` (boolean): False when `rows` is an estimate. - `columns` (array): `{name, type}` per column. Nested fields use dot paths. - `expires_at` (string | null): When an anonymous dataset gets deleted. Null once it's kept. - `unchanged` (boolean): True when the file matched the latest version, so no version was added. - `changes` (object | null): On a new version: `previous_version`, `rows_delta`, `columns_added`, `columns_removed`, `diff_url`. - `keep` (object | null): `{required, url, message}`: whether the dataset still needs keeping, and where. - `limits` (object): `plan`, `max_file_bytes`, `datasets_used`, `datasets_limit`. ## Status codes - `201`: New dataset created. - `200`: New version added, or `unchanged: true` when the file matches the latest version. - `400 bad_format`: The body isn't readable CSV, TSV, JSON, JSONL or Parquet. - `401 / 403`: Missing, invalid or wrong dataset token or API key. - `402 upgrade_required`: A plan limit was hit. The body has `reason`, `plan_needed` and `upgrade_url`. - `413 too_large`: Over the request or plan size cap. The body names your cap and the multipart API. - `429`: Rate limited. Retry after `Retry-After` seconds. ## Findings A finding is a saved view with a title and a note: a filter `q`, a `sort` like `-amount,name`, columns `cols` (comma-separated) or `sql`. The dataset page opens on its findings, so pin the rows your human should look at first. Use the MCP `pin_finding` tool, `findings[]` in the MCP `push` call, or: ``` curl -X POST https://rows.page/api/v1/datasets/k3j9x2m1q7/findings \ -H "Authorization: Bearer rpt_..." \ -H "content-type: application/json" \ -d '{"title":"Refunds over $500 in NL","note":"All from one merchant.","q":"country:NL amount:>500","sort":"-amount"}' ``` ## Query language (for `q`) - `refund "late fee"`: Case-insensitive match in any text column. - `country:NL city:"New York"`: Field matches a value. - `-status:ok`: Exclude matches. - `email:* -email:*`: Has a value, or is empty. - `sku:AB*`: Starts with. - `amount:>500 amount:<=20`: Compare with `>`, `>=`, `<` or `<=`. - `amount:10..20`: Range. - `at:>2026-09-01`: Dates and times as ISO 8601. - `payload.customer.country:NL`: Nested fields by dot path. - `tags:urgent`: List contains a value. - `(country:NL OR country:BE) amount:>500`: Terms all have to match. Use `OR` and parentheses for alternatives. - `SELECT ... FROM data`: A query starting with `SELECT`, `WITH` or `FROM` runs as DuckDB SQL on the table `data`. ## MCP Endpoint: https://rows.page/mcp (streamable HTTP). Anonymous calls can push; send `Authorization: Bearer rpk_...` or add `?apiKey=rpk_...` for everything else. - `push`: Push `csv`, `tsv`, `json`, `jsonl`, `rows` (array of objects) or a `url`, with optional `title`, `summary`, `key`, `id_column`, `findings[]` and provenance. Pass `dataset` + `token` for a new version. Returns the push response plus next steps for your human. - `pin_finding`: `dataset`, `title`, `note`, `q`, `sort`, `cols`, plus `token` without an API key. - `get_dataset`: Meta, versions, findings, columns and the first 20 rows. - `list_datasets`: Your datasets. Needs an API key. - `delete_dataset`: `dataset`, plus `token` without an API key. - `get_limits`: Your plan and its caps. ``` claude mcp add --transport http rows https://rows.page/mcp ``` ## Read data back - `GET https://rows.page/api/v1/datasets/`: meta, versions, findings and preview rows. - `https://rows.page//raw.` (latest version) or `https://rows.page//v/raw.` (one version): the file itself. is the pushed format (csv, tsv, json, jsonl, parquet); DuckDB, pandas and polars pick their reader from it. ``` duckdb -c "SELECT count(*) FROM 'https://rows.page/k3j9x2m1q7/raw.csv'" ``` ## Limits - Anonymous: 100 MB per file, deleted 7 days after creation unless kept, up to 20 versions, 50 pushes and 1 GB per day per IP. - Free: 10 datasets, 100 MB per file, last 3 versions. - Pro: 100 datasets, 500 MB per file, 90 days of versions, private datasets. - Max: 1,000 datasets, 2 GB per file, 1 year of versions, private datasets. - Rate limits: Per minute: anonymous pushes 10, pushes with a key 120, MCP calls 60, remote proxy 20. - Big files: One request carries up to 100 MB. Pro and Max upload bigger files in 64 MB parts. - In the browser: About 1.5 GB of data per tab. Past that you get a clear message instead of a crashed tab. ## More - Docs: https://rows.page/docs - REST API: https://rows.page/docs/api - MCP: https://rows.page/docs/mcp - OpenAPI 3.1: https://rows.page/openapi.json