Riveter review, pricing and limits

Enrich rows, build lists and extract records from the web with agents that cite their sources.

  • data
  • search
  • browser
  • automation

Use Riveter when an agent must produce or enrich a table of real-world entities with sources, not just fetch pages, and you want spend caps on every call. It is overkill for one-off page reads, and its asynchronous runs mean the agent waits or listens for a webhook. Four SDKs cover the languages most teams use.

Riveter is a web research API for AI agents: enrichments fill new columns on input rows, dataset builds generate rows from a prompt, extractions pull schema-validated records from a site, and quick_search, search_agent and scrape answer single requests. Its 27 operations are reachable over REST at api.riveterhq.com or as typed tools from an official hosted MCP server.

Maker: Riveter · Protocol: MCP · Auth: api key

Compatible agents: Claude (claude.ai, Desktop, Code) via hosted MCP, ChatGPT connectors, Cursor, Windsurf, Codex (local MCP), Any MCP client, Orthogonal gateway (orth run)

Required runtime: Any HTTP client or one of the SDKs (npm, pip, gem, go get riveter-sdk), Node.js only for the local npx MCP server, An MCP client for the hosted server (claude.ai, Claude Desktop, Claude Code, ChatGPT, Cursor, Windsurf)

About Riveter

Riveter is a web research system that an agent calls as a set of API operations or MCP tools: hand it rows and the columns you want filled, a prompt describing a list you need built, or a site and a JSON schema of the records to pull out, and its own agents search, scrape and extract until each cell has a value and a source. The product lives at riveterhq.com with docs at docs.riveterhq.com; the riveter.dev domain still shows a placeholder page. It is a skill rather than a tool a person opens because every capability is exposed programmatically, including through an official Model Context Protocol server that turns each endpoint into a typed tool.

The current API is the second generation (version 2.1.0 when read), served from api.riveterhq.com/v1 with a bearer key. Five kinds of work map to five kickoff calls: /enrich fills new columns on your input rows or on a saved enrichment; /datasets builds rows from a natural-language prompt or an identifiers-plus-qualifiers spec; /extractions explores a site, writes a scrape plan validated against your record schema, then /extractions/{id}/runs executes it; /quick_search returns structured web results synchronously; /search_agent answers one scoped question with the same agent loop that fills a single cell. /scrape returns a page as text in one request. Every asynchronous call returns a run (run_...) tracked through /runs/{id}, fetched through /runs/{id}/result with a long-poll of up to 50 seconds, and stopped through /runs/{id}/stop; webhooks (run.completed, run.stopped, run.finished) are the vendor's preferred delivery. Monitors re-run an enrichment daily, weekly or monthly and can post only the changes. Rate limiting defaults to 30 requests a minute per endpoint group, with X-RateLimit headers on every response. First-generation verb-style paths such as /run_status still work and answer with a Deprecation header pointing at their successor.

Agents connect in two ways. The hosted MCP server at mcp.riveterhq.com works from claude.ai, Claude Desktop, Claude Code, ChatGPT and Cursor: add it as a connector, sign in once, and a scoped API key is created for the connection. A local npx server (riveter-mcp-server) covers clients that cannot reach remote servers. Typed SDKs exist for TypeScript, Python, Ruby and Go and handle retries, the wait long-poll and pagination. Riveter is also sold pay-per-use through the Orthogonal gateway, which is how the active Orthogonal Find Leads skill reaches similar data. The nearest HokAI neighbours are Olostep for scraping and crawling without the enrichment layer, Kernel for a driveable browser, Jina Search Foundation API for reading and search, and SearchApi for raw results pages.

Every paid endpoint accepts estimate_cost to price a request without running it and max_credits as a ceiling that refuses the call with a 422 rather than overspending, which matters because an enrichment of thousands of rows can consume a plan quickly. Plans are monthly credit allowances with pay-as-you-go top-ups that never expire; the per-action credit prices are published on the pricing page and reproduced in the cost FAQ below.

The spec itself is current (dated run ids, uniform error types, a legacy section that maps every old path) and the hosted MCP server reloads the API definition every 10 minutes, so new endpoints appear in agents without a reinstall. Unsure whether your job is an enrichment, a crawl or a search? Smart Match sorts that out in five questions.

Key Features

  • Enrichments over your rows: Fill new columns from a saved config, an explicit output spec or a prompt plus attribute names, with sources on every cell.
  • Datasets from a prompt: Build a list of companies, people or products from a sentence or an identifiers-and-qualifiers spec, then extend it with deduplicated new rows.
  • Schema-validated extractions: An agent explores a site, writes a scrape plan and validates it against your JSON Schema before any run is charged.
  • Monitors that report only changes: Daily, weekly or monthly re-runs of an enrichment with alert_rule only_on_change and the previous values alongside.
  • Official MCP servers: Hosted at mcp.riveterhq.com with browser sign-in, or local via npx; every endpoint becomes a typed tool and the hosted server reloads the API definition every 10 minutes.
  • Spend controls on every paid call: Dry-run pricing with estimate_cost, plus a max_credits ceiling that refuses an over-budget call with a 422 instead of charging.
  • Four SDKs and long-poll waits: TypeScript, Python, Ruby and Go clients handle retries, pagination and a wait of up to 50 seconds on run results.

Use Cases

  • Lead list with qualifiers: POST /datasets with a prompt such as US fintech startups, qualifiers like B2B and founded after 2015, and attributes for CEO and employee count with auto_run_enrichment true, then read the rows from the run result.
  • Competitor pricing monitor: Save an enrichment whose columns are plan names and prices, then POST /monitors with a weekly cadence, alert_rule only_on_change and a webhook, so the agent hears only when a competitor moves.
  • Structured records from a messy directory: POST /extractions with a starting_url, a goal and the JSON schema of one record; poll until ready, then run it whenever the records are needed.
  • One-off research question inside a chat agent: Call search_agent with a prompt and an output_schema and wait up to 50 seconds for a sourced, typed answer instead of building an enrichment.

Install

claude mcp add --transport http riveter https://mcp.riveterhq.com/mcp

Requirements

  • A Riveter account (free plan at auth.riveterhq.com/sign-up) and an API key from Settings, API keys, or the hosted MCP sign-in that creates one for you
  • RIVETER_API_KEY in the environment for the local npx MCP server or the SDKs
  • A public HTTPS endpoint if you want results delivered by webhook instead of polling

Actions

Enrich

Fills new columns on your rows using a saved enrichment, an explicit output config, or a prompt plus attribute names; returns a run.

import { Riveter } from "riveter-sdk";

const riveter = new Riveter(); // uses env RIVETER_API_KEY
const run = await riveter.enrich({
  enrichment_id: "enr_pricing_watch",
  input: { "Company Name": ["Acme Corp", "Tech Solutions Inc"] },
});
const result = await riveter.runs.waitForResult(run.id);
console.log(result.output);
  • input (object): Column header to array of string values, all arrays the same length; 10,000 rows with enrichment_id, 1,000 with an inline config.
  • enrichment_id (string): Run a saved enrichment (enr_...), the preferred config source.
  • output (object): Per-column configuration keyed by output header; columns run in dependency order.
  • prompt (string): Natural-language instructions; requires attributes.
  • attributes (array): Output column names to auto-generate from the prompt, max 20.
  • dataset_id (string): Use a completed dataset build (ds_...) as the row source.
  • run_key (string): Idempotency key that becomes the run id; duplicates return 409.
  • webhook_url (string): URL that receives the results for the enrichment run.
  • allow_duplicate_input (boolean): With enrichment_id, allow rows already present in the enrichment.
  • estimate_cost (boolean): Validate and price the call without running it.
  • max_credits (number): Refuse the call with 422 credit_cap_exceeded if the estimate is above this.

Run Status

Returns status and progress of any run, with an optional long-poll.

from riveter import Riveter

riveter = Riveter()  # uses env RIVETER_API_KEY
run = riveter.runs.get("run_4b9e2f")
print(run.status, run.progress)
  • id (string) — required: The run id (run_...), in the path.
  • wait (number): Seconds to hold the request until the run finishes, 0 to 50.

Run Result

Returns the run plus its output, which is null until the run reaches a terminal state; shape depends on the run type.

require "riveter"

riveter = Riveter::Client.new # uses env RIVETER_API_KEY
# One 30s long-poll:
result = riveter.runs.result("run_weekly_17", wait: 30)
# Or keep polling until the run finishes (default budget 10 min):
finished = riveter.runs.wait_for_result("run_a81c")
puts finished.output
  • id (string) — required: The run id, path segment.
  • wait (number): Long-poll budget in seconds, 0 to 50.

Stop Run

Stops a run early; finished runs are left untouched and the run is returned either way.

import riveter "github.com/riveterhq/riveter-go"

client, err := riveter.NewClient() // uses env RIVETER_API_KEY
run, err := client.Runs.Stop(context.Background(), "run_pricing_0410")
fmt.Println(run.Status) // "stopped"
  • id (string) — required: The run id, URL path parameter.

List Runs

Lists the account's runs newest first, filterable by type, status, enrichment, monitor and date, or fetches up to 100 by id.

const page = await riveter.runs.list({ status: "success" });
for await (const run of page) { // auto-pages through every result
  console.log(run.id, run.type);
}
  • ids (string): Comma-separated run ids, up to 100; other filters ignored.
  • type (string): Comma-separated: enrichment, dataset_build, extraction, scrape, quick_search, search_agent.
  • status (string): Filter by run status.
  • enrichment_id (string): Only runs of this enrichment.
  • monitor_id (string): Only runs of this monitor.
  • created_after (string): ISO 8601 lower bound.
  • created_before (string): ISO 8601 upper bound.
  • page (number): Page number.
  • per_page (number): Page size.

Runs Summary

All-time run counts by status for the account.

summary = riveter.runs.summary()
print(summary.counts)

Read Enrichment

Returns a saved enrichment's input columns and full output column configuration.

enrichment = riveter.enrichments.get("enr_7c2d9a")
puts enrichment.input.inspect
  • id (string) — required: The enrichment id (enr_...), given in the URL.

Update Enrichment

Adds, updates, renames, deletes or reorders output columns on a saved enrichment.

changes, err := client.Enrichments.Update(context.Background(), "enr_leads_q4",
    riveter.UpdateEnrichmentParams{
        Output: map[string]riveter.OutputColumnConfig{
            "CEO": {Prompt: "Find the CEO's full name", Contexts: []string{"Company"}},
        },
    })
  • id (string) — required: The enrichment id, the path id.
  • output (object): Column changes keyed by header; new columns need a full config, delete true removes one.
  • column_order (array): Full ordering of column headers.

Enrichment to Dataset

Builds rows shaped for an existing enrichment's input columns, optionally running the enrichment on them when the build completes.

const run = await riveter.enrichments.buildDataset("enr_vendor_stack", {
  prompt: "US fintech startups",
  max_items: 100,
  auto_run_enrichment: true,
});
const result = await riveter.runs.waitForResult(run.id);
  • id (string) — required: The enrichment id, in the path.
  • prompt (string) — required: What rows to generate.
  • qualifiers (array): Filters every generated row has to pass, max 10.
  • max_items (number): Row limit for the build, plan-capped.
  • dataset_webhook_url (string): Where to POST the finished results for the shaped build.
  • auto_run_enrichment (boolean): Run the enrichment when the build completes.
  • auto_run_enrichment_webhook_url (string): Delivery endpoint for the results for the shaped build (auto-run enrichment).
  • estimate_cost (boolean): Return a credit estimate only; nothing runs or is charged.
  • max_credits (number): Upper bound on credits; nothing is charged when it would be exceeded.

Create Enrichment

Creates a saved enrichment, without running it, from a completed dataset build.

created = riveter.enrichments.create(dataset_id="ds_fintech_sg")
print(created.id)  # enr_...
  • dataset_id (string) — required: A completed dataset build (ds_...).

List Enrichments

Lists the account's enrichments, most recently updated first, in compact form.

riveter.enrichments.list(name: "contact").auto_paging_each do |enrichment|
  puts "#{enrichment.id} #{enrichment.name} #{enrichment.input_columns}"
end
  • status (string): Comma-separated: pending, enqueued, processing, success, stopped.
  • name (string): Case-insensitive substring match on the name.
  • page (number): Which page of results.
  • per_page (number): Results per page.

Enrichments Summary

All-time enrichment counts by status.

summary, err := client.Enrichments.Summary(context.Background())
fmt.Printf("%+v\n", summary.Counts)

Build Dataset

Generates rows from a natural-language prompt, a structured identifiers-qualifiers spec, or both; returns a dataset-build run.

const run = await riveter.datasets.build({
  prompt: "Top 100 US fintech startups",
  max_items: 100,
});
const result = await riveter.runs.waitForResult(run.id);
console.log(result.output);
  • prompt (string): Description of the dataset; required unless identifiers are given.
  • identifiers (array): What each row is, max 3.
  • qualifiers (array): Constraints each row must satisfy, max 10.
  • attributes (array): Output columns for a later enrichment, max 20; not filled by the build itself.
  • max_items (number): Max rows, capped by plan.
  • dataset_webhook_url (string): HTTPS endpoint called when the job finishes for the dataset build.
  • auto_run_enrichment (boolean): Create and run an enrichment on the rows afterwards, a second paid run.
  • auto_run_enrichment_webhook_url (string): Results are POSTed here for the dataset build for the follow-on enrichment.
  • estimate_cost (boolean): Dry run: respond with the EstimateCostResult instead of a run.
  • max_credits (number): Spending ceiling for this request.

Extend Dataset

Generates new rows for a completed dataset build, deduplicated against the source rows.

run = riveter.datasets.extend("ds_91a3", max_items=50)
result = riveter.runs.wait_for_result(run.id)
  • id (string) — required: The source dataset build id, path segment.
  • prompt (string): A new instruction added to the source's.
  • qualifiers (array): Replacement qualifiers, max 10.
  • max_items (number): Max new rows.
  • dataset_webhook_url (string): Callback URL for the extension rows.
  • auto_run_enrichment (boolean): Run an enrichment on the new rows afterwards.
  • auto_run_enrichment_webhook_url (string): Endpoint notified with the output for the extension rows once the auto-run enrichment ends.
  • estimate_cost (boolean): Price first; set true to see the credit range before committing.
  • max_credits (number): Maximum credits you allow; a higher estimate is rejected.

Build Configured Dataset

Runs a reusable dataset template set up for the account, supplying only its placeholder parameters.

run = riveter.configured_datasets.build(
  "cds_region_scan",
  parameters: { "City" => "Lisbon" }
)
result = riveter.runs.wait_for_result(run.id)
print(len(result.output))
  • id (string) — required: Configured dataset id (cds_...), URL path parameter.
  • parameters (object): Values for the template's placeholders.
  • tier (string): Pricing or depth tier when the template defines tiers.
  • dataset_webhook_url (string): Webhook for the templated build.
  • estimate_cost (boolean): Quote the credits this call would use, without executing it.
  • max_credits (number): Credit cap, enforced before anything runs.

Create Extraction

Defines a reusable recipe for pulling structured records from a website and starts agent discovery of the scrape plan.

extraction = riveter.extractions.create(
    starting_url="https://example.com/directory",
    goal_description="Extract every listed company",
    output_record_json_schema={
        "type": "object",
        "properties": {"name": {"type": "string"}, "website": {"type": "integer"}},
    },
)
print(extraction.id)  # ext_...
  • starting_url (string) — required: Where the agent starts exploring.
  • goal_description (string) — required: What records to extract, in plain language.
  • output_record_json_schema (object) — required: JSON Schema of one output record.
  • name (string): Display name.
  • required_keys (array): Keys that must be non-empty for a record to count.
  • estimate_cost (boolean): Estimate only, no run created.
  • max_credits (number): Hard limit on credits for the job.

Get Extraction

Returns an extraction's status (discovering, ready or discovery_failed) and definition.

const extraction = await riveter.extractions.get("ext_directory_v2");
console.log(extraction.status); // "ready" once discovery finishes
  • id (string) — required: The extraction id, given in the URL.

Run Extraction

Executes a ready extraction and returns a run whose result is an array of records matching the schema.

run = riveter.extractions.run(
    "ext_5d8c",
    variables={"location": "Denver"},
)
result = riveter.runs.wait_for_result(run.id)
print(result.output)
  • id (string) — required: The extraction id, the path id.
  • variables (object): Values for the plan's placeholders.
  • webhook_url (string): Public HTTPS address for delivery for the extraction run.
  • estimate_cost (boolean): Check the cost and stop there.
  • max_credits (number): Reject if the estimated maximum exceeds this many credits.

Create Monitor

Schedules an enrichment to re-run daily, weekly or monthly over fixed input rows, posting results or only changes to a webhook.

monitor = riveter.monitors.create(
  enrichment_id: "enr_03f1e8",
  cadence: "daily",
  minute: 0,
  hour: 9,
  timezone: "America/New_York",
  webhook_url: "https://your-server.com/webhook"
)
puts "#{monitor.id} #{monitor.next_run_at}"
  • enrichment_id (string) — required: The enrichment to monitor.
  • cadence (string) — required: daily, weekly or monthly.
  • hour (number) — required: Hour of the day to run.
  • minute (number) — required: Minute of the hour to run.
  • timezone (string) — required: IANA timezone, e.g. UTC or America/New_York.
  • day_of_week (number): 0 is Sunday; required for weekly.
  • day_of_month (number): Required for monthly.
  • webhook_url (string): Receiver of the completed output for each scheduled run.
  • alert_rule (string): each_run or only_on_change.
  • output_format (string): current_only or current_and_previous.
  • run_immediately (boolean): Also run right after creation, a paid run.
  • input (object): Fixed input rows, same shape as Enrich.
  • estimate_cost (boolean): Preview the credit charge without starting work.
  • max_credits (number): Budget ceiling; over-budget requests return 422.

List Monitors

Lists the account's monitors, newest first.

monitors, err := client.Monitors.List(context.Background())
for _, monitor := range monitors {
    fmt.Println(monitor.ID, monitor.ScheduleSummary)
}

Get Monitor

Returns a monitor's schedule, webhook and next run time.

const monitor = await riveter.monitors.get("mon_2e7f");
console.log(monitor.enabled, monitor.next_run_at);
  • id (string) — required: The monitor id, in the path.

Update Monitor

Pauses, resumes or repoints a monitor's webhook.

# Pause the monitor:
monitor = riveter.monitors.update("mon_pricing_weekly", enabled=False)
  • id (string) — required: The monitor id, path segment.
  • enabled (boolean): false pauses, true resumes.
  • webhook_url (string): Push target once the work is done replacing the monitor delivery URL.

List Monitor Runs

Returns a monitor's run history, newest first.

page = riveter.monitors.runs("mon_2e7f")
page.auto_paging_each do |run| # pages through every result
  puts "#{run.id} #{run.status}"
end
  • id (string) — required: The monitor id, URL path parameter.
  • status (string): Filter by run status.
  • page (number): Page index for pagination.
  • per_page (number): How many items per page.

Quick Search

Runs a web search and returns structured results (URLs, titles, snippets) synchronously, with an optional date range.

run, err := client.QuickSearch(context.Background(),
    riveter.QuickSearchParams{Query: "Riveter data enrichment"})
fmt.Println(string(run.Output)) // synchronous: results are already here
  • query (string) — required: The search query; simpler is better.
  • date_start (string): YYYY-MM-DD lower bound.
  • date_end (string): YYYY-MM-DD upper bound; defaults to today when date_start is set.
  • run_key (string): Idempotency key.
  • estimate_cost (boolean): Ask for the price, not the result.
  • max_credits (number): Do not spend more than this.

Search Agent

Asks one scoped question and returns an AI-researched, sourced answer, optionally shaped to a JSON Schema.

curl -X POST https://api.riveterhq.com/v1/search_agent \n  --header "Authorization: Bearer $RIVETER_API_KEY" --header "Content-Type: application/json" \n  --data '{"prompt": "What is the NAICS code for Grab Holdings?", "wait": 50}'
  • prompt (string) — required: The question or task.
  • output_schema (object): JSON Schema for the answer; output.result then matches it.
  • wait (number): How long to block for the answer, max 50 seconds; 0 returns at once for polling.
  • run_key (string): Optional idempotency key; becomes the run id.
  • estimate_cost (boolean): Return the estimate instead of kicking off a run.
  • max_credits (number): Guardrail on credits per run.

Scrape

Returns a web page as text synchronously, through Riveter's proxies, with an optional country and cache bypass.

page = riveter.scrape("https://example.com")
print(page.text)  # synchronous: no run to poll
  • url (string) — required: The URL to scrape.
  • proxy_country_code (string): Two-letter proxy country, e.g. us, gb, de.
  • skip_cache (boolean): Bypass the cache and fetch fresh.
  • estimate_cost (boolean): Cost preview flag.
  • max_credits (number): Cap on the credit charge.

Account

Returns the plan, credit balance and API key metadata for the key in use.

info = riveter.account
puts info.account.credit.balance

How to Invoke

REST at https://api.riveterhq.com/v1 with Authorization: Bearer <API key>; the hosted MCP server at https://mcp.riveterhq.com/mcp or the local npx riveter-mcp-server, both exposing every endpoint as a typed tool; SDKs for TypeScript, Python, Ruby and Go; or pay-per-use through the Orthogonal Run API with api: riveter.

Pricing

Credits per action, bought as a monthly plan or pay-as-you-go (riveterhq.com/pricing, read 11 October 2026). Free: $0, 250 credits a month. Self-Serve: $249 a month, 10,000 credits. Enterprise: custom credits, dedicated infrastructure. Pay as you go: load credits from $15 up front, no expiration. Per action: agent search 1 credit; web scrape 0.05 credits a page; contact and profile data 2 credits a lookup; list building 2 to 4 credits a result. Every paid endpoint accepts estimate_cost and max_credits.

Strengths

  • estimate_cost and max_credits make the credit spend of a large enrichment predictable before it runs.
  • The hosted MCP server needs no local process: sign in once from claude.ai, Claude Code, ChatGPT or Cursor and a scoped key is issued.
  • Legacy verb-style endpoints keep working and announce their successor in a Deprecation header, so old integrations do not break silently.

Weaknesses

  • Most calls are asynchronous runs, so a chat agent needs the wait long-poll or a webhook before it can answer.
  • The documented rate limit of 30 requests a minute per endpoint group is low for agents that fan out many small calls.
  • The vendor's riveter.dev domain is a placeholder page, which can send people looking for the product to the wrong site.

Frequently Asked Questions

How are Riveter credits priced and what does the free plan include?

The free plan carries 250 credits a month with no card. Self-Serve is $249 a month for 10,000 credits, Enterprise is custom, and pay-as-you-go credits start at $15 and never expire. Per action the pricing page lists 1 credit for an agent search, 0.05 credits per scraped page, 2 credits per contact or profile lookup and 2 to 4 credits per list-building result.

What is the quickest way to give Claude or Cursor the Riveter tools?

Add the hosted server, https://mcp.riveterhq.com/mcp, as a connector or with the claude mcp add command, then authenticate in the browser window that opens; Riveter issues an API key for that connection which you can revoke from Settings. If the client cannot open a browser, pass an API key as an Authorization header, or run the local npx riveter-mcp-server with RIVETER_API_KEY set.

Which agents and languages does Riveter support beyond MCP?

Any HTTP client can call the REST API with a bearer key, and official client libraries cover TypeScript, Python, Ruby and Go. Claude, ChatGPT, Cursor, Windsurf and Codex are named in the docs for the MCP route, and the Orthogonal gateway resells the same endpoints pay-per-use for agents already on that platform.

When is another web-data skill the better buy than Riveter?

For fetching or crawling pages as Markdown without the enrichment layer, Olostep is simpler and cheaper per page. For driving a real browser through logins and forms, Kernel is the fit. Jina's Reader and Search suit read-and-embed pipelines, and SearchApi returns the actual Google results page when positions matter.

Riveter or Olostep for an agent that researches companies?

Olostep gives the agent the raw material: pages, crawls, search hits and a cited answer endpoint, and the agent does the reasoning. Riveter does the reasoning server-side and returns a table: you name the columns, it searches, scrapes and fills each cell with a source, and a monitor can re-run it weekly and report only what changed. Teams that already have an agent loop often start with Olostep; teams that want finished datasets go to Riveter.

Top Alternatives

  • Olostep: Pick Olostep for scraping, crawling and search as raw content; pick Riveter when the deliverable is a filled-in table with a source per cell.
  • Orthogonal Find Leads: Pick Orthogonal Find Leads for a quick pay-per-call lead pull inside the Orthogonal gateway; pick Riveter direct when you need saved enrichments, monitors and extractions.

More Agent Skills on HokAI

View the official Riveter skill page